diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 2a88e301441f..c7b05d54b646 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -258,6 +258,16 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id) // The impl decl has a scope stack entry for the DeclNameStack, so we look at // the parent scope of that. auto parent_scope_inst_id = context.scope_stack().PeekParentInstId(); + while (parent_scope_inst_id.has_value()) { + if (auto match_first = context.insts().TryGetAs( + parent_scope_inst_id)) { + parent_scope_inst_id = match_first->enclosing_scope_inst_id; + // TODO: Save the match first as an identifier on the Impl for choosing a + // best candidate later. + } else { + break; + } + } auto impl_id = SemIR::ImplId::None; { diff --git a/toolchain/check/handle_match_first.cpp b/toolchain/check/handle_match_first.cpp index 9c4d10b5a691..e6a185e10488 100644 --- a/toolchain/check/handle_match_first.cpp +++ b/toolchain/check/handle_match_first.cpp @@ -8,18 +8,24 @@ namespace Carbon::Check { -auto HandleParseNode(Context& context, Parse::MatchFirstIntroducerId node_id) - -> bool { - return context.TODO(node_id, "HandleMatchFirstIntroducer"); +auto HandleParseNode(Context& /*context*/, + Parse::MatchFirstIntroducerId /*node_id*/) -> bool { + return true; } auto HandleParseNode(Context& context, Parse::MatchFirstDefinitionStartId node_id) -> bool { - return context.TODO(node_id, "HandleMatchFirstDefinitionStart"); + auto enclosing_scope_inst_id = context.scope_stack().PeekInstId(); + auto decl_id = AddInst( + context, node_id, {.enclosing_scope_inst_id = enclosing_scope_inst_id}); + context.scope_stack().PushForMatchFirstBlock(decl_id); + return true; } -auto HandleParseNode(Context& context, Parse::MatchFirstId node_id) -> bool { - return context.TODO(node_id, "HandleMatchFirst"); +auto HandleParseNode(Context& context, Parse::MatchFirstId /*node_id*/) + -> bool { + context.scope_stack().Pop(); + return true; } } // namespace Carbon::Check diff --git a/toolchain/check/scope_stack.cpp b/toolchain/check/scope_stack.cpp index f6d4399950ca..47774568744b 100644 --- a/toolchain/check/scope_stack.cpp +++ b/toolchain/check/scope_stack.cpp @@ -128,6 +128,11 @@ auto ScopeStack::PushForFunctionBody(SemIR::InstId scope_inst_id) -> void { destroy_id_stack_.PushArray(); } +auto ScopeStack::PushForMatchFirstBlock(SemIR::InstId scope_inst_id) -> void { + Push(scope_inst_id, SemIR::NameScopeId::None, SemIR::SpecificId::None, + /*lexical_lookup_has_load_error=*/false); +} + auto ScopeStack::Pop(bool check_unused) -> void { auto scope = scope_stack_.pop_back_val(); diff --git a/toolchain/check/scope_stack.h b/toolchain/check/scope_stack.h index 5a9241d5e368..b451b5a39988 100644 --- a/toolchain/check/scope_stack.h +++ b/toolchain/check/scope_stack.h @@ -64,6 +64,9 @@ class ScopeStack { // Pushes a function scope. auto PushForFunctionBody(SemIR::InstId scope_inst_id) -> void; + // Pushes a match_first block scope. + auto PushForMatchFirstBlock(SemIR::InstId scope_inst_id) -> void; + // Pops the top scope from scope_stack_. Removes names from lexical_lookup_. // If `check_unused` is set, checks and emits diagnostics for unused names. auto Pop(bool check_unused = false) -> void; diff --git a/toolchain/check/testdata/match_first/basic.carbon b/toolchain/check/testdata/match_first/basic.carbon new file mode 100644 index 000000000000..e1928b438297 --- /dev/null +++ b/toolchain/check/testdata/match_first/basic.carbon @@ -0,0 +1,114 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/none.carbon +// ^ARGS: -v compile %s +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/match_first/basic.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/match_first/basic.carbon + +// --- empty.carbon +library "[[@TEST_NAME]]"; + +match_first {} + +// --- todo_fail_class_inside.carbon +library "[[@TEST_NAME]]"; + +match_first { + // TODO: Only impls are allowed inside match_first. + class C; +} + +// --- todo_fail_interface_inside.carbon +library "[[@TEST_NAME]]"; + +match_first { + // TODO: Only impls are allowed inside match_first. + interface Z; +} + +// --- todo_fail_constraint_inside.carbon +library "[[@TEST_NAME]]"; + +match_first { + // TODO: Only impls are allowed inside match_first. + constraint N; +} + +// --- todo_fail_function_inside.carbon +library "[[@TEST_NAME]]"; + +match_first { + // TODO: Only impls are allowed inside match_first. + fn F(); +} + +// --- todo_fail_final_inside.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + // TODO: Can't write `final` inside the match first. + final impl () as Z {} +} + +// --- decl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z; +} + +impl () as Z {} + +// --- definition.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z {} +} + +// --- redecl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z; + +match_first { + impl () as Z {} +} + +// --- inside_class.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +class C { + match_first { + impl Self as Z {} + } +} + +// --- todo_fail_inside_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + // TODO: Can't write match_first in another match_first, that puts the same + // impl in two match_first blocks. + match_first { + impl () as Z {} + } +} diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 6a473bfb8d6a..4555d07a8908 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -103,6 +103,7 @@ CARBON_SEM_IR_INST_KIND(InterfaceDecl) CARBON_SEM_IR_INST_KIND(InterfaceWithSelfDecl) CARBON_SEM_IR_INST_KIND(LookupImplWitness) CARBON_SEM_IR_INST_KIND(MarkInPlaceInit) +CARBON_SEM_IR_INST_KIND(MatchFirstDecl) CARBON_SEM_IR_INST_KIND(MaybeUnformedType) CARBON_SEM_IR_INST_KIND(NameBindingDecl) CARBON_SEM_IR_INST_KIND(NamedConstraintDecl) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index a17704462229..bef4f56bba2b 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -942,6 +942,16 @@ struct GenericNamedConstraintType { SpecificId enclosing_specific_id; }; +// A `match_first` declaration. +struct MatchFirstDecl { + static constexpr auto Kind = + InstKind::MatchFirstDecl.Define( + {.ir_name = "match_first", + .constant_kind = InstConstantKind::AlwaysUnique, + .is_lowered = false}); + SemIR::InstId enclosing_scope_inst_id; +}; + // An `impl` declaration. struct ImplDecl { static constexpr auto Kind = InstKind::ImplDecl.Define(