diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 37a25ad5487d..1d761798e384 100644 --- a/common/fuzzing/carbon.proto +++ b/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; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index f9c1eb5260f0..0ab4834d4da5 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/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 "; diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index f034789b7872..d1789aa097bd 100644 --- a/explorer/ast/ast_rtti.txt +++ b/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; diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index f9a9eb1a08ca..85d0c0d3d75c 100644 --- a/explorer/ast/declaration.cpp +++ b/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(*this); + PrintID(out); + out << " {\n"; + for (Nonnull m : match_first_decl.impls()) { + out << *m; + } + out << "}\n"; + break; + } case DeclarationKind::FunctionDeclaration: cast(*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(*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(); diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index a4347015a1fa..164313e9504f 100644 --- a/explorer/ast/declaration.h +++ b/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 { return self_decl_; } auto self() -> Nonnull { return self_decl_; } + // Set the enclosing match_first declaration. Should only be called once, + // during type-checking. + void set_match_first(Nonnull match_first) { + match_first_ = match_first; + } + // Get the enclosing match_first declaration, if any exists. + auto match_first() const + -> std::optional> { + return match_first_; + } + private: ImplKind kind_; Nonnull impl_type_; @@ -749,6 +761,27 @@ class ImplDeclaration : public Declaration { std::vector> deduced_parameters_; std::vector> members_; std::vector> impl_bindings_; + std::optional> match_first_; +}; + +class MatchFirstDeclaration : public Declaration { + public: + MatchFirstDeclaration(SourceLocation source_loc, + std::vector> 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> { + return impls_; + } + auto impls() -> llvm::ArrayRef> { return impls_; } + + private: + std::vector> impls_; }; class AliasDeclaration : public Declaration { diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 3773d4737dbc..17af2bcb5430 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/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(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()."; } diff --git a/explorer/interpreter/impl_scope.cpp b/explorer/interpreter/impl_scope.cpp index ed0cccbf222b..cf9e9dd78336 100644 --- a/explorer/interpreter/impl_scope.cpp +++ b/explorer/interpreter/impl_scope.cpp @@ -215,6 +215,7 @@ static auto CombineResults(Nonnull 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(*a); @@ -225,13 +226,32 @@ static auto CombineResults(Nonnull 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; } diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index def117cda62a..2db0232c3b87 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -2061,6 +2061,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr { 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. diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index 4ea98df091ba..fcb72a4d3942 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/explorer/interpreter/resolve_control_flow.cpp @@ -178,6 +178,13 @@ auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { } break; } + case DeclarationKind::MatchFirstDeclaration: { + auto& match_first_decl = cast(*declaration); + for (Nonnull impl : match_first_decl.impls()) { + CARBON_RETURN_IF_ERROR(ResolveControlFlow(impl)); + } + break; + } case DeclarationKind::ChoiceDeclaration: case DeclarationKind::VariableDeclaration: case DeclarationKind::InterfaceExtendsDeclaration: diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index b1a225495100..6c5fed44fe85 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/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(declaration).impls()) { + CARBON_RETURN_IF_ERROR(ResolveNames(*impl, enclosing_scope, bodies)); + } + break; + } case DeclarationKind::DestructorDeclaration: case DeclarationKind::FunctionDeclaration: { auto& function = cast(declaration); diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 3e373985279b..2ad8d1af3928 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -306,6 +306,7 @@ static auto ResolveUnformed(Nonnull declaration) case DeclarationKind::InterfaceDeclaration: case DeclarationKind::ConstraintDeclaration: case DeclarationKind::ImplDeclaration: + case DeclarationKind::MatchFirstDeclaration: case DeclarationKind::ChoiceDeclaration: case DeclarationKind::VariableDeclaration: case DeclarationKind::InterfaceExtendsDeclaration: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 32a781f3fd3e..d8ab555ad152 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -5458,6 +5458,14 @@ auto TypeChecker::TypeCheckDeclaration( TypeCheckImplDeclaration(&cast(*d), impl_scope)); break; } + case DeclarationKind::MatchFirstDeclaration: { + auto* match_first = cast(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 d, CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(&impl_decl, scope_info)); break; } + case DeclarationKind::MatchFirstDeclaration: { + for (auto* impl : cast(d)->impls()) { + CARBON_RETURN_IF_ERROR(DeclareImplDeclaration(impl, scope_info)); + } + break; + } case DeclarationKind::FunctionDeclaration: { auto& func_def = cast(*d); CARBON_RETURN_IF_ERROR(DeclareCallableDeclaration(&func_def, scope_info)); diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 96dd7c08d2e7..06c428479080 100644 --- a/explorer/syntax/lexer.lpp +++ b/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); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index d8adf0611e4d..14252ea3ed08 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -112,6 +112,9 @@ %type > destructor_declaration %type > mix_declaration %type > alias_declaration +%type > impl_declaration +%type > match_first_declaration +%type >> match_first_declaration_list %type >> declaration_list %type >> class_body %type >> mixin_body @@ -255,6 +258,7 @@ LET LIBRARY MATCH + MATCH_FIRST MINUS MIX MIXIN @@ -1214,7 +1218,15 @@ declaration: $$ = arena->New(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 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(context.source_loc(), "Self"); } | type_expression ; +match_first_declaration: + MATCH_FIRST LEFT_CURLY_BRACE match_first_declaration_list RIGHT_CURLY_BRACE + { + $$ = arena->New(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 { diff --git a/explorer/testdata/impl/fail_ambiguous_impl_match_first.carbon b/explorer/testdata/impl/fail_ambiguous_impl_match_first.carbon new file mode 100644 index 000000000000..b7cfbd4bb131 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/impl/match_first.carbon b/explorer/testdata/impl/match_first.carbon new file mode 100644 index 000000000000..0d8bfd2fb713 --- /dev/null +++ b/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; +}