Basic checking for match_first blocks (#7481)

Push a scope_stack entry for match_first blocks, and handle impls being
inside those scope entries. An impl should not use the `match_first`
block as its "enclosing scope" for the purpose of deciding if the impl
is a redecl of another impl. We should look through it to the class or
namespace the impl (and match_first) are located inside.

We don't yet store the relationship between the impl and its match_first
block, nor then can we use it in impl lookup for prioritization.
This commit is contained in:
Dana Jansens
2026-07-11 14:01:38 +00:00
committed by GitHub
parent 8be274cf60
commit a46f4863db
7 changed files with 155 additions and 6 deletions
+10
View File
@@ -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<SemIR::MatchFirstDecl>(
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;
{
+12 -6
View File
@@ -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<SemIR::MatchFirstDecl>(
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
+5
View File
@@ -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();
+3
View File
@@ -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;
+114
View File
@@ -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 {}
}
}
+1
View File
@@ -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)
+10
View File
@@ -942,6 +942,16 @@ struct GenericNamedConstraintType {
SpecificId enclosing_specific_id;
};
// A `match_first` declaration.
struct MatchFirstDecl {
static constexpr auto Kind =
InstKind::MatchFirstDecl.Define<Parse::MatchFirstDefinitionStartId>(
{.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<Parse::AnyImplDeclId>(