Skip to content

Commit d958caa

Browse files
authored
Refactor CheckIsAllowedRedecl and stop function definition merging (#4800)
Rename `CheckIsAllowedRedecl` to `DiagnoseIfInvalidRedecl` to try to better document behavior, and clean up comments. This extends the no-merge-if-defined behavior to functions. It was already the case for class/interface, and just added for impl, so if anything functions were now inconsistent. I was kind of tempted to make a helper for it, but I didn't think of a great structure/name to get there: `DiagnoseRedef` isn't always called when it's a redefinition, for example due to `extern` diagnostics, it's hard to combine. Cleans up `is_defined` calls to rely more on `has_definition_started`, removing some code paths that are unused since definitions aren't merged.
1 parent 5b70a3e commit d958caa

File tree

15 files changed

+195
-142
lines changed

15 files changed

+195
-142
lines changed

toolchain/check/handle_class.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ static auto MergeClassRedecl(Context& context, SemIRLoc new_loc,
6565
return false;
6666
}
6767

68-
CheckIsAllowedRedecl(
68+
DiagnoseIfInvalidRedecl(
6969
context, Lex::TokenKind::Class, prev_class.name_id,
7070
RedeclInfo(new_class, new_loc, new_is_definition),
71-
RedeclInfo(prev_class, prev_loc, prev_class.is_defined()),
71+
RedeclInfo(prev_class, prev_loc, prev_class.has_definition_started()),
7272
prev_import_ir_id);
7373

74-
if (new_is_definition && prev_class.is_defined()) {
74+
if (new_is_definition && prev_class.has_definition_started()) {
7575
// Don't attempt to merge multiple definitions.
7676
return false;
7777
}
@@ -280,11 +280,10 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionStartId node_id)
280280
auto& class_info = context.classes().Get(class_id);
281281

282282
// Track that this declaration is the definition.
283-
if (!class_info.is_defined()) {
284-
class_info.definition_id = class_decl_id;
285-
class_info.scope_id = context.name_scopes().Add(
286-
class_decl_id, SemIR::NameId::Invalid, class_info.parent_scope_id);
287-
}
283+
CARBON_CHECK(!class_info.has_definition_started());
284+
class_info.definition_id = class_decl_id;
285+
class_info.scope_id = context.name_scopes().Add(
286+
class_decl_id, SemIR::NameId::Invalid, class_info.parent_scope_id);
288287

289288
// Enter the class scope.
290289
context.scope_stack().Push(

toolchain/check/handle_function.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,15 @@ static auto MergeFunctionRedecl(Context& context, SemIRLoc new_loc,
8888
return false;
8989
}
9090

91-
CheckIsAllowedRedecl(context, Lex::TokenKind::Fn, prev_function.name_id,
92-
RedeclInfo(new_function, new_loc, new_is_definition),
93-
RedeclInfo(prev_function, prev_function.latest_decl_id(),
94-
prev_function.has_definition_started()),
95-
prev_import_ir_id);
91+
DiagnoseIfInvalidRedecl(
92+
context, Lex::TokenKind::Fn, prev_function.name_id,
93+
RedeclInfo(new_function, new_loc, new_is_definition),
94+
RedeclInfo(prev_function, prev_function.latest_decl_id(),
95+
prev_function.has_definition_started()),
96+
prev_import_ir_id);
97+
if (new_is_definition && prev_function.has_definition_started()) {
98+
return false;
99+
}
96100

97101
if (!prev_function.first_owning_decl_id.is_valid()) {
98102
prev_function.first_owning_decl_id = new_function.first_owning_decl_id;

toolchain/check/handle_impl.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,7 @@ auto HandleParseNode(Context& context, Parse::ImplDefinitionStartId node_id)
453453
impl_decl_id, impl_info.scope_id,
454454
context.generics().GetSelfSpecific(impl_info.generic_id));
455455
StartGenericDefinition(context);
456-
457-
if (!impl_info.is_defined()) {
458-
ImplWitnessStartDefinition(context, impl_info);
459-
}
456+
ImplWitnessStartDefinition(context, impl_info);
460457
context.inst_block_stack().Push();
461458
context.node_stack().Push(node_id, impl_id);
462459

@@ -479,11 +476,9 @@ auto HandleParseNode(Context& context, Parse::ImplDefinitionId /*node_id*/)
479476
context.node_stack().Pop<Parse::NodeKind::ImplDefinitionStart>();
480477

481478
auto& impl_info = context.impls().Get(impl_id);
482-
if (!impl_info.is_defined()) {
483-
FinishImplWitness(context, impl_info);
484-
impl_info.defined = true;
485-
}
486-
479+
CARBON_CHECK(!impl_info.is_defined());
480+
FinishImplWitness(context, impl_info);
481+
impl_info.defined = true;
487482
FinishGenericDefinition(context, impl_info.generic_id);
488483

489484
context.inst_block_stack().Pop();

toolchain/check/handle_interface.cpp

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,15 @@ static auto BuildInterfaceDecl(Context& context,
7979
// TODO: This should be refactored a little, particularly for
8080
// prev_import_ir_id. See similar logic for classes and functions, which
8181
// might also be refactored to merge.
82-
CheckIsAllowedRedecl(
82+
DiagnoseIfInvalidRedecl(
8383
context, Lex::TokenKind::Interface, existing_interface.name_id,
8484
RedeclInfo(interface_info, node_id, is_definition),
8585
RedeclInfo(existing_interface, existing_interface.latest_decl_id(),
86-
existing_interface.is_defined()),
86+
existing_interface.has_definition_started()),
8787
/*prev_import_ir_id=*/SemIR::ImportIRId::Invalid);
8888

8989
// Can't merge interface definitions due to the generic requirements.
90-
// TODO: Should this also be mirrored to classes/functions for generics?
91-
if (!is_definition || !existing_interface.is_defined()) {
90+
if (!is_definition || !existing_interface.has_definition_started()) {
9291
// This is a redeclaration of an existing interface.
9392
interface_decl.interface_id = existing_interface_decl->interface_id;
9493
interface_decl.type_id = existing_interface_decl->type_id;
@@ -140,7 +139,7 @@ auto HandleParseNode(Context& context,
140139
auto& interface_info = context.interfaces().Get(interface_id);
141140

142141
// Track that this declaration is the definition.
143-
CARBON_CHECK(!interface_info.is_defined(),
142+
CARBON_CHECK(!interface_info.has_definition_started(),
144143
"Can't merge with defined interfaces.");
145144
interface_info.definition_id = interface_decl_id;
146145
interface_info.scope_id =
@@ -159,29 +158,27 @@ auto HandleParseNode(Context& context,
159158
context.args_type_info_stack().Push();
160159

161160
// Declare and introduce `Self`.
162-
if (!interface_info.is_defined()) {
163-
SemIR::FacetType facet_type =
164-
context.FacetTypeFromInterface(interface_id, self_specific_id);
165-
SemIR::TypeId self_type_id = context.GetTypeIdForTypeConstant(
166-
TryEvalInst(context, SemIR::InstId::Invalid, facet_type));
167-
168-
// We model `Self` as a symbolic binding whose type is the interface.
169-
// Because there is no equivalent non-symbolic value, we use `Invalid` as
170-
// the `value_id` on the `BindSymbolicName`.
171-
auto entity_name_id = context.entity_names().Add(
172-
{.name_id = SemIR::NameId::SelfType,
173-
.parent_scope_id = interface_info.scope_id,
174-
.bind_index = context.scope_stack().AddCompileTimeBinding()});
175-
interface_info.self_param_id =
176-
context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
177-
{.type_id = self_type_id,
178-
.entity_name_id = entity_name_id,
179-
.value_id = SemIR::InstId::Invalid}));
180-
context.scope_stack().PushCompileTimeBinding(interface_info.self_param_id);
181-
context.name_scopes().AddRequiredName(interface_info.scope_id,
182-
SemIR::NameId::SelfType,
183-
interface_info.self_param_id);
184-
}
161+
SemIR::FacetType facet_type =
162+
context.FacetTypeFromInterface(interface_id, self_specific_id);
163+
SemIR::TypeId self_type_id = context.GetTypeIdForTypeConstant(
164+
TryEvalInst(context, SemIR::InstId::Invalid, facet_type));
165+
166+
// We model `Self` as a symbolic binding whose type is the interface.
167+
// Because there is no equivalent non-symbolic value, we use `Invalid` as
168+
// the `value_id` on the `BindSymbolicName`.
169+
auto entity_name_id = context.entity_names().Add(
170+
{.name_id = SemIR::NameId::SelfType,
171+
.parent_scope_id = interface_info.scope_id,
172+
.bind_index = context.scope_stack().AddCompileTimeBinding()});
173+
interface_info.self_param_id =
174+
context.AddInst(SemIR::LocIdAndInst::NoLoc<SemIR::BindSymbolicName>(
175+
{.type_id = self_type_id,
176+
.entity_name_id = entity_name_id,
177+
.value_id = SemIR::InstId::Invalid}));
178+
context.scope_stack().PushCompileTimeBinding(interface_info.self_param_id);
179+
context.name_scopes().AddRequiredName(interface_info.scope_id,
180+
SemIR::NameId::SelfType,
181+
interface_info.self_param_id);
185182

186183
// Enter the interface scope.
187184
context.scope_stack().Push(interface_decl_id, interface_info.scope_id,

toolchain/check/merge.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,10 @@ auto DiagnoseExternRequiresDeclInApiFile(Context& context, SemIRLoc loc)
9191
context.emitter().Build(loc, ExternRequiresDeclInApiFile).Emit();
9292
}
9393

94-
// Checks to see if a structurally valid redeclaration is allowed in context.
95-
// These all still merge.
96-
auto CheckIsAllowedRedecl(Context& context, Lex::TokenKind decl_kind,
97-
SemIR::NameId name_id, RedeclInfo new_decl,
98-
RedeclInfo prev_decl, SemIR::ImportIRId import_ir_id)
99-
-> void {
94+
auto DiagnoseIfInvalidRedecl(Context& context, Lex::TokenKind decl_kind,
95+
SemIR::NameId name_id, RedeclInfo new_decl,
96+
RedeclInfo prev_decl,
97+
SemIR::ImportIRId import_ir_id) -> void {
10098
if (!import_ir_id.is_valid()) {
10199
// Check for disallowed redeclarations in the same file.
102100
if (!new_decl.is_definition) {

toolchain/check/merge.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Carbon::Check {
1616
auto DiagnoseExternRequiresDeclInApiFile(Context& context, SemIRLoc loc)
1717
-> void;
1818

19-
// Information on new and previous declarations for CheckIsAllowedRedecl.
19+
// Information on new and previous declarations for DiagnoseIfInvalidRedecl.
2020
struct RedeclInfo {
2121
explicit RedeclInfo(SemIR::EntityWithParamsBase params, SemIRLoc loc,
2222
bool is_definition)
@@ -35,18 +35,19 @@ struct RedeclInfo {
3535
SemIR::LibraryNameId extern_library_id;
3636
};
3737

38-
// Checks if a redeclaration is allowed prior to merging. This may emit a
39-
// diagnostic, but diagnostics do not prevent merging.
38+
// Checks for various invalid redeclarations. This can emit diagnostics.
39+
// However, merging is still often appropriate for error recovery, so this
40+
// doesn't return whether a diagnostic occurred.
4041
//
4142
// The kinds of things this verifies are:
4243
// - A declaration is not redundant.
4344
// - A definition doesn't redefine a prior definition.
4445
// - The use of `extern` is consistent within a library.
4546
// - Multiple libraries do not declare non-`extern`.
46-
auto CheckIsAllowedRedecl(Context& context, Lex::TokenKind decl_kind,
47-
SemIR::NameId name_id, RedeclInfo new_decl,
48-
RedeclInfo prev_decl,
49-
SemIR::ImportIRId prev_import_ir_id) -> void;
47+
auto DiagnoseIfInvalidRedecl(Context& context, Lex::TokenKind decl_kind,
48+
SemIR::NameId name_id, RedeclInfo new_decl,
49+
RedeclInfo prev_decl,
50+
SemIR::ImportIRId prev_import_ir_id) -> void;
5051

5152
// When the prior name lookup result is an import and we are successfully
5253
// merging, replace the name lookup result with the reference in the current

toolchain/check/testdata/class/fail_method_redefinition.carbon

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Class {
2525
// CHECK:STDOUT: %Class: type = class_type @Class [template]
2626
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
2727
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
28+
// CHECK:STDOUT: %.type: type = fn_type @.1 [template]
29+
// CHECK:STDOUT: %.d22: %.type = struct_value () [template]
2830
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
2931
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
3032
// CHECK:STDOUT: }
@@ -46,13 +48,13 @@ class Class {
4648
// CHECK:STDOUT: }
4749
// CHECK:STDOUT:
4850
// CHECK:STDOUT: class @Class {
49-
// CHECK:STDOUT: %F.decl.loc12: %F.type = fn_decl @F [template = constants.%F] {} {}
50-
// CHECK:STDOUT: %F.decl.loc19: %F.type = fn_decl @F [template = constants.%F] {} {}
51+
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
52+
// CHECK:STDOUT: %.decl: %.type = fn_decl @.1 [template = constants.%.d22] {} {}
5153
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
5254
// CHECK:STDOUT:
5355
// CHECK:STDOUT: !members:
5456
// CHECK:STDOUT: .Self = constants.%Class
55-
// CHECK:STDOUT: .F = %F.decl.loc12
57+
// CHECK:STDOUT: .F = %F.decl
5658
// CHECK:STDOUT: complete_type_witness = %complete_type
5759
// CHECK:STDOUT: }
5860
// CHECK:STDOUT:
@@ -61,3 +63,8 @@ class Class {
6163
// CHECK:STDOUT: return
6264
// CHECK:STDOUT: }
6365
// CHECK:STDOUT:
66+
// CHECK:STDOUT: fn @.1() {
67+
// CHECK:STDOUT: !entry:
68+
// CHECK:STDOUT: return
69+
// CHECK:STDOUT: }
70+
// CHECK:STDOUT:

toolchain/check/testdata/class/fail_redefinition.carbon

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn Class.I() {}
5454
// CHECK:STDOUT: %I.c9a: %I.type.2b6 = struct_value () [template]
5555
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
5656
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
57-
// CHECK:STDOUT: %.a95: type = class_type @.1 [template]
57+
// CHECK:STDOUT: %.a95: type = class_type @.2 [template]
5858
// CHECK:STDOUT: %G.type.bf6: type = fn_type @G.1 [template]
5959
// CHECK:STDOUT: %G.e39: %G.type.bf6 = struct_value () [template]
6060
// CHECK:STDOUT: %H.type.e2f: type = fn_type @H.2 [template]
@@ -63,6 +63,8 @@ fn Class.I() {}
6363
// CHECK:STDOUT: %I.a7f: %I.type.b27 = struct_value () [template]
6464
// CHECK:STDOUT: %G.type.621: type = fn_type @G.2 [template]
6565
// CHECK:STDOUT: %G.f0c: %G.type.621 = struct_value () [template]
66+
// CHECK:STDOUT: %.type: type = fn_type @.1 [template]
67+
// CHECK:STDOUT: %.d22: %.type = struct_value () [template]
6668
// CHECK:STDOUT: }
6769
// CHECK:STDOUT:
6870
// CHECK:STDOUT: imports {
@@ -79,11 +81,11 @@ fn Class.I() {}
7981
// CHECK:STDOUT: }
8082
// CHECK:STDOUT: %Core.import = import Core
8183
// CHECK:STDOUT: %Class.decl: type = class_decl @Class [template = constants.%Class] {} {}
82-
// CHECK:STDOUT: %.decl: type = class_decl @.1 [template = constants.%.a95] {} {}
84+
// CHECK:STDOUT: %.decl.loc24: type = class_decl @.2 [template = constants.%.a95] {} {}
8385
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
8486
// CHECK:STDOUT: %G.decl: %G.type.621 = fn_decl @G.2 [template = constants.%G.f0c] {} {}
8587
// CHECK:STDOUT: %H.decl: %H.type.91d = fn_decl @H.1 [template = constants.%H.d38] {} {}
86-
// CHECK:STDOUT: %I.decl: %I.type.2b6 = fn_decl @I.1 [template = constants.%I.c9a] {} {}
88+
// CHECK:STDOUT: %.decl.loc43: %.type = fn_decl @.1 [template = constants.%.d22] {} {}
8789
// CHECK:STDOUT: }
8890
// CHECK:STDOUT:
8991
// CHECK:STDOUT: class @Class {
@@ -101,7 +103,7 @@ fn Class.I() {}
101103
// CHECK:STDOUT: complete_type_witness = %complete_type
102104
// CHECK:STDOUT: }
103105
// CHECK:STDOUT:
104-
// CHECK:STDOUT: class @.1 {
106+
// CHECK:STDOUT: class @.2 {
105107
// CHECK:STDOUT: %G.decl: %G.type.bf6 = fn_decl @G.1 [template = constants.%G.e39] {} {}
106108
// CHECK:STDOUT: %H.decl: %H.type.e2f = fn_decl @H.2 [template = constants.%H.382] {} {}
107109
// CHECK:STDOUT: %I.decl: %I.type.b27 = fn_decl @I.2 [template = constants.%I.a7f] {} {}
@@ -144,3 +146,8 @@ fn Class.I() {}
144146
// CHECK:STDOUT: return
145147
// CHECK:STDOUT: }
146148
// CHECK:STDOUT:
149+
// CHECK:STDOUT: fn @.1() {
150+
// CHECK:STDOUT: !entry:
151+
// CHECK:STDOUT: return
152+
// CHECK:STDOUT: }
153+
// CHECK:STDOUT:

0 commit comments

Comments
 (0)