Skip to content

Commit 58de34e

Browse files
authored
Decouple associated constants from let (#5973)
Decouples associated constants from being special cased in let handlers. Enforces associated constant grammar restrictions in parsing instead of checking. Closes #5411
1 parent 0e6dd7e commit 58de34e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+755
-677
lines changed

toolchain/check/handle_binding_pattern.cpp

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "toolchain/check/type.h"
1515
#include "toolchain/check/type_completion.h"
1616
#include "toolchain/diagnostics/format_providers.h"
17+
#include "toolchain/parse/node_ids.h"
1718
#include "toolchain/sem_ir/ids.h"
1819
#include "toolchain/sem_ir/inst.h"
1920
#include "toolchain/sem_ir/pattern.h"
@@ -87,61 +88,6 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
8788
context.emitter().Emit(node_id, SelfOutsideImplicitParamList);
8889
}
8990

90-
// A binding in an interface scope declares an associated constant, not a
91-
// true binding, so we handle it separately.
92-
if (auto parent_interface_decl =
93-
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>();
94-
parent_interface_decl.has_value()) {
95-
// TODO: diagnose this during parsing, to avoid near-duplicate error
96-
// messages.
97-
if (!is_generic) {
98-
CARBON_DIAGNOSTIC(ExpectedSymbolicBindingInAssociatedConstant, Error,
99-
"found runtime binding pattern in associated constant "
100-
"declaration; expected a `:!` binding");
101-
context.emitter().Emit(node_id,
102-
ExpectedSymbolicBindingInAssociatedConstant);
103-
context.node_stack().Push(node_id, SemIR::ErrorInst::InstId);
104-
return true;
105-
}
106-
if (name_id == SemIR::NameId::Underscore) {
107-
// The action item here may be to document this as not allowed, and
108-
// add a proper diagnostic.
109-
context.TODO(node_id, "_ used as associated constant name");
110-
}
111-
cast_type_id = AsCompleteType(context, cast_type_id, type_node, [&] {
112-
CARBON_DIAGNOSTIC(IncompleteTypeInAssociatedConstantDecl, Error,
113-
"associated constant has incomplete type {0}",
114-
SemIR::TypeId);
115-
return context.emitter().Build(
116-
type_node, IncompleteTypeInAssociatedConstantDecl, cast_type_id);
117-
});
118-
if (is_template) {
119-
CARBON_DIAGNOSTIC(TemplateBindingInAssociatedConstantDecl, Error,
120-
"associated constant has `template` binding");
121-
context.emitter().Emit(type_node,
122-
TemplateBindingInAssociatedConstantDecl);
123-
}
124-
125-
SemIR::AssociatedConstantDecl assoc_const_decl = {
126-
.type_id = cast_type_id,
127-
.assoc_const_id = SemIR::AssociatedConstantId::None,
128-
.decl_block_id = SemIR::InstBlockId::None};
129-
auto decl_id = AddPlaceholderInstInNoBlock(
130-
context,
131-
context.parse_tree().As<Parse::CompileTimeBindingPatternId>(node_id),
132-
assoc_const_decl);
133-
assoc_const_decl.assoc_const_id = context.associated_constants().Add(
134-
{.name_id = name_id,
135-
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
136-
.decl_id = decl_id,
137-
.generic_id = SemIR::GenericId::None,
138-
.default_value_id = SemIR::InstId::None});
139-
ReplaceInstBeforeConstantUse(context, decl_id, assoc_const_decl);
140-
141-
context.node_stack().Push(node_id, decl_id);
142-
return true;
143-
}
144-
14591
// Allocate an instruction of the appropriate kind, linked to the name for
14692
// error locations.
14793
switch (context.full_pattern_stack().CurrentKind()) {
@@ -298,19 +244,20 @@ auto HandleParseNode(Context& context,
298244
context.scope_stack().Pop();
299245

300246
auto node_kind = Parse::NodeKind::CompileTimeBindingPattern;
301-
302247
const DeclIntroducerState& introducer =
303248
context.decl_introducer_state_stack().innermost();
304249
if (introducer.kind == Lex::TokenKind::Let) {
305250
// Disallow `let` outside of function and interface definitions.
306251
// TODO: Find a less brittle way of doing this. A `scope_inst_id` of `None`
307252
// can represent a block scope, but is also used for other kinds of scopes
308-
// that aren't necessarily part of an interface or function decl.
253+
// that aren't necessarily part of a function decl.
254+
// We don't need to check if the scope is an interface here as this is
255+
// already caught in the parse phase by the separated associated constant
256+
// logic.
309257
auto scope_inst_id = context.scope_stack().PeekInstId();
310258
if (scope_inst_id.has_value()) {
311259
auto scope_inst = context.insts().Get(scope_inst_id);
312-
if (!scope_inst.Is<SemIR::InterfaceDecl>() &&
313-
!scope_inst.Is<SemIR::FunctionDecl>()) {
260+
if (!scope_inst.Is<SemIR::FunctionDecl>()) {
314261
context.TODO(
315262
node_id,
316263
"`let` compile time binding outside function or interface");
@@ -322,6 +269,47 @@ auto HandleParseNode(Context& context,
322269
return HandleAnyBindingPattern(context, node_id, node_kind);
323270
}
324271

272+
auto HandleParseNode(Context& context,
273+
Parse::AssociatedConstantNameAndTypeId node_id) -> bool {
274+
auto [type_node, parsed_type_id] = context.node_stack().PopExprWithNodeId();
275+
auto [cast_type_inst_id, cast_type_id] =
276+
ExprAsType(context, type_node, parsed_type_id);
277+
278+
EndSubpatternAsExpr(context, cast_type_inst_id);
279+
280+
auto [name_node, name_id] = context.node_stack().PopNameWithNodeId();
281+
282+
if (name_id == SemIR::NameId::Underscore) {
283+
// The action item here may be to document this as not allowed, and
284+
// add a proper diagnostic.
285+
context.TODO(node_id, "_ used as associated constant name");
286+
}
287+
cast_type_id = AsCompleteType(context, cast_type_id, type_node, [&] {
288+
CARBON_DIAGNOSTIC(IncompleteTypeInAssociatedConstantDecl, Error,
289+
"associated constant has incomplete type {0}",
290+
SemIR::TypeId);
291+
return context.emitter().Build(
292+
type_node, IncompleteTypeInAssociatedConstantDecl, cast_type_id);
293+
});
294+
295+
SemIR::AssociatedConstantDecl assoc_const_decl = {
296+
.type_id = cast_type_id,
297+
.assoc_const_id = SemIR::AssociatedConstantId::None,
298+
.decl_block_id = SemIR::InstBlockId::None};
299+
auto decl_id =
300+
AddPlaceholderInstInNoBlock(context, node_id, assoc_const_decl);
301+
assoc_const_decl.assoc_const_id = context.associated_constants().Add(
302+
{.name_id = name_id,
303+
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
304+
.decl_id = decl_id,
305+
.generic_id = SemIR::GenericId::None,
306+
.default_value_id = SemIR::InstId::None});
307+
ReplaceInstBeforeConstantUse(context, decl_id, assoc_const_decl);
308+
309+
context.node_stack().Push(node_id, decl_id);
310+
return true;
311+
}
312+
325313
auto HandleParseNode(Context& context, Parse::FieldNameAndTypeId node_id)
326314
-> bool {
327315
auto [type_node, parsed_type_id] = context.node_stack().PopExprWithNodeId();

toolchain/check/handle_let_and_var.cpp

Lines changed: 71 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "toolchain/diagnostics/diagnostic_emitter.h"
1919
#include "toolchain/diagnostics/format_providers.h"
2020
#include "toolchain/lex/token_kind.h"
21+
#include "toolchain/parse/node_ids.h"
2122
#include "toolchain/parse/node_kind.h"
2223
#include "toolchain/parse/typed_nodes.h"
2324
#include "toolchain/sem_ir/ids.h"
@@ -29,15 +30,6 @@
2930

3031
namespace Carbon::Check {
3132

32-
// Handles the start of a declaration of an associated constant.
33-
static auto StartAssociatedConstant(Context& context) -> void {
34-
// An associated constant is always generic.
35-
StartGenericDecl(context);
36-
// Collect the declarations nested in the associated constant in a decl
37-
// block. This is popped by FinishAssociatedConstantDecl.
38-
context.inst_block_stack().Push();
39-
}
40-
4133
// Handles the end of the declaration region of an associated constant. This is
4234
// called at the `=` or the `;` of the declaration, whichever comes first.
4335
static auto EndAssociatedConstantDeclRegion(Context& context,
@@ -95,10 +87,16 @@ static auto HandleIntroducer(Context& context, Parse::NodeId node_id) -> bool {
9587
}
9688

9789
auto HandleParseNode(Context& context, Parse::LetIntroducerId node_id) -> bool {
98-
if (context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>()) {
99-
StartAssociatedConstant(context);
100-
}
90+
return HandleIntroducer<Lex::TokenKind::Let>(context, node_id);
91+
}
10192

93+
auto HandleParseNode(Context& context,
94+
Parse::AssociatedConstantIntroducerId node_id) -> bool {
95+
// An associated constant is always generic.
96+
StartGenericDecl(context);
97+
// Collect the declarations nested in the associated constant in a decl
98+
// block. This is popped by FinishAssociatedConstantDecl.
99+
context.inst_block_stack().Push();
102100
return HandleIntroducer<Lex::TokenKind::Let>(context, node_id);
103101
}
104102

@@ -177,14 +175,18 @@ static auto HandleInitializer(Context& context, Parse::NodeId node_id) -> bool {
177175

178176
auto HandleParseNode(Context& context, Parse::LetInitializerId node_id)
179177
-> bool {
180-
if (auto interface_decl =
181-
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>()) {
182-
auto generic_id =
183-
EndAssociatedConstantDeclRegion(context, interface_decl->interface_id);
178+
return HandleInitializer(context, node_id);
179+
}
184180

185-
// Start building the definition region of the constant.
186-
StartGenericDefinition(context, generic_id);
187-
}
181+
auto HandleParseNode(Context& context,
182+
Parse::AssociatedConstantInitializerId node_id) -> bool {
183+
auto interface_decl =
184+
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>();
185+
auto generic_id =
186+
EndAssociatedConstantDeclRegion(context, interface_decl->interface_id);
187+
188+
// Start building the definition region of the constant.
189+
StartGenericDefinition(context, generic_id);
188190

189191
return HandleInitializer(context, node_id);
190192
}
@@ -232,11 +234,10 @@ static auto HandleDecl(Context& context) -> DeclInfo {
232234
} else {
233235
// For an associated constant declaration, handle the completed declaration
234236
// now. We will have done this at the `=` if there was an initializer.
235-
if (IntroducerTokenKind == Lex::TokenKind::Let) {
236-
if (auto interface_decl =
237-
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>()) {
238-
EndAssociatedConstantDeclRegion(context, interface_decl->interface_id);
239-
}
237+
if (IntroducerNodeKind == Parse::NodeKind::AssociatedConstantIntroducer) {
238+
auto interface_decl =
239+
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>();
240+
EndAssociatedConstantDeclRegion(context, interface_decl->interface_id);
240241
}
241242

242243
EndFullPattern(context);
@@ -261,23 +262,59 @@ static auto HandleDecl(Context& context) -> DeclInfo {
261262
return decl_info;
262263
}
263264

264-
// Finishes an associated constant declaration. This is called at the `;` to
265-
// perform any final steps. The `AssociatedConstantDecl` instruction and the
266-
// corresponding `AssociatedConstant` entity are built as part of handling the
267-
// binding pattern, but we still need to finish building the `Generic` object
268-
// and attach the default value, if any is specified.
269-
static auto FinishAssociatedConstant(Context& context, Parse::LetDeclId node_id,
270-
SemIR::InterfaceId interface_id,
271-
DeclInfo& decl_info) -> void {
265+
auto HandleParseNode(Context& context, Parse::LetDeclId node_id) -> bool {
266+
auto decl_info =
267+
HandleDecl<Lex::TokenKind::Let, Parse::NodeKind::LetIntroducer,
268+
Parse::NodeKind::LetInitializer>(context);
269+
270+
LimitModifiersOnDecl(
271+
context, decl_info.introducer,
272+
KeywordModifierSet::Access | KeywordModifierSet::Interface);
273+
274+
// Diagnose interface modifiers given that we're not building an associated
275+
// constant. We use this rather than `LimitModifiersOnDecl` to get a more
276+
// specific error.
277+
RequireDefaultFinalOnlyInInterfaces(context, decl_info.introducer,
278+
std::nullopt);
279+
280+
if (decl_info.init_id.has_value()) {
281+
LocalPatternMatch(context, decl_info.pattern_id, decl_info.init_id);
282+
} else {
283+
CARBON_DIAGNOSTIC(
284+
ExpectedInitializerAfterLet, Error,
285+
"expected `=`; `let` declaration must have an initializer");
286+
context.emitter().Emit(LocIdForDiagnostics::TokenOnly(node_id),
287+
ExpectedInitializerAfterLet);
288+
}
289+
return true;
290+
}
291+
292+
auto HandleParseNode(Context& context, Parse::AssociatedConstantDeclId node_id)
293+
-> bool {
294+
auto decl_info =
295+
HandleDecl<Lex::TokenKind::Let,
296+
Parse::NodeKind::AssociatedConstantIntroducer,
297+
Parse::NodeKind::AssociatedConstantInitializer>(context);
298+
299+
LimitModifiersOnDecl(
300+
context, decl_info.introducer,
301+
KeywordModifierSet::Access | KeywordModifierSet::Interface);
302+
303+
auto interface_scope =
304+
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>();
305+
// The `AssociatedConstantDecl` instruction and the
306+
// corresponding `AssociatedConstant` entity are built as part of handling the
307+
// binding pattern, but we still need to finish building the `Generic` object
308+
// and attach the default value, if any is specified.
272309
if (decl_info.pattern_id == SemIR::ErrorInst::InstId) {
273310
context.name_scopes()
274-
.Get(context.interfaces().Get(interface_id).scope_id)
311+
.Get(context.interfaces().Get(interface_scope->interface_id).scope_id)
275312
.set_has_error();
276313
if (decl_info.init_id.has_value()) {
277314
DiscardGenericDecl(context);
278315
}
279316
context.inst_block_stack().Pop();
280-
return;
317+
return true;
281318
}
282319
auto decl = context.insts().GetAs<SemIR::AssociatedConstantDecl>(
283320
decl_info.pattern_id);
@@ -306,41 +343,6 @@ static auto FinishAssociatedConstant(Context& context, Parse::LetDeclId node_id,
306343
ReplaceInstPreservingConstantValue(context, decl_info.pattern_id, decl);
307344

308345
context.inst_block_stack().AddInstId(decl_info.pattern_id);
309-
}
310-
311-
auto HandleParseNode(Context& context, Parse::LetDeclId node_id) -> bool {
312-
auto decl_info =
313-
HandleDecl<Lex::TokenKind::Let, Parse::NodeKind::LetIntroducer,
314-
Parse::NodeKind::LetInitializer>(context);
315-
316-
LimitModifiersOnDecl(
317-
context, decl_info.introducer,
318-
KeywordModifierSet::Access | KeywordModifierSet::Interface);
319-
320-
// At interface scope, we are forming an associated constant, which has
321-
// different rules.
322-
if (auto interface_scope =
323-
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>()) {
324-
FinishAssociatedConstant(context, node_id, interface_scope->interface_id,
325-
decl_info);
326-
return true;
327-
}
328-
329-
// Diagnose interface modifiers given that we're not building an associated
330-
// constant. We use this rather than `LimitModifiersOnDecl` to get a more
331-
// specific error.
332-
RequireDefaultFinalOnlyInInterfaces(context, decl_info.introducer,
333-
std::nullopt);
334-
335-
if (decl_info.init_id.has_value()) {
336-
LocalPatternMatch(context, decl_info.pattern_id, decl_info.init_id);
337-
} else {
338-
CARBON_DIAGNOSTIC(
339-
ExpectedInitializerAfterLet, Error,
340-
"expected `=`; `let` declaration must have an initializer");
341-
context.emitter().Emit(LocIdForDiagnostics::TokenOnly(node_id),
342-
ExpectedInitializerAfterLet);
343-
}
344346
return true;
345347
}
346348

toolchain/check/handle_pattern_list.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,6 @@ auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool {
7474
context.node_stack()
7575
.PopAndDiscardSoloNodeId<Parse::NodeKind::TuplePatternStart>();
7676

77-
if (context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceDecl>()) {
78-
CARBON_DIAGNOSTIC(ExpectedSingleBindingInAssociatedConstant, Error,
79-
"found tuple pattern in associated constant declaration; "
80-
"expected symbolic binding pattern");
81-
context.emitter().Emit(node_id, ExpectedSingleBindingInAssociatedConstant);
82-
context.node_stack().Push(node_id, SemIR::ErrorInst::InstId);
83-
EndSubpatternAsNonExpr(context);
84-
return true;
85-
}
8677
const auto& inst_block = context.inst_blocks().Get(refs_id);
8778
llvm::SmallVector<SemIR::InstId> type_inst_ids;
8879
type_inst_ids.reserve(inst_block.size());

toolchain/check/node_stack.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ class NodeStack {
457457
case Parse::NodeKind::InterfaceIntroducer:
458458
case Parse::NodeKind::LetInitializer:
459459
case Parse::NodeKind::LetIntroducer:
460+
case Parse::NodeKind::AssociatedConstantIntroducer:
461+
case Parse::NodeKind::AssociatedConstantInitializer:
460462
case Parse::NodeKind::ReturnStatementStart:
461463
case Parse::NodeKind::StructLiteralStart:
462464
case Parse::NodeKind::StructTypeLiteralField:

0 commit comments

Comments
 (0)