From d46b040290711d25ac2c0422c80c881d39053ea9 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Mon, 13 Jul 2026 15:14:38 -0400 Subject: [PATCH] Connect impls to their containing `match_first` block (#7486) When we check an `impl` decl, find the containing `match_first` block, if any, and store a connection to it in the `SemIR::Impl` structure, along with the impl's position in that `match_first` block so that we can sort/prioritize the `SemIR::Impl`s later. Also, update the `is_final` flag if the `match_first` block is modified as `final`. But ensure we diagnose trying to put a `final impl`, or a redeclaration of one, in a `match_first` block. Also diagnose if an impl is attached to a `match_first` block more than once - either in two different blocks or in the same block at different positions. Putting the `match_first` connection on the `SemIR::Impl` structure means we have to import it, so implement import and add a smoke test for that, which ensures nothing explodes. Drop the `scope_stack` entry for the `match_first` block, as it was not needed. Once we started tracking the `match_first` size on the `Context` class, it became more straightforward to just store the `match_first` decl `InstId` in the same place. The `match_first` block is not supposed to act like a different scope for the purpose of redecls anyhow, so it's a bit simpler this way. --- toolchain/check/context.h | 14 + toolchain/check/handle_impl.cpp | 65 ++- toolchain/check/handle_match_first.cpp | 22 +- toolchain/check/import_ref.cpp | 36 +- toolchain/check/keyword_modifier_set.h | 5 +- toolchain/check/scope_stack.cpp | 5 - toolchain/check/scope_stack.h | 3 - .../check/testdata/match_first/basic.carbon | 437 +++++++++++++++++- .../check/testdata/match_first/import.carbon | 30 ++ toolchain/diagnostics/kind.def | 4 + toolchain/sem_ir/impl.h | 16 +- 11 files changed, 596 insertions(+), 41 deletions(-) create mode 100644 toolchain/check/testdata/match_first/import.carbon diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 14607723317b..c3881e443c9d 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -227,6 +227,17 @@ class Context { auto region_stack() -> RegionStack& { return region_stack_; } + // The `MatchFirstDecl` and state of the `match_first` block that we are + // currently checking. + struct MatchFirstContext { + SemIR::InstId decl_id; + bool is_final; + int block_size = 0; + }; + auto match_first_context() -> std::optional& { + return match_first_context_; + } + // An ongoing impl lookup, used to ensure termination. struct ImplLookupStackEntry { SemIR::ConstantId query_self_const_id; @@ -559,6 +570,9 @@ class Context { // Stack of single-entry regions being built. RegionStack region_stack_; + // The statte of the `match_first` block that we are currently checking. + std::optional match_first_context_; + // Tracks all ongoing impl lookups in order to ensure that lookup terminates // via the acyclic rule and the termination rule. llvm::SmallVector impl_lookup_stack_; diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index c7b05d54b646..9e68acc272c3 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -258,16 +258,6 @@ 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; { @@ -275,10 +265,10 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id) name, impl_decl_id, /*is_extern=*/false, SemIR::LibraryNameId::None), {.parent_scope_inst_id = parent_scope_inst_id, + .is_final = is_final, .self_id = self_type_inst_id, .constraint_id = constraint_type_inst_id, - .interface = specific_interface, - .is_final = is_final}}; + .interface = specific_interface}}; // There's a bunch of places that may represent a diagnostic that occurred // in checking the impl up to this point, which we consolidate into this // bool. Due to lack of an instruction to set to `ErrorInst`, an @@ -290,6 +280,20 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id) context.types().GetTypeIdForTypeInstId(impl.constraint_id) == SemIR::ErrorInst::TypeId; + if (is_final && context.match_first_context()) { + CARBON_DIAGNOSTIC(FinalImplInMatchFirst, Error, + "`final impl` in `match_first` block"); + CARBON_DIAGNOSTIC( + FinalImplInMatchFirstNote, Note, + "the `match_first` block can be modified as `final` instead"); + context.emitter() + .Build(node_id, FinalImplInMatchFirst) + .Note(context.match_first_context()->decl_id, + FinalImplInMatchFirstNote) + .Emit(); + impl_had_error = true; + } + CARBON_KIND_SWITCH(FindImplId(context, impl)) { case CARBON_KIND(RedeclaredImpl redeclared_impl): { // This is a redeclaration of another impl, now held in `impl_id`. @@ -299,8 +303,33 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id) // was the instruction that came last in the first declaration's eval // block. And FinishGenericRedecl allows the redecl to have fewer // instructions to support this case. - const auto& prev_impl = context.impls().Get(impl_id); + auto& prev_impl = context.impls().Get(impl_id); FinishGenericRedecl(context, prev_impl.generic_id); + + if (auto match_first = context.match_first_context()) { + if (prev_impl.match_first_id.has_value()) { + if (!impl_had_error) { + CARBON_DIAGNOSTIC( + ImplInTwoMatchFirst, Error, + "impl declared in `match_first` more than once"); + CARBON_DIAGNOSTIC(ImplInTwoMatchFirstNote, Note, + "previous declaration here"); + context.emitter() + .Build(node_id, ImplInTwoMatchFirst) + .Note(prev_impl.latest_decl_id(), ImplInTwoMatchFirstNote) + .Emit(); + } + impl_had_error = true; + } + + if (!impl_had_error) { + prev_impl.is_final = match_first->is_final; + prev_impl.match_first_id = match_first->decl_id; + prev_impl.match_first_position = match_first->block_size; + match_first->block_size += 1; + } + } + break; } case CARBON_KIND(NewImpl new_impl): { @@ -331,6 +360,16 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id) context, node_id, impl, context.generics().GetSelfSpecific(impl.generic_id)); impl.witness_block_id = context.inst_block_stack().Pop(); + + if (auto match_first = context.match_first_context()) { + // This should have been diagnosed above. + CARBON_CHECK(!impl.is_final); + + impl.is_final = match_first->is_final; + impl.match_first_id = match_first->decl_id; + impl.match_first_position = match_first->block_size; + match_first->block_size += 1; + } } FinishGenericDecl(context, node_id, impl.generic_id); diff --git a/toolchain/check/handle_match_first.cpp b/toolchain/check/handle_match_first.cpp index 3e021030c4cc..296f728ddd95 100644 --- a/toolchain/check/handle_match_first.cpp +++ b/toolchain/check/handle_match_first.cpp @@ -5,19 +5,24 @@ #include "toolchain/check/context.h" #include "toolchain/check/convert.h" #include "toolchain/check/handle.h" +#include "toolchain/check/keyword_modifier_set.h" +#include "toolchain/check/modifiers.h" #include "toolchain/diagnostics/diagnostic.h" namespace Carbon::Check { -auto HandleParseNode(Context& /*context*/, +auto HandleParseNode(Context& context, Parse::MatchFirstIntroducerId /*node_id*/) -> bool { + // Optional modifiers follow. + context.decl_introducer_state_stack().Push(); return true; } auto HandleParseNode(Context& context, Parse::MatchFirstDefinitionStartId node_id) -> bool { auto enclosing_scope_inst_id = context.scope_stack().PeekInstId(); - if (!context.insts() + if (context.match_first_context() || + !context.insts() .IsOneOf( enclosing_scope_inst_id)) { CARBON_DIAGNOSTIC( @@ -32,9 +37,12 @@ auto HandleParseNode(Context& context, return true; } + const auto& introducer = context.decl_introducer_state_stack().innermost(); + bool is_final = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Final); + auto decl_id = AddInst( context, node_id, {.enclosing_scope_inst_id = enclosing_scope_inst_id}); - context.scope_stack().PushForMatchFirstBlock(decl_id); + context.match_first_context() = {.decl_id = decl_id, .is_final = is_final}; context.node_stack().Push(node_id, decl_id); return true; } @@ -44,8 +52,14 @@ auto HandleParseNode(Context& context, Parse::MatchFirstId /*node_id*/) auto decl_id = context.node_stack().Pop(); if (decl_id != SemIR::ErrorInst::InstId) { - context.scope_stack().Pop(); + context.match_first_context() = std::nullopt; } + + auto introducer = + context.decl_introducer_state_stack().Pop(); + // Diagnose modifiers that are not allowed. + LimitModifiersOnDecl(context, introducer, KeywordModifierSet::MatchFirst); + return true; } diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index b25229e92a19..62d86b68d6b3 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -2758,13 +2758,15 @@ static auto ImportImplDecl(ImportContext& context, impl_decl.impl_id = context.local_impls().Add( {GetIncompleteLocalEntityBase(context, impl_decl_id, import_impl), {.parent_scope_inst_id = SemIR::InstId::None, + .is_final = import_impl.is_final, + .match_first_id = SemIR::InstId::None, + .match_first_position = import_impl.match_first_position, .self_id = SemIR::TypeInstId::None, .constraint_id = SemIR::TypeInstId::None, .interface = SemIR::SpecificInterface::None, .witness_id = witness_id, .scope_id = import_impl.is_complete() ? AddPlaceholderNameScope(context) - : SemIR::NameScopeId::None, - .is_final = import_impl.is_final}}); + : SemIR::NameScopeId::None}}); // Write the impl ID into the ImplDecl. auto impl_const_id = @@ -2841,6 +2843,8 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, auto self_const_id = GetLocalConstantId(resolver, import_impl.self_id); auto constraint_const_id = GetLocalConstantId(resolver, import_impl.constraint_id); + auto match_first_inst_id = + GetLocalConstantInstId(resolver, import_impl.match_first_id); auto& new_impl = resolver.local_impls().Get(impl_id); // Go directly to the simpler GetLocalConstantInstId to get an inst of the // same type locally. This does not handle symbolic values in a way that they @@ -2864,6 +2868,9 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, resolver, import_impl.constraint_id, constraint_const_id); new_impl.interface = GetLocalSpecificInterface( resolver, import_impl.interface, specific_interface_data); + // The MatchFirstDecl can't be symbolic, so we don't need to make a + // LoadedImportRef instruction for it. + new_impl.match_first_id = match_first_inst_id; if (import_impl.is_complete()) { ImportImplDefinition(resolver, import_impl, new_impl); } @@ -3900,6 +3907,22 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, .bit_width_id = bit_width_id}); } +static auto TryResolveTypedInst(ImportRefResolver& resolver, + SemIR::MatchFirstDecl inst, + SemIR::InstId import_inst_id) -> ResolveResult { + auto scope_inst_id = + GetLocalConstantInstId(resolver, inst.enclosing_scope_inst_id); + if (resolver.HasNewWork()) { + return ResolveResult::Retry(); + } + + SemIR::MatchFirstDecl decl = {.enclosing_scope_inst_id = scope_inst_id}; + auto decl_id = AddPlaceholderImportedInst(resolver, import_inst_id, decl); + ; + return ResolveResult::Done( + SetConstantValue(resolver.local_context(), decl_id, decl), decl_id); +} + static auto TryResolveTypedInst(ImportRefResolver& resolver, SemIR::Namespace inst, SemIR::InstId import_inst_id) -> ResolveResult { @@ -4451,9 +4474,6 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver, case CARBON_KIND(SemIR::GenericNamedConstraintType inst): { return TryResolveTypedInst(resolver, inst); } - case CARBON_KIND(SemIR::LookupImplWitness inst): { - return TryResolveTypedInst(resolver, inst); - } case CARBON_KIND(SemIR::ImplWitness inst): { return TryResolveTypedInst(resolver, inst); } @@ -4475,6 +4495,12 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver, case CARBON_KIND(SemIR::IntType inst): { return TryResolveTypedInst(resolver, inst); } + case CARBON_KIND(SemIR::LookupImplWitness inst): { + return TryResolveTypedInst(resolver, inst); + } + case CARBON_KIND(SemIR::MatchFirstDecl inst): { + return TryResolveTypedInst(resolver, inst, constant_inst_id); + } case CARBON_KIND(SemIR::MaybeUnformedType inst): { return TryResolveTypedInst(resolver, inst); } diff --git a/toolchain/check/keyword_modifier_set.h b/toolchain/check/keyword_modifier_set.h index e922e58f8ffd..3cc84a0b33d2 100644 --- a/toolchain/check/keyword_modifier_set.h +++ b/toolchain/check/keyword_modifier_set.h @@ -71,6 +71,7 @@ class KeywordModifierSet : public CARBON_ENUM_MASK_BASE(KeywordModifierSet) { static const KeywordModifierSet Access; static const KeywordModifierSet Class; static const KeywordModifierSet Method; + static const KeywordModifierSet MatchFirst; static const KeywordModifierSet ImplDecl; static const KeywordModifierSet Interface; static const KeywordModifierSet Evaluation; @@ -140,12 +141,14 @@ inline constexpr KeywordModifierSet KeywordModifierSet::Class(Abstract | Base); inline constexpr KeywordModifierSet KeywordModifierSet::Method(Abstract | Override | Virtual); +inline constexpr KeywordModifierSet KeywordModifierSet::MatchFirst(Final); inline constexpr KeywordModifierSet KeywordModifierSet::ImplDecl(Extend | Final); inline constexpr KeywordModifierSet KeywordModifierSet::Interface(Default | Final); inline constexpr KeywordModifierSet KeywordModifierSet::Decl(Class | Method | - Impl | Interface | + MatchFirst | Impl | + Interface | Export | Returned); inline constexpr KeywordModifierSet KeywordModifierSet::Evaluation(Eval | MustEval); diff --git a/toolchain/check/scope_stack.cpp b/toolchain/check/scope_stack.cpp index 47774568744b..f6d4399950ca 100644 --- a/toolchain/check/scope_stack.cpp +++ b/toolchain/check/scope_stack.cpp @@ -128,11 +128,6 @@ 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 b451b5a39988..5a9241d5e368 100644 --- a/toolchain/check/scope_stack.h +++ b/toolchain/check/scope_stack.h @@ -64,9 +64,6 @@ 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 index d17022a91203..24dd2ce3a2bc 100644 --- a/toolchain/check/testdata/match_first/basic.carbon +++ b/toolchain/check/testdata/match_first/basic.carbon @@ -48,13 +48,93 @@ match_first { fn F(); } -// --- todo_fail_final_inside.carbon +// --- fail_final_inside.carbon library "[[@TEST_NAME]]"; interface Z {} match_first { - // TODO: Can't write `final` inside the match first. + // CHECK:STDERR: fail_final_inside.carbon:[[@LINE+7]]:3: error: `final impl` in `match_first` block [FinalImplInMatchFirst] + // CHECK:STDERR: final impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_final_inside.carbon:[[@LINE-4]]:1: note: the `match_first` block can be modified as `final` instead [FinalImplInMatchFirstNote] + // CHECK:STDERR: match_first { + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + final impl () as Z {} +} + +// --- fail_final_redecl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +// CHECK:STDERR: fail_final_redecl.carbon:[[@LINE+4]]:1: error: impl declared but not defined [ImplMissingDefinition] +// CHECK:STDERR: final impl () as Z; +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +final impl () as Z; +match_first { + // This is not seen as a redecl of the `final impl`. It's not valid to write + // this `impl` since it overlaps the `final impl`, but we already have a + // diagnostic. + impl () as Z {} +} + +// --- fail_final_redecl_defined_once.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +final impl () as Z {} +match_first { + // This is not seen as a redecl of the `final impl`. It's not valid to write + // this `impl` since it overlaps the `final impl`, but we already have a + // diagnostic. + // CHECK:STDERR: fail_final_redecl_defined_once.carbon:[[@LINE+4]]:3: error: impl declared but not defined [ImplMissingDefinition] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; +} + +// --- fail_final_redecl_defined_twice.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +final impl () as Z {} +match_first { + // This is not seen as a redecl of the `final impl`. It's not valid to write + // this `impl` since it overlaps the `final impl`, but we already have a + // diagnostic. + // CHECK:STDERR: fail_final_redecl_defined_twice.carbon:[[@LINE+7]]:3: error: `impl` will never be used [ImplFinalOverlapsNonFinal] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: fail_final_redecl_defined_twice.carbon:[[@LINE-8]]:1: note: `final impl` declared here would always be used instead [ImplFinalOverlapsNonFinalNote] + // CHECK:STDERR: final impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z {} +} + +// --- fail_final_final_redecl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +final impl () as Z; +match_first { + // This is not seen as a redecl of the `final impl`. It's not valid to write + // this `impl` since it overlaps the `final impl`, but we already have a + // diagnostic. + // CHECK:STDERR: fail_final_final_redecl.carbon:[[@LINE+7]]:3: error: `final impl` in `match_first` block [FinalImplInMatchFirst] + // CHECK:STDERR: final impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_final_final_redecl.carbon:[[@LINE-7]]:1: note: the `match_first` block can be modified as `final` instead [FinalImplInMatchFirstNote] + // CHECK:STDERR: match_first { + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: final impl () as Z {} } @@ -66,7 +146,9 @@ interface Z {} match_first { impl () as Z; } +//@dump-sem-ir-begin impl () as Z {} +//@dump-sem-ir-end // --- definition.carbon library "[[@TEST_NAME]]"; @@ -74,7 +156,21 @@ library "[[@TEST_NAME]]"; interface Z {} match_first { + //@dump-sem-ir-begin impl () as Z {} + //@dump-sem-ir-end +} + +// --- redecl_defined.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z; +match_first { + //@dump-sem-ir-begin + impl () as Z {} + //@dump-sem-ir-end } // --- redecl.carbon @@ -84,9 +180,44 @@ interface Z {} impl () as Z; match_first { - impl () as Z {} + impl () as Z; +} +impl () as Z {} + +// --- fail_todo_redecl_after_defined.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z {} +match_first { + // TODO: We want to allow a redecl in match_first after the definition. + // CHECK:STDERR: fail_todo_redecl_after_defined.carbon:[[@LINE+7]]:3: error: redefinition of `impl () as Z` [ImplRedefinition] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_todo_redecl_after_defined.carbon:[[@LINE-6]]:1: note: previous definition was here [ImplPreviousDefinition] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; } +// --- fail_redecl_with_definition_after_defined.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z {} +match_first { + // CHECK:STDERR: fail_redecl_with_definition_after_defined.carbon:[[@LINE+7]]:3: error: redefinition of `impl () as Z` [ImplRedefinition] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_with_definition_after_defined.carbon:[[@LINE-5]]:1: note: previous definition was here [ImplPreviousDefinition] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z {} +} // --- inside_class.carbon library "[[@TEST_NAME]]"; @@ -151,14 +282,306 @@ library "[[@TEST_NAME]]"; interface Z {} match_first { - // CHECK:STDERR: fail_inside_match_first.carbon:[[@LINE+7]]:3: error: found `match_first` in invalid scope; expected namespace or class [MatchFirstInWrongScope] + // CHECK:STDERR: fail_inside_match_first.carbon:[[@LINE+5]]:3: error: found `match_first` in invalid scope; expected namespace or class [MatchFirstInWrongScope] // CHECK:STDERR: match_first { // CHECK:STDERR: ^~~~~~~~~~~~~ - // CHECK:STDERR: fail_inside_match_first.carbon:[[@LINE-4]]:1: note: in enclosing scope here [MatchFirstInWrongScopeNote] - // CHECK:STDERR: match_first { - // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_inside_match_first.carbon: note: in enclosing scope here [MatchFirstInWrongScopeNote] // CHECK:STDERR: match_first { impl () as Z {} } } + +// --- fail_redecl_in_same_block.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z; + // CHECK:STDERR: fail_redecl_in_same_block.carbon:[[@LINE+7]]:3: error: impl declared in `match_first` more than once [ImplInTwoMatchFirst] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_in_same_block.carbon:[[@LINE-4]]:3: note: previous declaration here [ImplInTwoMatchFirstNote] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; +} +impl () as Z {} + +// --- fail_defined_in_same_block.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z; + // CHECK:STDERR: fail_defined_in_same_block.carbon:[[@LINE+7]]:3: error: impl declared in `match_first` more than once [ImplInTwoMatchFirst] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: fail_defined_in_same_block.carbon:[[@LINE-4]]:3: note: previous declaration here [ImplInTwoMatchFirstNote] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z {} +} + +// --- fail_redecl_after_defined_in_same_block.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z {} + // CHECK:STDERR: fail_redecl_after_defined_in_same_block.carbon:[[@LINE+7]]:3: error: redefinition of `impl () as Z` [ImplRedefinition] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_after_defined_in_same_block.carbon:[[@LINE-4]]:3: note: previous definition was here [ImplPreviousDefinition] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; +} + +// --- fail_redecl_in_different_block.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z; +} +match_first { + // CHECK:STDERR: fail_redecl_in_different_block.carbon:[[@LINE+7]]:3: error: impl declared in `match_first` more than once [ImplInTwoMatchFirst] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_in_different_block.carbon:[[@LINE-6]]:3: note: previous declaration here [ImplInTwoMatchFirstNote] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; +} +impl () as Z {} + +// --- fail_redecl_defined_in_different_block_firset.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z {} +} +match_first { + // CHECK:STDERR: fail_redecl_defined_in_different_block_firset.carbon:[[@LINE+7]]:3: error: redefinition of `impl () as Z` [ImplRedefinition] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_defined_in_different_block_firset.carbon:[[@LINE-6]]:3: note: previous definition was here [ImplPreviousDefinition] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z; +} + +// --- fail_redecl_defined_in_different_block_second.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +match_first { + impl () as Z; +} +match_first { + // CHECK:STDERR: fail_redecl_defined_in_different_block_second.carbon:[[@LINE+7]]:3: error: impl declared in `match_first` more than once [ImplInTwoMatchFirst] + // CHECK:STDERR: impl () as Z {} + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: fail_redecl_defined_in_different_block_second.carbon:[[@LINE-6]]:3: note: previous declaration here [ImplInTwoMatchFirstNote] + // CHECK:STDERR: impl () as Z; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + impl () as Z {} +} + +// --- final_block_redecl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z; +final match_first { + impl () as Z; +} +//@dump-sem-ir-begin +impl () as Z {} +//@dump-sem-ir-end + +// --- final_block_decl.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +final match_first { + impl () as Z; +} +//@dump-sem-ir-begin +impl () as Z {} +//@dump-sem-ir-end + +// --- final_block_defined.carbon +library "[[@TEST_NAME]]"; + +interface Z {} + +impl () as Z; +final match_first { + //@dump-sem-ir-begin + impl () as Z {} + //@dump-sem-ir-end +} + +// CHECK:STDOUT: --- decl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc9_7.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc9_7.2: type = converted %.loc9_7.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref.loc9: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @empty_tuple.type.as.Z.impl: %.loc6_9.2 as %Z.ref.loc6 { +// CHECK:STDOUT: +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref.loc6 +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- definition.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: %Z.impl_witness: = impl_witness @empty_tuple.type.as.Z.impl.%Z.impl_witness_table [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc7_9.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc7_9.2: type = converted %.loc7_9.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @empty_tuple.type.as.Z.impl: %.loc7_9.2 as %Z.ref { +// CHECK:STDOUT: %Z.impl_witness_table = impl_witness_table (), @empty_tuple.type.as.Z.impl [concrete] +// CHECK:STDOUT: %Z.impl_witness: = impl_witness %Z.impl_witness_table [concrete = constants.%Z.impl_witness] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- redecl_defined.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc8_9.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc8_9.2: type = converted %.loc8_9.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref.loc8: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @empty_tuple.type.as.Z.impl: %.loc5_7.2 as %Z.ref.loc5 { +// CHECK:STDOUT: +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref.loc5 +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- final_block_redecl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc10_7.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc10_7.2: type = converted %.loc10_7.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref.loc10: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: final impl @empty_tuple.type.as.Z.impl: %.loc5_7.2 as %Z.ref.loc5 { +// CHECK:STDOUT: +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref.loc5 +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- final_block_decl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc9_7.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc9_7.2: type = converted %.loc9_7.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref.loc9: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: final impl @empty_tuple.type.as.Z.impl: %.loc6_9.2 as %Z.ref.loc6 { +// CHECK:STDOUT: +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref.loc6 +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- final_block_defined.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Z.type: type = facet_type <@Z> [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: impl_decl @empty_tuple.type.as.Z.impl [concrete] {} { +// CHECK:STDOUT: %.loc8_9.1: %empty_tuple.type = tuple_literal () [concrete = constants.%empty_tuple] +// CHECK:STDOUT: %.loc8_9.2: type = converted %.loc8_9.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type] +// CHECK:STDOUT: %Z.ref.loc8: type = name_ref Z, file.%Z.decl [concrete = constants.%Z.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: final impl @empty_tuple.type.as.Z.impl: %.loc5_7.2 as %Z.ref.loc5 { +// CHECK:STDOUT: +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: extend %Z.ref.loc5 +// CHECK:STDOUT: witness = %Z.impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/match_first/import.carbon b/toolchain/check/testdata/match_first/import.carbon new file mode 100644 index 000000000000..c8e46e2ebb13 --- /dev/null +++ b/toolchain/check/testdata/match_first/import.carbon @@ -0,0 +1,30 @@ +// 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/import.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/match_first/import.carbon + +// --- for_import.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } + +final match_first { + impl () as Z where .Z1 = () {} +} + +// --- import.carbon +library "[[@TEST_NAME]]"; + +import library "for_import"; + +fn F() { + let _: ().(Z.Z1) = (); +} diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index e4c5519ea97c..7c2e68569b13 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -369,6 +369,8 @@ CARBON_DIAGNOSTIC_KIND(ImplAsOutsideClass) CARBON_DIAGNOSTIC_KIND(ImplAssociatedConstantNeedsValue) CARBON_DIAGNOSTIC_KIND(ImplDeclInInvalidScope) CARBON_DIAGNOSTIC_KIND(ImplFunctionWithNonFunction) +CARBON_DIAGNOSTIC_KIND(ImplInTwoMatchFirst) +CARBON_DIAGNOSTIC_KIND(ImplInTwoMatchFirstNote) CARBON_DIAGNOSTIC_KIND(ImplIsOrphan) CARBON_DIAGNOSTIC_KIND(ImplMissingDefinition) CARBON_DIAGNOSTIC_KIND(ImplMissingFunction) @@ -384,6 +386,8 @@ CARBON_DIAGNOSTIC_KIND(ImplFinalOverlapsNonFinal) CARBON_DIAGNOSTIC_KIND(ImplFinalOverlapsNonFinalNote) CARBON_DIAGNOSTIC_KIND(ImplNonFinalSameTypeStructure) CARBON_DIAGNOSTIC_KIND(ImplNonFinalSameTypeStructureNote) +CARBON_DIAGNOSTIC_KIND(FinalImplInMatchFirst) +CARBON_DIAGNOSTIC_KIND(FinalImplInMatchFirstNote) CARBON_DIAGNOSTIC_KIND(FinalImplInvalidFile) CARBON_DIAGNOSTIC_KIND(FinalImplOverlapsSameFile) CARBON_DIAGNOSTIC_KIND(FinalImplOverlapsSameFileNote) diff --git a/toolchain/sem_ir/impl.h b/toolchain/sem_ir/impl.h index 18f7ea12b1cc..e3513323ba9d 100644 --- a/toolchain/sem_ir/impl.h +++ b/toolchain/sem_ir/impl.h @@ -22,6 +22,19 @@ struct ImplFields { // Note that this is None for an imported impl. InstId parent_scope_inst_id; + // The following members are set at the start of the impl declaration, and may + // be modified at the start of redeclarations. + + // Whether the impl declaration is marked `final`. + bool is_final; + + // The `MatchFirstDecl` of the `match_first` block that the impl is associated + // with. + InstId match_first_id = SemIR::InstId::None; + + // The position of the impl in its associated `match_first` block. + int match_first_position = 0; + // The following members always have values and do not change. // The type for which the impl is implementing a constraint. @@ -50,9 +63,6 @@ struct ImplFields { // TODO: Handle control flow in the impl body, such as if-expressions. InstBlockId body_block_id = InstBlockId::None; - // Whether the impl declaration is marked `final`. - bool is_final; - // The following members are set at the `}` of the impl definition. bool defined = false; };