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

Basic support for `match_first` declarations. (#2523)

Add basic support for `match_first` declarations to explorer, as described in https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#prioritization-rule. The concrete syntax here is not yet approved, so `match_first` is renamed to `__match_first` for now, but the semantics are necessary to implement other approved features in explorer so we're intentionally getting a little ahead of the design here.
Richard Smith 3 лет назад
Родитель
Сommit
74aae0911f

+ 5 - 0
common/fuzzing/carbon.proto

@@ -410,6 +410,10 @@ message ImplDeclaration {
   repeated Declaration members = 4;
 }
 
+message MatchFirstDeclaration {
+  repeated Declaration impls = 1;
+}
+
 message AliasDeclaration {
   optional string name = 1;
   optional Expression target = 2;
@@ -445,6 +449,7 @@ message Declaration {
     InterfaceExtendsDeclaration interface_extends = 12;
     InterfaceImplDeclaration interface_impl = 13;
     ConstraintDeclaration constraint = 14;
+    MatchFirstDeclaration match_first = 15;
   }
 }
 

+ 11 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -890,6 +890,17 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
       break;
     }
 
+    case Fuzzing::Declaration::kMatchFirst: {
+      const auto& match_first = declaration.match_first();
+      out << "__match_first {\n";
+      for (const auto& impl : match_first.impls()) {
+        DeclarationToCarbon(impl, out);
+        out << "\n";
+      }
+      out << "}";
+      break;
+    }
+
     case Fuzzing::Declaration::kAlias: {
       const auto& alias = declaration.alias();
       out << "alias ";

+ 1 - 0
explorer/ast/ast_rtti.txt

@@ -29,6 +29,7 @@ abstract class Declaration : AstNode;
   class InterfaceImplDeclaration : Declaration;
   class AssociatedConstantDeclaration : Declaration;
   class ImplDeclaration : Declaration;
+  class MatchFirstDeclaration : Declaration;
   class AliasDeclaration : Declaration;
 class ImplBinding : AstNode;
 class AlternativeSignature : AstNode;

+ 14 - 0
explorer/ast/declaration.cpp

@@ -36,6 +36,16 @@ void Declaration::Print(llvm::raw_ostream& out) const {
       out << "}\n";
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration: {
+      const auto& match_first_decl = cast<MatchFirstDeclaration>(*this);
+      PrintID(out);
+      out << " {\n";
+      for (Nonnull<const ImplDeclaration*> m : match_first_decl.impls()) {
+        out << *m;
+      }
+      out << "}\n";
+      break;
+    }
     case DeclarationKind::FunctionDeclaration:
       cast<FunctionDeclaration>(*this).PrintDepth(-1, out);
       break;
@@ -139,6 +149,9 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
           << impl_decl.interface();
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration:
+      out << "match_first";
+      break;
     case DeclarationKind::FunctionDeclaration:
       out << "fn " << cast<FunctionDeclaration>(*this).name();
       break;
@@ -232,6 +245,7 @@ auto GetName(const Declaration& declaration)
     case DeclarationKind::InterfaceExtendsDeclaration:
     case DeclarationKind::InterfaceImplDeclaration:
     case DeclarationKind::ImplDeclaration:
+    case DeclarationKind::MatchFirstDeclaration:
       return std::nullopt;
     case DeclarationKind::SelfDeclaration:
       return SelfDeclaration::name();

+ 33 - 0
explorer/ast/declaration.h

@@ -29,6 +29,7 @@ namespace Carbon {
 class MixinPseudoType;
 class ConstraintType;
 class NominalClassType;
+class MatchFirstDeclaration;
 
 // Abstract base class of all AST nodes representing patterns.
 //
@@ -740,6 +741,17 @@ class ImplDeclaration : public Declaration {
   auto self() const -> Nonnull<const SelfDeclaration*> { return self_decl_; }
   auto self() -> Nonnull<SelfDeclaration*> { return self_decl_; }
 
+  // Set the enclosing match_first declaration. Should only be called once,
+  // during type-checking.
+  void set_match_first(Nonnull<const MatchFirstDeclaration*> match_first) {
+    match_first_ = match_first;
+  }
+  // Get the enclosing match_first declaration, if any exists.
+  auto match_first() const
+      -> std::optional<Nonnull<const MatchFirstDeclaration*>> {
+    return match_first_;
+  }
+
  private:
   ImplKind kind_;
   Nonnull<Expression*> impl_type_;
@@ -749,6 +761,27 @@ class ImplDeclaration : public Declaration {
   std::vector<Nonnull<GenericBinding*>> deduced_parameters_;
   std::vector<Nonnull<Declaration*>> members_;
   std::vector<Nonnull<const ImplBinding*>> impl_bindings_;
+  std::optional<Nonnull<const MatchFirstDeclaration*>> match_first_;
+};
+
+class MatchFirstDeclaration : public Declaration {
+ public:
+  MatchFirstDeclaration(SourceLocation source_loc,
+                        std::vector<Nonnull<ImplDeclaration*>> impls)
+      : Declaration(AstNodeKind::MatchFirstDeclaration, source_loc),
+        impls_(std::move(impls)) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromMatchFirstDeclaration(node->kind());
+  }
+
+  auto impls() const -> llvm::ArrayRef<Nonnull<const ImplDeclaration*>> {
+    return impls_;
+  }
+  auto impls() -> llvm::ArrayRef<Nonnull<ImplDeclaration*>> { return impls_; }
+
+ private:
+  std::vector<Nonnull<ImplDeclaration*>> impls_;
 };
 
 class AliasDeclaration : public Declaration {

+ 9 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -774,6 +774,15 @@ static auto DeclarationToProto(const Declaration& declaration)
       break;
     }
 
+    case DeclarationKind::MatchFirstDeclaration: {
+      const auto& match_first = cast<MatchFirstDeclaration>(declaration);
+      auto* match_first_proto = declaration_proto.mutable_match_first();
+      for (const auto* impl : match_first.impls()) {
+        *match_first_proto->add_impls() = DeclarationToProto(*impl);
+      }
+      break;
+    }
+
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable SelfDeclaration in DeclarationToProto().";
     }

+ 20 - 0
explorer/interpreter/impl_scope.cpp

@@ -215,6 +215,7 @@ static auto CombineResults(Nonnull<const InterfaceType*> iface_type,
   if (!a) {
     return b;
   }
+
   // If either of them was a symbolic result, then they'll end up being
   // equivalent. In that case, pick `a`.
   const auto* impl_a = dyn_cast<ImplWitness>(*a);
@@ -225,13 +226,32 @@ static auto CombineResults(Nonnull<const InterfaceType*> iface_type,
   if (!impl_a) {
     return b;
   }
+
   // If they refer to the same `impl` declaration, it doesn't matter which one
   // we pick, so we pick `a`.
   // TODO: Compare the identities of the `impl`s, not the declarations.
   if (&impl_a->declaration() == &impl_b->declaration()) {
     return a;
   }
+
   // TODO: Order the `impl`s based on type structure.
+
+  // If the declarations appear in the same `match_first` block, whichever
+  // appears first wins.
+  // TODO: Once we support an impl being declared more than once, we will need
+  // to check this more carefully.
+  if (impl_a->declaration().match_first() &&
+      impl_a->declaration().match_first() ==
+          impl_b->declaration().match_first()) {
+    for (auto* impl : (*impl_a->declaration().match_first())->impls()) {
+      if (impl == &impl_a->declaration()) {
+        return a;
+      }
+      if (impl == &impl_b->declaration()) {
+        return b;
+      }
+    }
+  }
   return ProgramError(source_loc)
          << "ambiguous implementations of " << *iface_type << " for " << *type;
 }

+ 1 - 0
explorer/interpreter/interpreter.cpp

@@ -2061,6 +2061,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
     case DeclarationKind::InterfaceImplDeclaration:
     case DeclarationKind::AssociatedConstantDeclaration:
     case DeclarationKind::ImplDeclaration:
+    case DeclarationKind::MatchFirstDeclaration:
     case DeclarationKind::SelfDeclaration:
     case DeclarationKind::AliasDeclaration:
       // These declarations have no run-time effects.

+ 7 - 0
explorer/interpreter/resolve_control_flow.cpp

@@ -178,6 +178,13 @@ auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
       }
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration: {
+      auto& match_first_decl = cast<MatchFirstDeclaration>(*declaration);
+      for (Nonnull<Declaration*> impl : match_first_decl.impls()) {
+        CARBON_RETURN_IF_ERROR(ResolveControlFlow(impl));
+      }
+      break;
+    }
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::VariableDeclaration:
     case DeclarationKind::InterfaceExtendsDeclaration:

+ 8 - 0
explorer/interpreter/resolve_names.cpp

@@ -94,6 +94,7 @@ static auto AddExposedNames(const Declaration& declaration,
       break;
     }
     case DeclarationKind::ImplDeclaration:
+    case DeclarationKind::MatchFirstDeclaration:
     case DeclarationKind::MixDeclaration:
     case DeclarationKind::InterfaceExtendsDeclaration:
     case DeclarationKind::InterfaceImplDeclaration: {
@@ -558,6 +559,13 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
           ResolveMemberNames(impl.members(), impl_scope, bodies));
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration: {
+      // A `match_first` declaration does not introduce a scope.
+      for (auto* impl : cast<MatchFirstDeclaration>(declaration).impls()) {
+        CARBON_RETURN_IF_ERROR(ResolveNames(*impl, enclosing_scope, bodies));
+      }
+      break;
+    }
     case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration: {
       auto& function = cast<CallableDeclaration>(declaration);

+ 1 - 0
explorer/interpreter/resolve_unformed.cpp

@@ -306,6 +306,7 @@ static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
     case DeclarationKind::InterfaceDeclaration:
     case DeclarationKind::ConstraintDeclaration:
     case DeclarationKind::ImplDeclaration:
+    case DeclarationKind::MatchFirstDeclaration:
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::VariableDeclaration:
     case DeclarationKind::InterfaceExtendsDeclaration:

+ 14 - 0
explorer/interpreter/type_checker.cpp

@@ -5458,6 +5458,14 @@ auto TypeChecker::TypeCheckDeclaration(
           TypeCheckImplDeclaration(&cast<ImplDeclaration>(*d), impl_scope));
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration: {
+      auto* match_first = cast<MatchFirstDeclaration>(d);
+      for (auto* impl : match_first->impls()) {
+        impl->set_match_first(match_first);
+        CARBON_RETURN_IF_ERROR(TypeCheckImplDeclaration(impl, impl_scope));
+      }
+      break;
+    }
     case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration:
       CARBON_RETURN_IF_ERROR(TypeCheckCallableDeclaration(
@@ -5535,6 +5543,12 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
       CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(&impl_decl, scope_info));
       break;
     }
+    case DeclarationKind::MatchFirstDeclaration: {
+      for (auto* impl : cast<MatchFirstDeclaration>(d)->impls()) {
+        CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(impl, scope_info));
+      }
+      break;
+    }
     case DeclarationKind::FunctionDeclaration: {
       auto& func_def = cast<CallableDeclaration>(*d);
       CARBON_RETURN_IF_ERROR(DeclareCallableDeclaration(&func_def, scope_info));

+ 2 - 0
explorer/syntax/lexer.lpp

@@ -86,6 +86,7 @@ LESS_LESS            "<<"
 LET                  "let"
 LIBRARY              "library"
 MATCH                "match"
+MATCH_FIRST          "__match_first"
 MINUS                "-"
 MIX                  "__mix"
 MIXIN                "__mixin"
@@ -193,6 +194,7 @@ operand_start         [(A-Za-z0-9_\"]
 {LESS}                { return CARBON_SIMPLE_TOKEN(LESS);                }
 {LET}                 { return CARBON_SIMPLE_TOKEN(LET);                 }
 {LIBRARY}             { return CARBON_SIMPLE_TOKEN(LIBRARY);             }
+{MATCH_FIRST}         { return CARBON_SIMPLE_TOKEN(MATCH_FIRST);         }
 {MATCH}               { return CARBON_SIMPLE_TOKEN(MATCH);               }
 {MINUS}               { return CARBON_SIMPLE_TOKEN(MINUS);               }
 {MIXIN}               { return CARBON_SIMPLE_TOKEN(MIXIN);               }

+ 29 - 4
explorer/syntax/parser.ypp

@@ -112,6 +112,9 @@
 %type <Nonnull<DestructorDeclaration*>> destructor_declaration
 %type <Nonnull<MixDeclaration*>> mix_declaration
 %type <Nonnull<AliasDeclaration*>> alias_declaration
+%type <Nonnull<ImplDeclaration*>> impl_declaration
+%type <Nonnull<MatchFirstDeclaration*>> match_first_declaration
+%type <std::vector<Nonnull<ImplDeclaration*>>> match_first_declaration_list
 %type <std::vector<Nonnull<Declaration*>>> declaration_list
 %type <std::vector<Nonnull<Declaration*>>> class_body
 %type <std::vector<Nonnull<Declaration*>>> mixin_body
@@ -255,6 +258,7 @@
   LET
   LIBRARY
   MATCH
+  MATCH_FIRST
   MINUS
   MIX
   MIXIN
@@ -1214,7 +1218,15 @@ declaration:
       $$ = arena->New<ConstraintDeclaration>(arena, context.source_loc(), $2,
                                              $3, $5);
     }
-| impl_kind_intro impl_deduced_params impl_type AS type_or_where_expression LEFT_CURLY_BRACE impl_body RIGHT_CURLY_BRACE
+| impl_declaration
+    { $$ = $1; }
+| match_first_declaration
+    { $$ = $1; }
+| alias_declaration
+    { $$ = $1; }
+;
+impl_declaration:
+  impl_kind_intro 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, $3, $5, $2, $7);
@@ -1225,9 +1237,6 @@ declaration:
         YYERROR;
       }
     }
-| alias_declaration
-    { $$ = $1; }
-;
 impl_kind_intro:
   IMPL // Internal
     { $$ = Carbon::ImplKind::InternalImpl; }
@@ -1239,6 +1248,22 @@ impl_type:
     { $$ = arena->New<IdentifierExpression>(context.source_loc(), "Self"); }
 | type_expression
 ;
+match_first_declaration:
+  MATCH_FIRST LEFT_CURLY_BRACE match_first_declaration_list RIGHT_CURLY_BRACE
+    {
+      $$ = arena->New<MatchFirstDeclaration>(context.source_loc(),
+                                             std::move($3));
+    }
+;
+match_first_declaration_list:
+  // Empty
+    { $$ = {}; }
+| match_first_declaration_list impl_declaration
+    {
+      $$ = std::move($1);
+      $$.push_back($2);
+    }
+;
 destructor_declaration:
   DESTRUCTOR deduced_params block
   {

+ 36 - 0
explorer/testdata/impl/fail_ambiguous_impl_match_first.carbon

@@ -0,0 +1,36 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+class A(T:! type) {}
+interface B(T:! type) {}
+
+// This match_first doesn't affect the ambiguity between
+// these impls and the one below.
+// TODO: Once we order by type structure, the second impl
+// in this block should win.
+__match_first {
+  external impl A(i32) as B(i32) {}
+  external impl forall [T:! type] A(i32) as B(T) {}
+}
+
+external impl forall [T:! type] A(T) as B(i32) {}
+
+fn F[T:! B(i32)](x: T) {}
+fn G[T:! B(bool)](x: T) {}
+
+fn Main() -> i32 {
+  let a: A(bool) = {};
+  let b: A(i32) = {};
+  F(a);
+  G(b);
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/impl/fail_ambiguous_impl_match_first.carbon:[[@LINE+1]]: ambiguous implementations of interface B(T = i32) for class A(T = i32)
+  F(b);
+  return 0;
+}

+ 70 - 0
explorer/testdata/impl/match_first.carbon

@@ -0,0 +1,70 @@
+// 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
+//
+// AUTOUPDATE
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: XYZ: 1
+// CHECK:STDOUT: XY: 1
+// CHECK:STDOUT: XZ: 1
+// CHECK:STDOUT: YZ: 2
+// CHECK:STDOUT: JustX: 1
+// CHECK:STDOUT: JustY: 2
+// CHECK:STDOUT: JustZ: 3
+// CHECK:STDOUT: None: 4
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+interface A {
+  fn Which() -> i32;
+}
+
+interface X {}
+interface Y {}
+interface Z {}
+
+__match_first {
+  external impl forall [T:! X] T as A {
+    fn Which() -> i32 { return 1; }
+  }
+  external impl forall [T:! Y] T as A {
+    fn Which() -> i32 { return 2; }
+  }
+  external impl forall [T:! Z] T as A {
+    fn Which() -> i32 { return 3; }
+  }
+  external impl forall [T:! type] T as A {
+    fn Which() -> i32 { return 4; }
+  }
+}
+
+class XYZ {}
+class XY {}
+class XZ {}
+class YZ {}
+class JustX {}
+class JustY {}
+class JustZ {}
+class None {}
+
+impl XYZ as X & Y & Z {}
+impl XY as X & Y {}
+impl XZ as X & Z {}
+impl YZ as Y & Z {}
+impl JustX as X {}
+impl JustY as Y {}
+impl JustZ as Z {}
+
+fn Main() -> i32 {
+  Print("XYZ: {0}", XYZ.(A.Which)());
+  Print("XY: {0}", XY.(A.Which)());
+  Print("XZ: {0}", XZ.(A.Which)());
+  Print("YZ: {0}", YZ.(A.Which)());
+  Print("JustX: {0}", JustX.(A.Which)());
+  Print("JustY: {0}", JustY.(A.Which)());
+  Print("JustZ: {0}", JustZ.(A.Which)());
+  Print("None: {0}", None.(A.Which)());
+  return 0;
+}