From a584ee120ed7f91da893cce7cd3cb08b1f1ff4f8 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Mon, 17 Mar 2025 15:29:56 -0700 Subject: [PATCH] Add support for `_` binding patterns (#5097) Co-authored-by: Jon Ross-Perkins --- toolchain/check/handle_binding_pattern.cpp | 84 +++-- toolchain/check/pattern_match.cpp | 26 +- .../patterns/no_prelude/underscore.carbon | 288 ++++++++++++++++++ toolchain/parse/handle_binding_pattern.cpp | 3 + toolchain/parse/node_ids.h | 3 + toolchain/parse/node_kind.def | 2 + toolchain/parse/state.def | 4 + toolchain/parse/testdata/let/let.carbon | 18 +- toolchain/parse/typed_nodes.h | 15 +- toolchain/sem_ir/formatter.cpp | 4 + toolchain/sem_ir/ids.h | 2 + toolchain/sem_ir/inst_namer.cpp | 7 +- toolchain/sem_ir/name.cpp | 2 + toolchain/sem_ir/pattern.cpp | 6 +- toolchain/sem_ir/typed_insts.h | 4 + 15 files changed, 423 insertions(+), 45 deletions(-) create mode 100644 toolchain/check/testdata/patterns/no_prelude/underscore.carbon diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index d0e6b64e94d7..41891b8630d6 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -19,6 +19,13 @@ namespace Carbon::Check { +auto HandleParseNode(Context& context, Parse::UnderscoreNameId node_id) + -> bool { + context.node_stack().Push(node_id, SemIR::NameId::Underscore); + return true; +} + +// TODO: make this function shorter by factoring pieces out. static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, Parse::NodeKind node_kind) -> bool { // TODO: split this into smaller, more focused functions. @@ -26,8 +33,6 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, auto [cast_type_inst_id, cast_type_id] = ExprAsType(context, type_node, parsed_type_id); - // TODO: Handle `_` bindings. - SemIR::ExprRegionId type_expr_region_id = EndSubpatternAsExpr(context, cast_type_inst_id); @@ -39,54 +44,63 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, // A non-generic template binding is diagnosed by the parser. is_template &= is_generic; - // Every other kind of pattern binding has a name. auto [name_node, name_id] = context.node_stack().PopNameWithNodeId(); const DeclIntroducerState& introducer = context.decl_introducer_state_stack().innermost(); auto make_binding_pattern = [&]() -> SemIR::InstId { + // bind_id and entity_name_id are not populated if name_id is Underscore. auto bind_id = SemIR::InstId::None; - auto binding_pattern_id = SemIR::InstId::None; // TODO: Eventually the name will need to support associations with other // scopes, but right now we don't support qualified names here. - auto entity_name_id = context.entity_names().AddSymbolicBindingName( - name_id, context.scope_stack().PeekNameScopeId(), - is_generic ? context.scope_stack().AddCompileTimeBinding() - : SemIR::CompileTimeBindIndex::None, - is_template); - if (is_generic) { - bind_id = AddInstInNoBlock( - context, - SemIR::LocIdAndInst(name_node, SemIR::BindSymbolicName{ + auto entity_name_id = SemIR::EntityNameId::None; + if (name_id != SemIR::NameId::Underscore) { + entity_name_id = context.entity_names().AddSymbolicBindingName( + name_id, context.scope_stack().PeekNameScopeId(), + is_generic ? context.scope_stack().AddCompileTimeBinding() + : SemIR::CompileTimeBindIndex::None, + is_template); + if (is_generic) { + bind_id = AddInstInNoBlock( + context, SemIR::LocIdAndInst(name_node, + SemIR::BindSymbolicName{ .type_id = cast_type_id, .entity_name_id = entity_name_id, .value_id = SemIR::InstId::None})); + } else { + bind_id = AddInstInNoBlock( + context, + SemIR::LocIdAndInst( + name_node, SemIR::BindName{.type_id = cast_type_id, + .entity_name_id = entity_name_id, + .value_id = SemIR::InstId::None})); + } + } + + auto binding_pattern_id = SemIR::InstId::None; + if (is_generic) { binding_pattern_id = AddPatternInst( context, name_node, {.type_id = cast_type_id, .entity_name_id = entity_name_id}); } else { - bind_id = AddInstInNoBlock( - context, - SemIR::LocIdAndInst( - name_node, SemIR::BindName{.type_id = cast_type_id, - .entity_name_id = entity_name_id, - .value_id = SemIR::InstId::None})); binding_pattern_id = AddPatternInst( context, name_node, {.type_id = cast_type_id, .entity_name_id = entity_name_id}); } - // Add name to lookup immediately, so it can be used in the rest of the - // enclosing pattern. - if (is_generic) { - context.scope_stack().PushCompileTimeBinding(bind_id); + if (name_id != SemIR::NameId::Underscore) { + // Add name to lookup immediately, so it can be used in the rest of the + // enclosing pattern. + if (is_generic) { + context.scope_stack().PushCompileTimeBinding(bind_id); + } + auto name_context = + context.decl_name_stack().MakeUnqualifiedName(name_node, name_id); + context.decl_name_stack().AddNameOrDiagnose( + name_context, bind_id, introducer.modifier_set.GetAccessKind()); + context.full_pattern_stack().AddBindName(name_id); } - auto name_context = - context.decl_name_stack().MakeUnqualifiedName(name_node, name_id); - context.decl_name_stack().AddNameOrDiagnose( - name_context, bind_id, introducer.modifier_set.GetAccessKind()); - context.full_pattern_stack().AddBindName(name_id); bool inserted = context.bind_name_map() .Insert(binding_pattern_id, @@ -112,6 +126,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, context.scope_stack().GetCurrentScopeAs(); parent_class_decl.has_value() && !is_generic && node_kind == Parse::NodeKind::VarBindingPattern) { + if (name_id == SemIR::NameId::Underscore) { + // The action item here may be to document this as not allowed, and + // add a proper diagnostic. + context.TODO(node_id, "_ used as field name"); + } cast_type_id = AsConcreteType( context, cast_type_id, type_node, [&] { @@ -151,6 +170,11 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, if (auto parent_interface_decl = context.scope_stack().GetCurrentScopeAs(); parent_interface_decl.has_value() && is_generic) { + if (name_id == SemIR::NameId::Underscore) { + // The action item here may be to document this as not allowed, and + // add a proper diagnostic. + context.TODO(node_id, "_ used as associated constant name"); + } cast_type_id = AsCompleteType(context, cast_type_id, type_node, [&] { CARBON_DIAGNOSTIC(IncompleteTypeInAssociatedConstantDecl, Error, "associated constant has incomplete type {0}", @@ -242,7 +266,9 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, } auto result_inst_id = SemIR::InstId::None; if (had_error) { - AddNameToLookup(context, name_id, SemIR::ErrorInst::SingletonInstId); + if (name_id != SemIR::NameId::Underscore) { + AddNameToLookup(context, name_id, SemIR::ErrorInst::SingletonInstId); + } // Replace the parameter with `ErrorInst` so that we don't try // constructing a generic based on it. result_inst_id = SemIR::ErrorInst::SingletonInstId; diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 70c88f7ffc80..c8af1f6de0cf 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -203,20 +203,30 @@ auto MatchContext::DoEmitPatternMatch(Context& context, std::exchange(context.bind_name_map().Lookup(entry.pattern_id).value(), {.bind_name_id = SemIR::InstId::None, .type_expr_region_id = SemIR::ExprRegionId::None}); + // bind_name_id doesn't have a value in the case of an unused binding pattern, + // but type_expr_region_id should always be populated. + CARBON_CHECK(type_expr_region_id.has_value()); InsertHere(context, type_expr_region_id); auto value_id = SemIR::InstId::None; if (kind_ == MatchKind::Local) { - value_id = ConvertToValueOrRefOfType( - context, context.insts().GetLocId(entry.scrutinee_id), - entry.scrutinee_id, binding_pattern.type_id); + value_id = + Convert(context, context.insts().GetLocId(entry.scrutinee_id), + entry.scrutinee_id, + {.kind = bind_name_id.has_value() ? ConversionTarget::ValueOrRef + : ConversionTarget::Discarded, + .type_id = binding_pattern.type_id}); } else { + // In a function call, conversion is handled while matching the enclosing + // `*ParamPattern`. value_id = entry.scrutinee_id; } - auto bind_name = context.insts().GetAs(bind_name_id); - CARBON_CHECK(!bind_name.value_id.has_value()); - bind_name.value_id = value_id; - ReplaceInstBeforeConstantUse(context, bind_name_id, bind_name); - context.inst_block_stack().AddInstId(bind_name_id); + if (bind_name_id.has_value()) { + auto bind_name = context.insts().GetAs(bind_name_id); + CARBON_CHECK(!bind_name.value_id.has_value()); + bind_name.value_id = value_id; + ReplaceInstBeforeConstantUse(context, bind_name_id, bind_name); + context.inst_block_stack().AddInstId(bind_name_id); + } } auto MatchContext::DoEmitPatternMatch(Context& context, diff --git a/toolchain/check/testdata/patterns/no_prelude/underscore.carbon b/toolchain/check/testdata/patterns/no_prelude/underscore.carbon new file mode 100644 index 000000000000..1fc438162f92 --- /dev/null +++ b/toolchain/check/testdata/patterns/no_prelude/underscore.carbon @@ -0,0 +1,288 @@ +// 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 +// +// This is mostly checking against crashes for compile time bindings in +// difficult contexts. +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/patterns/no_prelude/underscore.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/patterns/no_prelude/underscore.carbon + +// --- basic.carbon + +library "[[@TEST_NAME]]"; + +let _: {} = {}; +var _: {} = {}; + +fn F() { + let _: {} = {}; + var _: {} = {}; +} + +// --- function.carbon + +library "[[@TEST_NAME]]"; + +fn F(_: {}); + +fn G(_: {}) {} + +fn H() { + F({}); +} + +// --- fail_class.carbon + +library "[[@TEST_NAME]]"; + +class C { + // CHECK:STDERR: fail_class.carbon:[[@LINE+4]]:7: error: semantics TODO: `_ used as field name` [SemanticsTodo] + // CHECK:STDERR: var _: (); + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: + var _: (); +} + +// --- fail_interface.carbon + +library "[[@TEST_NAME]]"; + +interface I { + // CHECK:STDERR: fail_interface.carbon:[[@LINE+4]]:7: error: semantics TODO: `_ used as associated constant name` [SemanticsTodo] + // CHECK:STDERR: let _:! {}; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + let _:! {}; +} + +// --- fail_use.carbon + +fn F() -> {} { + let _: {} = {}; + // CHECK:STDERR: fail_use.carbon:[[@LINE+12]]:10: error: expected expression [ExpectedExpr] + // CHECK:STDERR: return _; + // CHECK:STDERR: ^ + // CHECK:STDERR: + // CHECK:STDERR: fail_use.carbon:[[@LINE+8]]:10: error: `return` statements must end with a `;` [ExpectedStatementSemi] + // CHECK:STDERR: return _; + // CHECK:STDERR: ^ + // CHECK:STDERR: + // CHECK:STDERR: fail_use.carbon:[[@LINE+4]]:10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] + // CHECK:STDERR: return _; + // CHECK:STDERR: ^ + // CHECK:STDERR: + return _; +} + +// CHECK:STDOUT: --- basic.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %_.patt.loc4: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc4_9.1: type = splice_block %.loc4_9.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc4_9.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc4_9.3: type = converted %.loc4_9.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc4_14: %empty_struct_type = converted @__global_init.%.loc4, %empty_struct [concrete = constants.%empty_struct] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %_.patt.loc5: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: %.loc5_1: %empty_struct_type = var_pattern %_.patt.loc5 +// CHECK:STDOUT: } +// CHECK:STDOUT: %_.var: ref %empty_struct_type = var _ +// CHECK:STDOUT: %.loc5_9.1: type = splice_block %.loc5_9.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc5_9.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc5_9.3: type = converted %.loc5_9.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %_.patt.loc8: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc8_16.1: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc8_11.1: type = splice_block %.loc8_11.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc8_11.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc8_11.3: type = converted %.loc8_11.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc8_16.2: %empty_struct_type = converted %.loc8_16.1, %empty_struct [concrete = constants.%empty_struct] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %_.patt.loc9: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: %.loc9_3.1: %empty_struct_type = var_pattern %_.patt.loc9 +// CHECK:STDOUT: } +// CHECK:STDOUT: %_.var: ref %empty_struct_type = var _ +// CHECK:STDOUT: %.loc9_16.1: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc9_16.2: init %empty_struct_type = struct_init () to %_.var [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc9_3.2: init %empty_struct_type = converted %.loc9_16.1, %.loc9_16.2 [concrete = constants.%empty_struct] +// CHECK:STDOUT: assign %_.var, %.loc9_3.2 +// CHECK:STDOUT: %.loc9_11.1: type = splice_block %.loc9_11.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc9_11.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc9_11.3: type = converted %.loc9_11.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc4: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc5_14.1: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc5_14.2: init %empty_struct_type = struct_init () to file.%_.var [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc5_1: init %empty_struct_type = converted %.loc5_14.1, %.loc5_14.2 [concrete = constants.%empty_struct] +// CHECK:STDOUT: assign file.%_.var, %.loc5_1 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- function.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %H.type: type = fn_type @H [concrete] +// CHECK:STDOUT: %H: %H.type = struct_value () [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: .H = %H.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %_.patt: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: %_.param_patt: %empty_struct_type = value_param_pattern %_.patt, call_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %_.param: %empty_struct_type = value_param call_param0 +// CHECK:STDOUT: %.loc4_10.1: type = splice_block %.loc4_10.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc4_10.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc4_10.3: type = converted %.loc4_10.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] { +// CHECK:STDOUT: %_.patt: %empty_struct_type = binding_pattern _ +// CHECK:STDOUT: %_.param_patt: %empty_struct_type = value_param_pattern %_.patt, call_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %_.param: %empty_struct_type = value_param call_param0 +// CHECK:STDOUT: %.loc6_10.1: type = splice_block %.loc6_10.3 [concrete = constants.%empty_struct_type] { +// CHECK:STDOUT: %.loc6_10.2: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc6_10.3: type = converted %.loc6_10.2, constants.%empty_struct_type [concrete = constants.%empty_struct_type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: %H.decl: %H.type = fn_decl @H [concrete = constants.%H] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%_.param_patt: %empty_struct_type); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%_.param_patt: %empty_struct_type) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %.loc9_6.1: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc9_6.2: %empty_struct_type = converted %.loc9_6.1, %empty_struct [concrete = constants.%empty_struct] +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc9_6.2) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_class.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %C.elem: type = unbound_element_type %C, %empty_tuple.type [concrete] +// CHECK:STDOUT: %struct_type._: type = struct_type {._: %empty_tuple.type} [concrete] +// CHECK:STDOUT: %complete_type: = complete_type_witness %struct_type._ [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %.loc9_8: %C.elem = field_decl _, element0 [concrete] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %.loc9_3: %C.elem = var_pattern %.loc9_8 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.var: ref %C.elem = var +// CHECK:STDOUT: %complete_type: = complete_type_witness %struct_type._ [concrete = constants.%complete_type] +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: ._ = %.loc9_8 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_interface.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %I.type: type = facet_type <@I> [concrete] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %I.assoc_type: type = assoc_entity_type %I.type [concrete] +// CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, @I.%_ [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .I = %I.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %I.decl: type = interface_decl @I [concrete = constants.%I.type] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I { +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] +// CHECK:STDOUT: %_: %empty_struct_type = assoc_const_decl @_ [concrete] { +// CHECK:STDOUT: %assoc0: %I.assoc_type = assoc_entity element0, @I.%_ [concrete = constants.%assoc0] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: ._ = @_.%assoc0 +// CHECK:STDOUT: witness = (%_) +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic assoc_const @_(@I.%Self: %I.type) { +// CHECK:STDOUT: assoc_const _:! %empty_struct_type; +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @_(constants.%Self) {} +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_use.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> %empty_struct_type; +// CHECK:STDOUT: diff --git a/toolchain/parse/handle_binding_pattern.cpp b/toolchain/parse/handle_binding_pattern.cpp index 3cfb0ded4c67..fe799c8e969a 100644 --- a/toolchain/parse/handle_binding_pattern.cpp +++ b/toolchain/parse/handle_binding_pattern.cpp @@ -46,6 +46,9 @@ auto HandleBindingPattern(Context& context) -> void { // parameter list of a function. context.AddLeafNode(NodeKind::SelfValueName, *self); has_name = true; + } else if (auto underscore = context.ConsumeIf(Lex::TokenKind::Underscore)) { + context.AddLeafNode(NodeKind::UnderscoreName, *underscore); + has_name = true; } if (!has_name) { // Add a placeholder for the name. diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h index a7c695dc0c02..6b98ee95e3f1 100644 --- a/toolchain/parse/node_ids.h +++ b/toolchain/parse/node_ids.h @@ -141,6 +141,9 @@ using AnyInterfaceDeclId = using AnyNamespaceId = NodeIdOneOf; using AnyPointerDeferenceExprId = NodeIdOneOf; +using AnyRuntimeBindingPatternName = + NodeIdOneOf; // NodeId with kind that is anything but T::Kind. template diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 9032155a21f1..dcc37807003f 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -107,6 +107,8 @@ CARBON_PARSE_NODE_KIND(SelfValueName) CARBON_PARSE_NODE_KIND(SelfValueNameExpr) CARBON_PARSE_NODE_KIND(SelfTypeNameExpr) +CARBON_PARSE_NODE_KIND(UnderscoreName) + CARBON_PARSE_NODE_KIND(BaseName) CARBON_PARSE_NODE_KIND(PackageExpr) CARBON_PARSE_NODE_KIND(CoreNameExpr) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 95c1106ef797..a9fbaf71c910 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -1003,6 +1003,8 @@ CARBON_PARSE_STATE(Pattern) // ^~~~~ // self: ... // ^~~~~ +// _: ... +// ^~ // 1. Expr // 2. BindingPatternFinishAsRegular // @@ -1010,6 +1012,8 @@ CARBON_PARSE_STATE(Pattern) // ^~~~~~~~~~~~~~~~~ // [template] self:! ... // ^~~~~~~~~~~~~~~~~ +// [template] _:! ... +// ^~~~~~~~~~~~~~ // 1. Expr // 2. BindingPatternFinishAsGeneric // diff --git a/toolchain/parse/testdata/let/let.carbon b/toolchain/parse/testdata/let/let.carbon index 8552d3245027..0ca614d4053a 100644 --- a/toolchain/parse/testdata/let/let.carbon +++ b/toolchain/parse/testdata/let/let.carbon @@ -9,8 +9,10 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/let.carbon let v: i32 = 0; +let _: i32 = 1; fn F() { let s: String = "hello"; + let _: i32 = "goodbye"; } // CHECK:STDOUT: - filename: let.carbon @@ -23,6 +25,13 @@ fn F() { // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '0'}, // CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'UnderscoreName', text: '_'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'LetBindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '1'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, // CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, @@ -35,6 +44,13 @@ fn F() { // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'StringLiteral', text: '"hello"'}, // CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 13}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'UnderscoreName', text: '_'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'LetBindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'StringLiteral', text: '"goodbye"'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 20}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 70bb941f1294..3f81939580ab 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -167,6 +167,11 @@ using SelfTypeNameExpr = using BaseName = LeafNode; +// The `_` token, when used in the name position of a binding pattern. +using UnderscoreName = + LeafNode; + // A name qualifier with parameters, such as `A(T:! type).` or `A[T:! type](N:! // T).`. struct IdentifierNameQualifierWithParams { @@ -333,7 +338,7 @@ struct LetBindingPattern { static constexpr auto Kind = NodeKind::LetBindingPattern.Define( {.category = NodeCategory::Pattern, .child_count = 2}); - NodeIdOneOf name; + AnyRuntimeBindingPatternName name; Lex::ColonTokenIndex token; AnyExprId type; }; @@ -343,7 +348,7 @@ struct VarBindingPattern { static constexpr auto Kind = NodeKind::VarBindingPattern.Define( {.category = NodeCategory::Pattern, .child_count = 2}); - NodeIdOneOf name; + AnyRuntimeBindingPatternName name; Lex::ColonTokenIndex token; AnyExprId type; }; @@ -354,7 +359,7 @@ struct TemplateBindingName { NodeKind::TemplateBindingName.Define({.child_count = 1}); Lex::TemplateTokenIndex token; - NodeIdOneOf name; + AnyRuntimeBindingPatternName name; }; // `name:! Type` @@ -362,7 +367,9 @@ struct CompileTimeBindingPattern { static constexpr auto Kind = NodeKind::CompileTimeBindingPattern.Define( {.category = NodeCategory::Pattern, .child_count = 2}); - NodeIdOneOf + // TODO: is there some way to reuse AnyRuntimeBindingPatternName here? + NodeIdOneOf name; Lex::ColonExclaimTokenIndex token; AnyExprId type; diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index b122dbd3c185..bbea2c777e6f 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -1214,6 +1214,10 @@ class FormatterImpl { auto FormatArg(BoolValue v) -> void { out_ << v; } auto FormatArg(EntityNameId id) -> void { + if (!id.has_value()) { + out_ << "_"; + return; + } const auto& info = sem_ir_->entity_names().Get(id); FormatName(info.name_id); if (info.bind_index().has_value()) { diff --git a/toolchain/sem_ir/ids.h b/toolchain/sem_ir/ids.h index 4513560cc14f..9a8da5d9e1e8 100644 --- a/toolchain/sem_ir/ids.h +++ b/toolchain/sem_ir/ids.h @@ -436,6 +436,8 @@ struct FloatKind : public IdBase { X(SelfType) \ /* The name of `self`. */ \ X(SelfValue) \ + /* The name of `_`. */ \ + X(Underscore) \ /* The name of `vptr`. */ \ X(Vptr) diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index 4a303e861898..ddc50ffd212d 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -533,8 +533,11 @@ auto InstNamer::CollectNamesInBlock(ScopeId top_scope_id, case BindingPattern::Kind: case SymbolicBindingPattern::Kind: { auto inst = untyped_inst.As(); - add_inst_name_id( - sem_ir_->entity_names().Get(inst.entity_name_id).name_id, ".patt"); + auto name_id = NameId::Underscore; + if (inst.entity_name_id.has_value()) { + name_id = sem_ir_->entity_names().Get(inst.entity_name_id).name_id; + } + add_inst_name_id(name_id, ".patt"); continue; } case CARBON_KIND(BoolLiteral inst): { diff --git a/toolchain/sem_ir/name.cpp b/toolchain/sem_ir/name.cpp index 39444b6c2ea4..27fc444fb72b 100644 --- a/toolchain/sem_ir/name.cpp +++ b/toolchain/sem_ir/name.cpp @@ -35,6 +35,8 @@ static auto GetSpecialName(NameId name_id, bool for_ir) -> llvm::StringRef { return "Self"; case NameId::SpecialNameId::SelfValue: return "self"; + case NameId::SpecialNameId::Underscore: + return "_"; case NameId::SpecialNameId::Vptr: return for_ir ? "vptr" : ""; } diff --git a/toolchain/sem_ir/pattern.cpp b/toolchain/sem_ir/pattern.cpp index e25d76c29e05..4f1164f037c2 100644 --- a/toolchain/sem_ir/pattern.cpp +++ b/toolchain/sem_ir/pattern.cpp @@ -38,7 +38,11 @@ auto GetPrettyNameFromPatternId(const File& sem_ir, InstId pattern_id) } if (auto binding_pattern = inst.TryAs()) { - return sem_ir.entity_names().Get(binding_pattern->entity_name_id).name_id; + if (binding_pattern->entity_name_id.has_value()) { + return sem_ir.entity_names().Get(binding_pattern->entity_name_id).name_id; + } else { + return NameId::Underscore; + } } return NameId::None; diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index f9f2216a58b3..3baf52c18eea 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -336,6 +336,10 @@ struct AnyBindingPattern { InstKind kind; TypeId type_id; + + // The name declared by the binding pattern. `None` indicates that the + // pattern has `_` in the name position, and so does not truly declare + // a name. EntityNameId entity_name_id; };