Skip to content

Commit 68f6906

Browse files
authored
Refactor BuildFunctionDecl (#5098)
Trying to break apart the function on reasonable boundaries because it's big. Changes behavior of class functions with virtual modifiers to remove the modifier when diagnosing, which is more consistent with other modifier diagnostics.
1 parent bdf5d17 commit 68f6906

File tree

3 files changed

+168
-111
lines changed

3 files changed

+168
-111
lines changed

toolchain/check/decl_name_stack.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
namespace Carbon::Check {
2020

21-
auto DeclNameStack::NameContext::prev_inst_id() -> SemIR::InstId {
21+
auto DeclNameStack::NameContext::prev_inst_id() const -> SemIR::InstId {
2222
switch (state) {
2323
case NameContext::State::Error:
2424
// The name is malformed and a diagnostic has already been emitted.

toolchain/check/decl_name_stack.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class DeclNameStack {
118118

119119
// Returns any name collision found, or `None`. Requires a non-poisoned
120120
// value.
121-
auto prev_inst_id() -> SemIR::InstId;
121+
auto prev_inst_id() const -> SemIR::InstId;
122122

123123
// Returns the name_id for a new instruction. This is `None` when the name
124124
// resolved.

toolchain/check/handle_function.cpp

Lines changed: 166 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
#include "toolchain/check/import_ref.h"
1616
#include "toolchain/check/inst.h"
1717
#include "toolchain/check/interface.h"
18+
#include "toolchain/check/keyword_modifier_set.h"
1819
#include "toolchain/check/literal.h"
1920
#include "toolchain/check/merge.h"
2021
#include "toolchain/check/modifiers.h"
2122
#include "toolchain/check/name_component.h"
2223
#include "toolchain/check/name_lookup.h"
2324
#include "toolchain/check/type.h"
2425
#include "toolchain/check/type_completion.h"
26+
#include "toolchain/parse/node_ids.h"
2527
#include "toolchain/sem_ir/builtin_function_kind.h"
2628
#include "toolchain/sem_ir/entry_point.h"
2729
#include "toolchain/sem_ir/function.h"
@@ -75,11 +77,33 @@ auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
7577
return true;
7678
}
7779

78-
static auto DiagnoseModifiers(Context& context, DeclIntroducerState& introducer,
80+
// Returns the ID of the self parameter pattern, or None.
81+
// TODO: Do this during initial traversal of implicit params.
82+
static auto FindSelfPattern(Context& context,
83+
SemIR::InstBlockId implicit_param_patterns_id)
84+
-> SemIR::InstId {
85+
auto implicit_param_patterns =
86+
context.inst_blocks().GetOrEmpty(implicit_param_patterns_id);
87+
if (const auto* i = llvm::find_if(implicit_param_patterns,
88+
[&](auto implicit_param_id) {
89+
return SemIR::IsSelfPattern(
90+
context.sem_ir(), implicit_param_id);
91+
});
92+
i != implicit_param_patterns.end()) {
93+
return *i;
94+
}
95+
return SemIR::InstId::None;
96+
}
97+
98+
// Diagnoses issues with the modifiers, removing modifiers that shouldn't be
99+
// present.
100+
static auto DiagnoseModifiers(Context& context,
101+
Parse::AnyFunctionDeclId node_id,
102+
DeclIntroducerState& introducer,
79103
bool is_definition,
80104
SemIR::InstId parent_scope_inst_id,
81-
std::optional<SemIR::Inst> parent_scope_inst)
82-
-> void {
105+
std::optional<SemIR::Inst> parent_scope_inst,
106+
SemIR::InstId self_param_id) -> void {
83107
CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst);
84108
LimitModifiersOnDecl(context, introducer,
85109
KeywordModifierSet::Access | KeywordModifierSet::Extern |
@@ -90,6 +114,33 @@ static auto DiagnoseModifiers(Context& context, DeclIntroducerState& introducer,
90114
CheckMethodModifiersOnFunction(context, introducer, parent_scope_inst_id,
91115
parent_scope_inst);
92116
RequireDefaultFinalOnlyInInterfaces(context, introducer, parent_scope_inst);
117+
118+
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Interface)) {
119+
// TODO: Once we are saving the modifiers for a function, add check that
120+
// the function may only be defined if it is marked `default` or `final`.
121+
context.TODO(introducer.modifier_node_id(ModifierOrder::Decl),
122+
"interface modifier");
123+
}
124+
125+
if (!self_param_id.has_value() &&
126+
introducer.modifier_set.HasAnyOf(KeywordModifierSet::Method)) {
127+
CARBON_DIAGNOSTIC(VirtualWithoutSelf, Error, "virtual class function");
128+
context.emitter().Build(node_id, VirtualWithoutSelf).Emit();
129+
// TODO: Remove the incorrect modifier.
130+
// introducer.modifier_set.Remove(KeywordModifierSet::Method);
131+
}
132+
}
133+
134+
// Returns the virtual-family modifier as an enum.
135+
static auto GetVirtualModifier(const KeywordModifierSet& modifier_set)
136+
-> SemIR::Function::VirtualModifier {
137+
return modifier_set.ToEnum<SemIR::Function::VirtualModifier>()
138+
.Case(KeywordModifierSet::Virtual,
139+
SemIR::Function::VirtualModifier::Virtual)
140+
.Case(KeywordModifierSet::Abstract,
141+
SemIR::Function::VirtualModifier::Abstract)
142+
.Case(KeywordModifierSet::Impl, SemIR::Function::VirtualModifier::Impl)
143+
.Default(SemIR::Function::VirtualModifier::None);
93144
}
94145

95146
// Tries to merge new_function into prev_function_id. Since new_function won't
@@ -140,11 +191,17 @@ static auto MergeFunctionRedecl(Context& context,
140191

141192
// Check whether this is a redeclaration, merging if needed.
142193
static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
143-
SemIR::NameId name_id, SemIR::InstId prev_id,
144-
SemIR::LocId name_loc_id,
194+
const DeclNameStack::NameContext& name_context,
145195
SemIR::FunctionDecl& function_decl,
146196
SemIR::Function& function_info, bool is_definition)
147197
-> void {
198+
if (name_context.state == DeclNameStack::NameContext::State::Poisoned) {
199+
DiagnosePoisonedName(context, name_context.name_id_for_new_inst(),
200+
name_context.poisoning_loc_id, name_context.loc_id);
201+
return;
202+
}
203+
204+
auto prev_id = name_context.prev_inst_id();
148205
if (!prev_id.has_value()) {
149206
return;
150207
}
@@ -185,7 +242,8 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
185242
}
186243

187244
if (!prev_function_id.has_value()) {
188-
DiagnoseDuplicateName(context, name_id, name_loc_id, prev_id);
245+
DiagnoseDuplicateName(context, name_context.name_id, name_context.loc_id,
246+
prev_id);
189247
return;
190248
}
191249

@@ -197,6 +255,89 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
197255
}
198256
}
199257

258+
// Adds the declaration to name lookup when appropriate.
259+
static auto MaybeAddToNameLookup(
260+
Context& context, const DeclNameStack::NameContext& name_context,
261+
const KeywordModifierSet& modifier_set,
262+
const std::optional<SemIR::Inst>& parent_scope_inst, SemIR::InstId decl_id)
263+
-> void {
264+
if (name_context.state == DeclNameStack::NameContext::State::Poisoned ||
265+
name_context.prev_inst_id().has_value()) {
266+
return;
267+
}
268+
269+
// At interface scope, a function declaration introduces an associated
270+
// function.
271+
auto lookup_result_id = decl_id;
272+
if (parent_scope_inst && !name_context.has_qualifiers) {
273+
if (auto interface_scope =
274+
parent_scope_inst->TryAs<SemIR::InterfaceDecl>()) {
275+
lookup_result_id = BuildAssociatedEntity(
276+
context, interface_scope->interface_id, decl_id);
277+
}
278+
}
279+
280+
context.decl_name_stack().AddName(name_context, lookup_result_id,
281+
modifier_set.GetAccessKind());
282+
}
283+
284+
// If the function is the entry point, do corresponding validation.
285+
static auto ValidateForEntryPoint(Context& context,
286+
Parse::AnyFunctionDeclId node_id,
287+
SemIR::FunctionId function_id,
288+
const SemIR::Function& function_info)
289+
-> void {
290+
if (!SemIR::IsEntryPoint(context.sem_ir(), function_id)) {
291+
return;
292+
}
293+
294+
auto return_type_id = function_info.GetDeclaredReturnType(context.sem_ir());
295+
// TODO: Update this once valid signatures for the entry point are decided.
296+
if (function_info.implicit_param_patterns_id.has_value() ||
297+
!function_info.param_patterns_id.has_value() ||
298+
!context.inst_blocks().Get(function_info.param_patterns_id).empty() ||
299+
(return_type_id.has_value() &&
300+
return_type_id != GetTupleType(context, {}) &&
301+
// TODO: Decide on valid return types for `Main.Run`. Perhaps we should
302+
// have an interface for this.
303+
return_type_id != MakeIntType(context, node_id, SemIR::IntKind::Signed,
304+
context.ints().Add(32)))) {
305+
CARBON_DIAGNOSTIC(InvalidMainRunSignature, Error,
306+
"invalid signature for `Main.Run` function; expected "
307+
"`fn ()` or `fn () -> i32`");
308+
context.emitter().Emit(node_id, InvalidMainRunSignature);
309+
}
310+
}
311+
312+
// Requests a vtable be created when processing a virtual function.
313+
static auto RequestVtableIfVirtual(
314+
Context& context, Parse::AnyFunctionDeclId node_id,
315+
SemIR::Function::VirtualModifier virtual_modifier,
316+
const std::optional<SemIR::Inst>& parent_scope_inst, SemIR::InstId decl_id)
317+
-> void {
318+
// In order to request a vtable, the function must be virtual, and in a class
319+
// scope.
320+
if (virtual_modifier == SemIR::Function::VirtualModifier::None ||
321+
!parent_scope_inst) {
322+
return;
323+
}
324+
auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>();
325+
if (!class_decl) {
326+
return;
327+
}
328+
329+
auto& class_info = context.classes().Get(class_decl->class_id);
330+
if (virtual_modifier == SemIR::Function::VirtualModifier::Impl &&
331+
!class_info.base_id.has_value()) {
332+
CARBON_DIAGNOSTIC(ImplWithoutBase, Error, "impl without base class");
333+
context.emitter().Build(node_id, ImplWithoutBase).Emit();
334+
}
335+
// TODO: If this is an `impl` function, check there's a matching base
336+
// function that's impl or virtual.
337+
class_info.is_dynamic = true;
338+
context.vtable_stack().AddInstId(decl_id);
339+
}
340+
200341
// Build a FunctionDecl describing the signature of a function. This
201342
// handles the common logic shared by function declaration syntax and function
202343
// definition syntax.
@@ -216,6 +357,8 @@ static auto BuildFunctionDecl(Context& context,
216357
context.TODO(node_id, "function with positional parameters");
217358
name.param_patterns_id = SemIR::InstBlockId::Empty;
218359
}
360+
auto self_param_id =
361+
FindSelfPattern(context, name.implicit_param_patterns_id);
219362

220363
auto name_context = context.decl_name_stack().FinishName(name);
221364
context.node_stack()
@@ -226,89 +369,34 @@ static auto BuildFunctionDecl(Context& context,
226369
context.name_scopes().GetInstIfValid(name_context.parent_scope_id);
227370
auto introducer =
228371
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Fn>();
229-
DiagnoseModifiers(context, introducer, is_definition, parent_scope_inst_id,
230-
parent_scope_inst);
372+
DiagnoseModifiers(context, node_id, introducer, is_definition,
373+
parent_scope_inst_id, parent_scope_inst, self_param_id);
231374
bool is_extern = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extern);
232-
auto virtual_modifier =
233-
introducer.modifier_set.ToEnum<SemIR::Function::VirtualModifier>()
234-
.Case(KeywordModifierSet::Virtual,
235-
SemIR::Function::VirtualModifier::Virtual)
236-
.Case(KeywordModifierSet::Abstract,
237-
SemIR::Function::VirtualModifier::Abstract)
238-
.Case(KeywordModifierSet::Impl,
239-
SemIR::Function::VirtualModifier::Impl)
240-
.Default(SemIR::Function::VirtualModifier::None);
241-
SemIR::Class* virtual_class_info = nullptr;
242-
if (virtual_modifier != SemIR::Function::VirtualModifier::None &&
243-
parent_scope_inst) {
244-
if (auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>()) {
245-
virtual_class_info = &context.classes().Get(class_decl->class_id);
246-
if (virtual_modifier == SemIR::Function::VirtualModifier::Impl &&
247-
!virtual_class_info->base_id.has_value()) {
248-
CARBON_DIAGNOSTIC(ImplWithoutBase, Error, "impl without base class");
249-
context.emitter().Build(node_id, ImplWithoutBase).Emit();
250-
}
251-
// TODO: If this is an `impl` function, check there's a matching base
252-
// function that's impl or virtual.
253-
virtual_class_info->is_dynamic = true;
254-
}
255-
}
256-
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Interface)) {
257-
// TODO: Once we are saving the modifiers for a function, add check that
258-
// the function may only be defined if it is marked `default` or `final`.
259-
context.TODO(introducer.modifier_node_id(ModifierOrder::Decl),
260-
"interface modifier");
261-
}
375+
auto virtual_modifier = GetVirtualModifier(introducer.modifier_set);
262376

263377
// Add the function declaration.
264-
auto decl_block_id = context.inst_block_stack().Pop();
265-
auto function_decl = SemIR::FunctionDecl{
266-
SemIR::TypeId::None, SemIR::FunctionId::None, decl_block_id};
378+
SemIR::FunctionDecl function_decl = {SemIR::TypeId::None,
379+
SemIR::FunctionId::None,
380+
context.inst_block_stack().Pop()};
267381
auto decl_id =
268382
AddPlaceholderInst(context, SemIR::LocIdAndInst(node_id, function_decl));
383+
RequestVtableIfVirtual(context, node_id, virtual_modifier, parent_scope_inst,
384+
decl_id);
269385

270-
// Find self parameter pattern.
271-
// TODO: Do this during initial traversal of implicit params.
272-
auto self_param_id = SemIR::InstId::None;
273-
auto implicit_param_patterns =
274-
context.inst_blocks().GetOrEmpty(name.implicit_param_patterns_id);
275-
if (const auto* i = llvm::find_if(implicit_param_patterns,
276-
[&](auto implicit_param_id) {
277-
return SemIR::IsSelfPattern(
278-
context.sem_ir(), implicit_param_id);
279-
});
280-
i != implicit_param_patterns.end()) {
281-
self_param_id = *i;
282-
}
283-
284-
if (virtual_modifier != SemIR::Function::VirtualModifier::None &&
285-
!self_param_id.has_value()) {
286-
CARBON_DIAGNOSTIC(VirtualWithoutSelf, Error, "virtual class function");
287-
context.emitter().Build(node_id, VirtualWithoutSelf).Emit();
288-
}
289386
// Build the function entity. This will be merged into an existing function if
290387
// there is one, or otherwise added to the function store.
291388
auto function_info =
292-
SemIR::Function{{name_context.MakeEntityWithParamsBase(
293-
name, decl_id, is_extern, introducer.extern_library)},
389+
SemIR::Function{name_context.MakeEntityWithParamsBase(
390+
name, decl_id, is_extern, introducer.extern_library),
294391
{.return_slot_pattern_id = name.return_slot_pattern_id,
295392
.virtual_modifier = virtual_modifier,
296393
.self_param_id = self_param_id}};
297394
if (is_definition) {
298395
function_info.definition_id = decl_id;
299396
}
300-
if (virtual_class_info) {
301-
context.vtable_stack().AddInstId(decl_id);
302-
}
303397

304-
if (name_context.state == DeclNameStack::NameContext::State::Poisoned) {
305-
DiagnosePoisonedName(context, name_context.name_id_for_new_inst(),
306-
name_context.poisoning_loc_id, name_context.loc_id);
307-
} else {
308-
TryMergeRedecl(context, node_id, name_context.name_id,
309-
name_context.prev_inst_id(), name_context.loc_id,
310-
function_decl, function_info, is_definition);
311-
}
398+
TryMergeRedecl(context, node_id, name_context, function_decl, function_info,
399+
is_definition);
312400

313401
// Create a new function if this isn't a valid redeclaration.
314402
if (!function_decl.function_id.has_value()) {
@@ -338,43 +426,12 @@ static auto BuildFunctionDecl(Context& context,
338426
context.emitter().Emit(TokenOnly(node_id), DefinedAbstractFunction);
339427
}
340428

341-
// Check if we need to add this to name lookup, now that the function decl is
342-
// done.
343-
if (name_context.state != DeclNameStack::NameContext::State::Poisoned &&
344-
!name_context.prev_inst_id().has_value()) {
345-
// At interface scope, a function declaration introduces an associated
346-
// function.
347-
auto lookup_result_id = decl_id;
348-
if (parent_scope_inst && !name_context.has_qualifiers) {
349-
if (auto interface_scope =
350-
parent_scope_inst->TryAs<SemIR::InterfaceDecl>()) {
351-
lookup_result_id = BuildAssociatedEntity(
352-
context, interface_scope->interface_id, decl_id);
353-
}
354-
}
429+
// Add to name lookup if needed, now that the decl is built.
430+
MaybeAddToNameLookup(context, name_context, introducer.modifier_set,
431+
parent_scope_inst, decl_id);
355432

356-
context.decl_name_stack().AddName(name_context, lookup_result_id,
357-
introducer.modifier_set.GetAccessKind());
358-
}
359-
360-
if (SemIR::IsEntryPoint(context.sem_ir(), function_decl.function_id)) {
361-
auto return_type_id = function_info.GetDeclaredReturnType(context.sem_ir());
362-
// TODO: Update this once valid signatures for the entry point are decided.
363-
if (function_info.implicit_param_patterns_id.has_value() ||
364-
!function_info.param_patterns_id.has_value() ||
365-
!context.inst_blocks().Get(function_info.param_patterns_id).empty() ||
366-
(return_type_id.has_value() &&
367-
return_type_id != GetTupleType(context, {}) &&
368-
// TODO: Decide on valid return types for `Main.Run`. Perhaps we should
369-
// have an interface for this.
370-
return_type_id != MakeIntType(context, node_id, SemIR::IntKind::Signed,
371-
context.ints().Add(32)))) {
372-
CARBON_DIAGNOSTIC(InvalidMainRunSignature, Error,
373-
"invalid signature for `Main.Run` function; expected "
374-
"`fn ()` or `fn () -> i32`");
375-
context.emitter().Emit(node_id, InvalidMainRunSignature);
376-
}
377-
}
433+
ValidateForEntryPoint(context, node_id, function_decl.function_id,
434+
function_info);
378435

379436
if (!is_definition && context.sem_ir().is_impl() && !is_extern) {
380437
context.definitions_required().push_back(decl_id);

0 commit comments

Comments
 (0)