Просмотр исходного кода

Support for `as` operator and user-defined `As` impls. (#1476)

Richard Smith 3 лет назад
Родитель
Сommit
93e19649aa

+ 1 - 0
common/fuzzing/carbon.proto

@@ -53,6 +53,7 @@ message PrimitiveOperatorExpression {
     Sub = 10;
     Ptr = 11;
     Combine = 12;
+    As = 13;
   }
   optional Operator op = 1;
   repeated Expression arguments = 2;

+ 4 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -106,6 +106,10 @@ static auto PrimitiveOperatorToCarbon(
       PrefixUnaryOperatorToCarbon("&", arg0, out);
       break;
 
+    case Fuzzing::PrimitiveOperatorExpression::As:
+      BinaryOperatorToCarbon(arg0, " as ", arg1, out);
+      break;
+
     case Fuzzing::PrimitiveOperatorExpression::Deref:
       PrefixUnaryOperatorToCarbon("*", arg0, out);
       break;

+ 2 - 0
explorer/ast/expression.cpp

@@ -61,6 +61,8 @@ auto ToString(Operator op) -> std::string_view {
   switch (op) {
     case Operator::Add:
       return "+";
+    case Operator::As:
+      return "as";
     case Operator::AddressOf:
     case Operator::Combine:
       return "&";

+ 17 - 0
explorer/ast/expression.h

@@ -115,6 +115,7 @@ enum class Operator {
   Add,
   AddressOf,
   And,
+  As,
   Combine,
   Deref,
   Eq,
@@ -506,9 +507,25 @@ class PrimitiveOperatorExpression : public Expression {
     return arguments_;
   }
 
+  // Set the rewritten form of this expression. Can only be called during type
+  // checking.
+  auto set_rewritten_form(const Expression* rewritten_form) -> void {
+    CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice";
+    rewritten_form_ = rewritten_form;
+    set_static_type(&rewritten_form->static_type());
+    set_value_category(rewritten_form->value_category());
+  }
+  // Get the rewritten form of this expression. A rewritten form is used when
+  // the expression is rewritten as a function call on an interface. A
+  // rewritten form is not used when providing built-in operator semantics.
+  auto rewritten_form() const -> std::optional<Nonnull<const Expression*>> {
+    return rewritten_form_;
+  }
+
  private:
   Operator op_;
   std::vector<Nonnull<Expression*>> arguments_;
+  std::optional<Nonnull<const Expression*>> rewritten_form_;
 };
 
 using ImplExpMap = std::map<Nonnull<const ImplBinding*>, Nonnull<Expression*>>;

+ 15 - 0
explorer/data/prelude.carbon

@@ -4,11 +4,26 @@
 
 package Carbon api;
 
+// Explicitly convert `Self` to `T`.
+interface As(T:! Type) {
+  fn Convert[me: Self]() -> T;
+}
+
 // Implicitly convert `Self` to `T`.
 interface ImplicitAs(T:! Type) {
   fn Convert[me: Self]() -> T;
 }
 
+// TODO: ImplicitAs(T) should extend As(T).
+impl forall [T:! Type, U:! ImplicitAs(T)] U as As(T) {
+  fn Convert[me: Self]() -> T { return me.Convert(); }
+}
+
+// Every type implicitly converts to itself.
+impl forall [T:! Type] T as ImplicitAs(T) {
+  fn Convert[me: Self]() -> T { return me; }
+}
+
 // TODO: Simplify this once we have variadics.
 // TODO: Should these be final?
 impl forall [U1:! Type, T1:! ImplicitAs(U1)]

+ 2 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -37,6 +37,8 @@ static auto OperatorToProtoEnum(const Operator op)
   switch (op) {
     case Operator::AddressOf:
       return Fuzzing::PrimitiveOperatorExpression::AddressOf;
+    case Operator::As:
+      return Fuzzing::PrimitiveOperatorExpression::As;
     case Operator::Deref:
       return Fuzzing::PrimitiveOperatorExpression::Deref;
     case Operator::Neg:

+ 9 - 0
explorer/interpreter/action_stack.cpp

@@ -176,6 +176,15 @@ auto ActionStack::Spawn(std::unique_ptr<Action> child, RuntimeScope scope)
   return Success();
 }
 
+auto ActionStack::ReplaceWith(std::unique_ptr<Action> replacement)
+    -> ErrorOr<Success> {
+  std::unique_ptr<Action> old = todo_.Pop();
+  CARBON_CHECK(replacement->kind() == old->kind())
+      << "ReplaceWith can't change action kind";
+  todo_.Push(std::move(replacement));
+  return Success();
+}
+
 auto ActionStack::RunAgain() -> ErrorOr<Success> {
   Action& action = *todo_.Top();
   action.set_pos(action.pos() + 1);

+ 3 - 0
explorer/interpreter/action_stack.h

@@ -87,6 +87,9 @@ class ActionStack {
   auto Spawn(std::unique_ptr<Action> child) -> ErrorOr<Success>;
   auto Spawn(std::unique_ptr<Action> child, RuntimeScope scope)
       -> ErrorOr<Success>;
+  // Replace the current action with another action of the same kind and run it
+  // next.
+  auto ReplaceWith(std::unique_ptr<Action> child) -> ErrorOr<Success>;
 
   // Start a new recursive action.
   auto BeginRecursiveAction() {

+ 3 - 0
explorer/interpreter/builtins.cpp

@@ -15,6 +15,9 @@ void Builtins::Register(Nonnull<const Declaration*> decl) {
     if (interface->name() == GetName(Builtin::ImplicitAs)) {
       builtins_[static_cast<int>(Builtin::ImplicitAs)] = interface;
     }
+    if (interface->name() == GetName(Builtin::As)) {
+      builtins_[static_cast<int>(Builtin::As)] = interface;
+    }
   }
 }
 

+ 3 - 2
explorer/interpreter/builtins.h

@@ -20,9 +20,10 @@ class Builtins {
  public:
   explicit Builtins() {}
 
-  enum class Builtin { ImplicitAs, Last = ImplicitAs };
+  enum class Builtin { ImplicitAs, As, Last = As };
   // TODO: In C++20, replace with `using enum Builtin;`.
   static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
+  static constexpr Builtin As = Builtin::As;
 
   // Register a declaration that might be a builtin.
   void Register(Nonnull<const Declaration*> decl);
@@ -38,7 +39,7 @@ class Builtins {
 
  private:
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
-  static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs"};
+  static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs", "As"};
 
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
 };

+ 8 - 0
explorer/interpreter/interpreter.cpp

@@ -208,6 +208,8 @@ auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
       return arena_->New<PointerValue>(cast<LValue>(*args[0]).address());
     case Operator::Combine:
       return &cast<TypeOfConstraintType>(static_type)->constraint_type();
+    case Operator::As:
+      return Convert(args[0], args[1], source_loc);
   }
 }
 
@@ -420,6 +422,9 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
     }
     case ExpressionKind::PrimitiveOperatorExpression: {
       const auto& op = cast<PrimitiveOperatorExpression>(exp);
+      if (auto rewrite = op.rewritten_form()) {
+        return todo_.ReplaceWith(std::make_unique<LValAction>(*rewrite));
+      }
       if (op.op() != Operator::Deref) {
         CARBON_FATAL()
             << "Can't treat primitive operator expression as lvalue: " << exp;
@@ -1079,6 +1084,9 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           arena_->New<BoolValue>(cast<BoolLiteral>(exp).value()));
     case ExpressionKind::PrimitiveOperatorExpression: {
       const auto& op = cast<PrimitiveOperatorExpression>(exp);
+      if (auto rewrite = op.rewritten_form()) {
+        return todo_.ReplaceWith(std::make_unique<ExpressionAction>(*rewrite));
+      }
       if (act.pos() != static_cast<int>(op.arguments().size())) {
         //    { {v :: op(vs,[],e,es) :: C, E, F} :: S, H}
         // -> { {e :: op(vs,v,[],es) :: C, E, F} :: S, H}

+ 20 - 0
explorer/interpreter/type_checker.cpp

@@ -2052,6 +2052,26 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           op.set_value_category(ValueCategory::Let);
           return Success();
         }
+        case Operator::As: {
+          CARBON_ASSIGN_OR_RETURN(
+              Nonnull<const Value*> type,
+              InterpExp(op.arguments()[1], arena_, trace_stream_));
+          CARBON_RETURN_IF_ERROR(
+              ExpectIsConcreteType(op.arguments()[1]->source_loc(), type));
+          ErrorOr<Nonnull<Expression*>> converted =
+              BuildBuiltinMethodCall(impl_scope, op.arguments()[0],
+                                     BuiltinInterfaceName{Builtins::As, type},
+                                     BuiltinMethodCall{"Convert"});
+          if (!converted.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << "type error in `as`: `" << *ts[0]
+                   << "` is not explicitly convertible to `" << *type << "`:\n"
+                   << converted.error().message();
+          }
+          op.set_rewritten_form(*converted);
+          return Success();
+        }
       }
       break;
     }

+ 22 - 2
explorer/syntax/parser.ypp

@@ -130,6 +130,8 @@
 %type <Nonnull<Expression*>> additive_operand
 %type <Nonnull<Expression*>> additive_lhs
 %type <Nonnull<Expression*>> additive_expression
+%type <Nonnull<Expression*>> as_operand
+%type <Nonnull<Expression*>> as_expression
 %type <Nonnull<Expression*>> unimpl_expression
 %type <Nonnull<Expression*>> value_expression
 %type <Nonnull<Expression*>> comparison_operand
@@ -144,6 +146,7 @@
 %type <Nonnull<WhereClause*>> where_clause
 %type <std::vector<Nonnull<WhereClause*>>> where_clause_list
 %type <Nonnull<Expression*>> where_expression
+%type <Nonnull<Expression*>> type_or_where_expression
 %type <Nonnull<Expression*>> statement_expression
 %type <Nonnull<Expression*>> if_expression
 %type <Nonnull<Expression*>> expression
@@ -466,6 +469,18 @@ additive_expression:
           std::vector<Nonnull<Expression*>>({$1, $3}));
     }
 ;
+as_operand:
+  minus_expression
+| ref_deref_expression
+;
+as_expression:
+  as_operand AS as_operand
+    {
+      $$ = arena->New<PrimitiveOperatorExpression>(
+          context.source_loc(), Operator::As,
+          std::vector<Nonnull<Expression*>>{$1, $3});
+    }
+;
 unimpl_expression:
   // ref_deref_expression excluded due to precedence diamond.
   ref_deref_expression UNIMPL_EXAMPLE ref_deref_expression
@@ -477,6 +492,7 @@ unimpl_expression:
 value_expression:
   // ref_deref_expression excluded due to precedence diamond.
   additive_expression
+| as_expression
 | combine_expression
 | fn_type_expression
 | unimpl_expression
@@ -560,6 +576,10 @@ where_expression:
       $$ = arena->New<WhereExpression>(context.source_loc(), self, $3);
     }
 ;
+type_or_where_expression:
+  type_expression
+| where_expression
+;
 statement_expression:
   ref_deref_expression
 | predicate_expression
@@ -998,7 +1018,7 @@ declaration:
       $$ = arena->New<InterfaceDeclaration>(context.source_loc(), $2, $3, self,
                                             $5);
     }
-| impl_kind IMPL impl_deduced_params impl_type AS expression LEFT_CURLY_BRACE impl_body RIGHT_CURLY_BRACE
+| impl_kind IMPL impl_deduced_params impl_type AS type_or_where_expression LEFT_CURLY_BRACE impl_body RIGHT_CURLY_BRACE
     {
       ErrorOr<ImplDeclaration*> impl = ImplDeclaration::Create(
           arena, context.source_loc(), $1, $4, $6, $3, $8);
@@ -1021,7 +1041,7 @@ impl_kind:
 impl_type:
   // Self
     { $$ = arena->New<IdentifierExpression>(context.source_loc(), "Self"); }
-| expression
+| type_expression
 ;
 declaration_list:
   // Empty

+ 17 - 0
explorer/testdata/as/as_same_type.carbon

@@ -0,0 +1,17 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 5
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  let n: i32 = 5;
+  return n as i32;
+}

+ 23 - 0
explorer/testdata/as/convert.carbon

@@ -0,0 +1,23 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 5
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as As(i32) {
+  fn Convert[me: Self]() -> i32 { return me.n; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  return a as i32;
+}

+ 16 - 0
explorer/testdata/as/fail_destination_not_type.carbon

@@ -0,0 +1,16 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/as/fail_destination_not_type.carbon:[[@LINE+1]]: Expected a type, but got 7
+  return 4 as 7;
+}

+ 20 - 0
explorer/testdata/as/fail_no_conversion.carbon

@@ -0,0 +1,20 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/as/fail_no_conversion.carbon:[[@LINE+2]]: type error in `as`: `class A` is not explicitly convertible to `i32`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/as/fail_no_conversion.carbon:[[@LINE+1]]: could not find implementation of interface As(T = i32) for class A
+  return a as i32;
+}

+ 23 - 0
explorer/testdata/as/implicit_as.carbon

@@ -0,0 +1,23 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 5
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as ImplicitAs(i32) {
+  fn Convert[me: Self]() -> i32 { return me.n; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  return a as i32;
+}

+ 0 - 4
explorer/testdata/generic_function/generic_method.carbon

@@ -30,10 +30,6 @@ class Integer {
   var int: i32;
 }
 
-impl Integer as ImplicitAs(Integer) {
-  fn Convert[me: Self]() -> Integer { return me; }
-}
-
 fn Main() -> i32 {
   var i: Integer = {.int = 1};
   var c: Cell(Integer) = Cell(Integer).Create(i);

+ 0 - 4
explorer/testdata/impl/generic_method_impl.carbon

@@ -33,10 +33,6 @@ class Integer {
   var int: i32;
 }
 
-impl Integer as ImplicitAs(Integer) {
-  fn Convert[me: Self]() -> Integer { return me; }
-}
-
 fn Main() -> i32 {
   var i: Integer = {.int = 1};
   var c: Cell(Integer) = Cell(Integer).Create(i); // c contains 1