Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support user-defined implicit conversions via ImplicitAs #1273

Merged
merged 31 commits into from
May 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fbf33ae
Support `impl as` in generic classes.
zygoloid May 16, 2022
43ed1fc
Start implementing ImplicitAs.
zygoloid Apr 27, 2022
294c6f7
Implicit conversion for `while` conditions.
zygoloid May 17, 2022
f0c8e3d
Split TypeCheckExp into a function to recursively type-check operands…
zygoloid May 17, 2022
6f66126
Use single-expression typechecking to avoid skip_typechecking_expr hack.
zygoloid May 17, 2022
bbf24ab
Factor out builtin tracking into a separate class.
zygoloid May 17, 2022
2aaf688
Merge branch 'explorer-impl-as-in-generic-class' into explorer-implic…
zygoloid May 17, 2022
cb431de
Support deducing the parameters of a parameterized interface.
zygoloid May 17, 2022
81e9791
Autoformat
zygoloid May 17, 2022
e25a1ef
Support for implicit conversion on left of `.`.
zygoloid May 17, 2022
6fb8771
Support implicit conversion in condition of `if` expression.
zygoloid May 17, 2022
00d3bec
Suppot implicit conversions on the RHS of `=`.
zygoloid May 17, 2022
e71e814
Support implicit conversion for the condition of an `if` statement.
zygoloid May 17, 2022
8de4baa
Support implicit conversion in a return statement.
zygoloid May 17, 2022
d9cba7e
Support implicit conversion in variable initializers.
zygoloid May 17, 2022
9dc0fa1
Support implicit conversions when pattern-matching a choice value.
zygoloid May 18, 2022
995862e
Support implicit conversions in `match` statements.
zygoloid May 18, 2022
3534f00
Support implicit conversion in the operand of `__run`.
zygoloid May 18, 2022
566f58b
Make user-defined implicit conversions less optional.
zygoloid May 18, 2022
d142d52
Reformat.
zygoloid May 18, 2022
c6349ee
Fix build failure in fuzzer from new ValueLiteral node.
zygoloid May 19, 2022
069f527
Merge branch 'trunk' into explorer-implicitas
zygoloid May 19, 2022
eee2e57
Revert "Split TypeCheckExp into a function to recursively type-check …
zygoloid May 19, 2022
d2af4ad
Responses to review comments.
zygoloid May 20, 2022
26239fc
Add test for missing parens in choice pattern match.
zygoloid May 20, 2022
dcbaf0d
Add missing %{not}s.
zygoloid May 20, 2022
bd6263d
Merge branch 'trunk' into explorer-implicitas
zygoloid May 20, 2022
defdf2f
Fix test failure after merge: basic support for deduction of non-type…
zygoloid May 20, 2022
2402ccf
Merge branch 'trunk' into explorer-implicitas
zygoloid May 20, 2022
30894b3
More fallout from adding tests covering non-type class and interface
zygoloid May 20, 2022
a7a6ddd
Merge branch 'trunk' into explorer-implicitas
zygoloid May 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions explorer/ast/ast_rtti.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ abstract class Expression : AstNode;
class StructLiteral : Expression;
class StructTypeLiteral : Expression;
class TypeTypeLiteral : Expression;
class ValueLiteral : Expression;
class IdentifierExpression : Expression;
class IntrinsicExpression : Expression;
class IfExpression : Expression;
Expand Down
6 changes: 6 additions & 0 deletions explorer/ast/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ class VariableDeclaration : public Declaration {

auto has_initializer() const -> bool { return initializer_.has_value(); }

// Can only be called by type-checking, if a conversion was required.
void set_initializer(Nonnull<Expression*> initializer) {
CARBON_CHECK(has_initializer()) << "should not add a new initializer";
initializer_ = initializer;
}

private:
// TODO: split this into a non-optional name and a type, initialized by
// a constructor that takes a BindingPattern and handles errors like a
Expand Down
5 changes: 5 additions & 0 deletions explorer/ast/expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ void Expression::Print(llvm::raw_ostream& out) const {
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::TypeTypeLiteral:
case ExpressionKind::ContinuationTypeLiteral:
case ExpressionKind::ValueLiteral:
PrintID(out);
break;
}
Expand Down Expand Up @@ -236,6 +237,10 @@ void Expression::PrintID(llvm::raw_ostream& out) const {
case ExpressionKind::ContinuationTypeLiteral:
out << "Continuation";
break;
case ExpressionKind::ValueLiteral:
// FIXME: For layering reasons, we can't print out the value from here.
out << "ValueLiteral";
break;
case ExpressionKind::IndexExpression:
case ExpressionKind::FieldAccessExpression:
case ExpressionKind::CompoundFieldAccessExpression:
Expand Down
38 changes: 38 additions & 0 deletions explorer/ast/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ class Expression : public AstNode {
value_category_ = value_category;
}

// Determines whether the expression has already been type-checked. Should
// only be used by type-checking.
auto is_type_checked() -> bool {
return static_type_.has_value() && value_category_.has_value();
}

protected:
// Constructs an Expression representing syntax at the given line number.
// `kind` must be the enumerator corresponding to the most-derived type being
Expand Down Expand Up @@ -238,6 +244,9 @@ class CompoundFieldAccessExpression : public Expression {
impl_ = impl;
}

// Can only be called by type-checking, if a conversion was required.
void set_object(Nonnull<Expression*> object) { object_ = object; }

private:
Nonnull<Expression*> object_;
Nonnull<Expression*> path_;
Expand Down Expand Up @@ -467,6 +476,9 @@ class CallExpression : public Expression {
deduced_args_ = deduced_args;
}

// Can only be called by type-checking, if a conversion was required.
void set_argument(Nonnull<Expression*> argument) { argument_ = argument; }

private:
Nonnull<Expression*> function_;
Nonnull<Expression*> argument_;
Expand Down Expand Up @@ -537,6 +549,29 @@ class TypeTypeLiteral : public Expression {
}
};

// A literal value. This is used in desugaring, and can't be expressed in
// source syntax.
class ValueLiteral : public Expression {
public:
// Value literals are created by type-checking, and so are created with their
// type and value category already known.
ValueLiteral(SourceLocation source_loc, Nonnull<const Value*> value,
Nonnull<const Value*> type, ValueCategory value_category)
: Expression(AstNodeKind::ValueLiteral, source_loc), value_(value) {
set_static_type(type);
set_value_category(value_category);
}

static auto classof(const AstNode* node) -> bool {
return InheritsFromValueLiteral(node->kind());
}

auto value() const -> const Value& { return *value_; }

private:
Nonnull<const Value*> value_;
};

class IntrinsicExpression : public Expression {
public:
enum class Intrinsic {
Expand Down Expand Up @@ -595,6 +630,9 @@ class IfExpression : public Expression {
}
auto else_expression() -> Expression& { return *else_expression_; }

// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }

private:
Nonnull<Expression*> condition_;
Nonnull<Expression*> then_expression_;
Expand Down
25 changes: 25 additions & 0 deletions explorer/ast/statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class Assign : public Statement {
auto rhs() const -> const Expression& { return *rhs_; }
auto rhs() -> Expression& { return *rhs_; }

// Can only be called by type-checking, if a conversion was required.
void set_rhs(Nonnull<Expression*> rhs) { rhs_ = rhs; }
Comment on lines +104 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where you're adding setters, is there anything this can do relative to checking type checking of rhs_ or rhs that would help enforce the "Can only"? Maybe it would make sense to do something like "set_desugared_rhs" which could only be called once, and a desugared_rhs getter that returns desugared_rhs if set, rhs otherwise?

Note, I'm wondering about this mainly in the larger context of adding setters. This could go a step further with a templated type, e.g.:

template <typename T>
class Desugarable {
 public:
  Desugarable(Nonnull<T*> original) : original_ {}
  // Should be called at most once by type-checking.
  void set_desugared(Nonnull<T*> desugared) { CARBON_CHECK(!desugarded_); desugared_ = desugarded; }

  // Would it even work to have the non-const version CARBON_CHECK if desugared_ is already set, in order to prevent accidents?
  auto original() -> T& { return original_; }
  auto original() const -> const T& { return original_; }
  auto desugared() const -> const T& { return desugared_ ? *desugarded_ : original_; }

 private:
  Nonnull<T*> original_;
  std::optional<Nonnull<T*>> desugarded_;
};

Then maybe something like this becomes:

auto rhs() const -> const Desugurable<Expression>& { return rhs_; }
auto rhs() -> Desugurable<Expression>& { return rhs_; }
Desugarable<Expression> rhs_;

However, that's a much higher code delta, especially because it affects interpreter call sites. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is probably a better direction here, but I think it's likely to take a bit of discussion to hammer out the best approach. Do you want this addressed before this change lands, or can this be deferred to a later refactoring?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably reasonable to defer to a later refactoring, that was part of my concern over size of the delta.


private:
Nonnull<Expression*> lhs_;
Nonnull<Expression*> rhs_;
Expand All @@ -125,6 +128,9 @@ class VariableDefinition : public Statement {
auto init() -> Expression& { return *init_; }
auto value_category() const -> ValueCategory { return value_category_; }

// Can only be called by type-checking, if a conversion was required.
void set_init(Nonnull<Expression*> init) { init_ = init; }

private:
Nonnull<Pattern*> pattern_;
Nonnull<Expression*> init_;
Expand Down Expand Up @@ -153,6 +159,9 @@ class If : public Statement {
}
auto else_block() -> std::optional<Nonnull<Block*>> { return else_block_; }

// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }

private:
Nonnull<Expression*> condition_;
Nonnull<Block*> then_block_;
Expand Down Expand Up @@ -186,6 +195,11 @@ class Return : public Statement {
auto function() const -> const FunctionDeclaration& { return **function_; }
auto function() -> FunctionDeclaration& { return **function_; }

// Can only be called by type-checking, if a conversion was required.
void set_expression(Nonnull<Expression*> expression) {
expression_ = expression;
}

// Can only be called once, by ResolveControlFlow.
void set_function(Nonnull<FunctionDeclaration*> function) {
CARBON_CHECK(!function_.has_value());
Expand Down Expand Up @@ -215,6 +229,9 @@ class While : public Statement {
auto body() const -> const Block& { return *body_; }
auto body() -> Block& { return *body_; }

// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }

private:
Nonnull<Expression*> condition_;
Nonnull<Block*> body_;
Expand Down Expand Up @@ -306,6 +323,11 @@ class Match : public Statement {
auto clauses() const -> llvm::ArrayRef<Clause> { return clauses_; }
auto clauses() -> llvm::MutableArrayRef<Clause> { return clauses_; }

// Can only be called by type-checking, if a conversion was required.
void set_expression(Nonnull<Expression*> expression) {
expression_ = expression;
}

private:
Nonnull<Expression*> expression_;
std::vector<Clause> clauses_;
Expand Down Expand Up @@ -376,6 +398,9 @@ class Run : public Statement {
auto argument() const -> const Expression& { return *argument_; }
auto argument() -> Expression& { return *argument_; }

// Can only be called by type-checking, if a conversion was required.
void set_argument(Nonnull<Expression*> argument) { argument_ = argument; }

private:
Nonnull<Expression*> argument_;
};
Expand Down
30 changes: 30 additions & 0 deletions explorer/data/prelude.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@

package Carbon api;

// Implicitly convert `Self` to `T`.
interface ImplicitAs(T:! Type) {
fn Convert[me: Self]() -> T;
}

// TODO: Simplify this once we have variadics.
// TODO: Should these be final?
impl forall [U1:! Type, T1:! ImplicitAs(U1)]
(T1,) as ImplicitAs((U1,)) {
fn Convert[me: Self]() -> (U1,) {
let (v1: T1,) = me;
return (v1.Convert(),);
}
}
impl forall [U1:! Type, U2:! Type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)]
(T1, T2) as ImplicitAs((U1, U2)) {
fn Convert[me: Self]() -> (U1, U2) {
let (v1: T1, v2: T2) = me;
return (v1.Convert(), v2.Convert());
}
}
impl forall [U1:! Type, U2:! Type, U3:! Type,
T1:! ImplicitAs(U1), T2:! ImplicitAs(U2), T3:! ImplicitAs(U3)]
(T1, T2, T3) as ImplicitAs((U1, U2, U3)) {
fn Convert[me: Self]() -> (U1, U2, U3) {
let (v1: T1, v2: T2, v3: T3) = me;
return (v1.Convert(), v2.Convert(), v3.Convert());
}
}

// Note that Print is experimental, and not part of an accepted proposal, but
// is included here for printing state in tests.
fn Print(format_str: String) {
Expand Down
5 changes: 3 additions & 2 deletions explorer/fuzzing/ast_to_proto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ static auto ExpressionToProto(const Expression& expression)
-> Fuzzing::Expression {
Fuzzing::Expression expression_proto;
switch (expression.kind()) {
case ExpressionKind::InstantiateImpl: {
// UNDER CONSTRUCTION
case ExpressionKind::InstantiateImpl:
case ExpressionKind::ValueLiteral: {
// These do not correspond to source syntax.
break;
}
case ExpressionKind::CallExpression: {
Expand Down
4 changes: 4 additions & 0 deletions explorer/interpreter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -192,17 +192,20 @@ cc_library(
cc_library(
name = "type_checker",
srcs = [
"builtins.cpp",
"impl_scope.cpp",
"type_checker.cpp",
],
hdrs = [
"builtins.h",
"impl_scope.h",
"type_checker.h",
],
deps = [
":action_and_value",
":dictionary",
":interpreter",
"//common:error",
"//common:ostream",
"//explorer/ast",
"//explorer/ast:declaration",
Expand All @@ -211,6 +214,7 @@ cc_library(
"//explorer/common:arena",
"//explorer/common:error_builders",
"//explorer/common:nonnull",
"//explorer/common:source_location",
"@llvm-project//llvm:Support",
],
)
32 changes: 32 additions & 0 deletions explorer/interpreter/builtins.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "explorer/interpreter/builtins.h"

#include "explorer/common/error_builders.h"

using llvm::dyn_cast;

namespace Carbon {

void Builtins::Register(Nonnull<const Declaration*> decl) {
if (auto* interface = dyn_cast<InterfaceDeclaration>(decl)) {
if (interface->name() == GetName(Builtin::ImplicitAs)) {
builtins_[static_cast<int>(Builtin::ImplicitAs)] = interface;
}
}
}

auto Builtins::Get(SourceLocation source_loc, Builtin builtin) const
-> ErrorOr<Nonnull<const Declaration*>> {
std::optional<const Declaration*> result =
builtins_[static_cast<int>(builtin)];
if (!result.has_value()) {
return CompilationError(source_loc)
<< "missing declaration for builtin `" << GetName(builtin) << "`";
}
return result.value();
}

} // namespace Carbon
48 changes: 48 additions & 0 deletions explorer/interpreter/builtins.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_EXPLORER_INTERPRETER_BUILTINS_H_
#define CARBON_EXPLORER_INTERPRETER_BUILTINS_H_

#include <optional>

#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/expression.h"
#include "explorer/common/nonnull.h"
#include "explorer/common/source_location.h"
#include "explorer/interpreter/value.h"

namespace Carbon {

class Builtins {
public:
explicit Builtins() {}

enum class Builtin { ImplicitAs, Last = ImplicitAs };
// FIXME: In C++20, replace with `using enum Builtin;`.
static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;

// Register a declaration that might be a builtin.
void Register(Nonnull<const Declaration*> decl);

// Get a registered builtin.
auto Get(SourceLocation source_loc, Builtin builtin) const
-> ErrorOr<Nonnull<const Declaration*>>;

// Get the source name of a builtin.
static constexpr auto GetName(Builtin builtin) -> const char* {
return BuiltinNames[static_cast<int>(builtin)];
}

private:
static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs"};

std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
};

} // namespace Carbon

#endif // CARBON_EXPLORER_INTERPRETER_BUILTINS_H_
5 changes: 5 additions & 0 deletions explorer/interpreter/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
case ExpressionKind::ContinuationTypeLiteral:
case ExpressionKind::StringLiteral:
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::ValueLiteral:
case ExpressionKind::IntrinsicExpression:
case ExpressionKind::IfExpression:
case ExpressionKind::ArrayTypeLiteral:
Expand Down Expand Up @@ -1042,6 +1043,10 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
CARBON_CHECK(act.pos() == 0);
return todo_.FinishAction(arena_->New<StringType>());
}
case ExpressionKind::ValueLiteral: {
CARBON_CHECK(act.pos() == 0);
return todo_.FinishAction(&cast<ValueLiteral>(exp).value());
}
case ExpressionKind::IfExpression: {
const auto& if_expr = cast<IfExpression>(exp);
if (act.pos() == 0) {
Expand Down
1 change: 1 addition & 0 deletions explorer/interpreter/resolve_names.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ static auto ResolveNames(Expression& expression,
case ExpressionKind::StringLiteral:
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::TypeTypeLiteral:
case ExpressionKind::ValueLiteral:
break;
case ExpressionKind::InstantiateImpl: // created after name resolution
case ExpressionKind::UnimplementedExpression:
Expand Down
Loading