Skip to content

Commit abd12c1

Browse files
authored
Support extended scopes that are parameterized types (#4524)
* The `extended_scopes` in a `NameScope` were represented by a `NameScopeId`. Replace that with an `InstId` of an instruction returning the type that is extending this name scope. * `Context::LookupQualifiedName` now can take multiple scopes to look in. * `GetAsLookupScope` was moved out of `member_access.cpp` and is now `Context::AppendLookupScopesForConstant` This PR also fixes some existing issues that were revealed as part of writing and testing this PR: * Additional validation and handling of invalid ids. * `extend impl` in a class is not properly imported yet, but at least now it doesn't crash. The change to use an `InstId` also allowed some diagnostics and formatting to be improved. --------- Co-authored-by: Josh L <[email protected]>
1 parent 5e293ad commit abd12c1

Some content is hidden

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

46 files changed

+2056
-209
lines changed

toolchain/check/context.cpp

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,11 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
321321
// Walk the non-lexical scopes and perform lookups into each of them.
322322
for (auto [index, lookup_scope_id, specific_id] :
323323
llvm::reverse(non_lexical_scopes)) {
324-
if (auto non_lexical_result = LookupQualifiedName(
325-
node_id, name_id,
326-
{.name_scope_id = lookup_scope_id, .specific_id = specific_id},
327-
/*required=*/false);
324+
if (auto non_lexical_result =
325+
LookupQualifiedName(node_id, name_id,
326+
LookupScope{.name_scope_id = lookup_scope_id,
327+
.specific_id = specific_id},
328+
/*required=*/false);
328329
non_lexical_result.inst_id.is_valid()) {
329330
return non_lexical_result;
330331
}
@@ -440,11 +441,59 @@ struct ProhibitedAccessInfo {
440441
bool is_parent_access;
441442
};
442443

444+
auto Context::AppendLookupScopesForConstant(
445+
SemIRLoc loc, SemIR::ConstantId base_const_id,
446+
llvm::SmallVector<LookupScope>* scopes) -> bool {
447+
auto base_id = constant_values().GetInstId(base_const_id);
448+
auto base = insts().Get(base_id);
449+
if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
450+
scopes->push_back(
451+
LookupScope{.name_scope_id = base_as_namespace->name_scope_id,
452+
.specific_id = SemIR::SpecificId::Invalid});
453+
return true;
454+
}
455+
if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
456+
TryToDefineType(GetTypeIdForTypeConstant(base_const_id), [&] {
457+
CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
458+
"member access into incomplete class {0}",
459+
InstIdAsType);
460+
return emitter().Build(loc, QualifiedExprInIncompleteClassScope, base_id);
461+
});
462+
auto& class_info = classes().Get(base_as_class->class_id);
463+
scopes->push_back(LookupScope{.name_scope_id = class_info.scope_id,
464+
.specific_id = base_as_class->specific_id});
465+
return true;
466+
}
467+
if (auto base_as_facet_type = base.TryAs<SemIR::FacetType>()) {
468+
TryToDefineType(GetTypeIdForTypeConstant(base_const_id), [&] {
469+
CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
470+
"member access into undefined interface {0}",
471+
InstIdAsType);
472+
return emitter().Build(loc, QualifiedExprInUndefinedInterfaceScope,
473+
base_id);
474+
});
475+
const auto& facet_type_info =
476+
facet_types().Get(base_as_facet_type->facet_type_id);
477+
for (auto interface : facet_type_info.impls_constraints) {
478+
auto& interface_info = interfaces().Get(interface.interface_id);
479+
scopes->push_back(LookupScope{.name_scope_id = interface_info.scope_id,
480+
.specific_id = interface.specific_id});
481+
}
482+
return true;
483+
}
484+
// TODO: Per the design, if `base_id` is any kind of type, then lookup should
485+
// treat it as a name scope, even if it doesn't have members. For example,
486+
// `(i32*).X` should fail because there's no name `X` in `i32*`, not because
487+
// there's no name `X` in `type`.
488+
return false;
489+
}
490+
443491
auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
444-
LookupScope scope, bool required,
492+
llvm::ArrayRef<LookupScope> lookup_scopes,
493+
bool required,
445494
std::optional<AccessInfo> access_info)
446495
-> LookupResult {
447-
llvm::SmallVector<LookupScope> scopes = {scope};
496+
llvm::SmallVector<LookupScope> scopes(lookup_scopes);
448497

449498
// TODO: Support reporting of multiple prohibited access.
450499
llvm::SmallVector<ProhibitedAccessInfo> prohibited_accesses;
@@ -457,6 +506,10 @@ auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
457506
// Walk this scope and, if nothing is found here, the scopes it extends.
458507
while (!scopes.empty()) {
459508
auto [scope_id, specific_id] = scopes.pop_back_val();
509+
if (!scope_id.is_valid()) {
510+
has_error = true;
511+
continue;
512+
}
460513
const auto& name_scope = name_scopes().Get(scope_id);
461514
has_error |= name_scope.has_error;
462515

@@ -479,13 +532,25 @@ auto Context::LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
479532
if (!scope_result_id.is_valid() || is_access_prohibited) {
480533
// If nothing is found in this scope or if we encountered an invalid
481534
// access, look in its extended scopes.
482-
auto extended = name_scope.extended_scopes;
535+
const auto& extended = name_scope.extended_scopes;
483536
scopes.reserve(scopes.size() + extended.size());
484537
for (auto extended_id : llvm::reverse(extended)) {
485-
// TODO: Track a constant describing the extended scope, and substitute
486-
// into it to determine its corresponding specific.
487-
scopes.push_back({.name_scope_id = extended_id,
488-
.specific_id = SemIR::SpecificId::Invalid});
538+
// Substitute into the constant describing the extended scope to
539+
// determine its corresponding specific.
540+
CARBON_CHECK(extended_id.is_valid());
541+
SemIR::ConstantId const_id =
542+
GetConstantValueInSpecific(sem_ir(), specific_id, extended_id);
543+
544+
DiagnosticAnnotationScope annotate_diagnostics(
545+
&emitter(), [&](auto& builder) {
546+
CARBON_DIAGNOSTIC(FromExtendHere, Note,
547+
"declared as an extended scope here");
548+
builder.Note(extended_id, FromExtendHere);
549+
});
550+
if (!AppendLookupScopesForConstant(loc, const_id, &scopes)) {
551+
// TODO: Handle case where we have a symbolic type and instead should
552+
// look in its type.
553+
}
489554
}
490555
is_parent_access |= !extended.empty();
491556
continue;

toolchain/check/context.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,19 @@ class Context {
225225
const SemIR::NameScope& scope)
226226
-> std::pair<SemIR::InstId, SemIR::AccessKind>;
227227

228-
// Performs a qualified name lookup in a specified scope and in scopes that
229-
// it extends, returning the referenced instruction.
228+
// Appends the lookup scopes corresponding to `base_const_id` to `*scopes`.
229+
// Returns `false` if not a scope. On invalid scopes, prints a diagnostic, but
230+
// still updates `*scopes` and returns `true`.
231+
auto AppendLookupScopesForConstant(SemIRLoc loc,
232+
SemIR::ConstantId base_const_id,
233+
llvm::SmallVector<LookupScope>* scopes)
234+
-> bool;
235+
236+
// Performs a qualified name lookup in a specified scopes and in scopes that
237+
// they extend, returning the referenced instruction.
230238
auto LookupQualifiedName(SemIRLoc loc, SemIR::NameId name_id,
231-
LookupScope scope, bool required = true,
239+
llvm::ArrayRef<LookupScope> lookup_scopes,
240+
bool required = true,
232241
std::optional<AccessInfo> access_info = std::nullopt)
233242
-> LookupResult;
234243

toolchain/check/deduce.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class DeductionWorklist {
5050
// Adds a single (param, arg) deduction of a specific.
5151
auto Add(SemIR::SpecificId param, SemIR::SpecificId arg,
5252
bool needs_substitution) -> void {
53+
if (!param.is_valid() || !arg.is_valid()) {
54+
return;
55+
}
5356
auto& param_specific = context_.specifics().Get(param);
5457
auto& arg_specific = context_.specifics().Get(arg);
5558
if (param_specific.generic_id != arg_specific.generic_id) {

toolchain/check/handle_class.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
380380
return true;
381381
}
382382

383-
auto adapted_type_id =
384-
ExprAsType(context, node_id, adapted_type_expr_id).type_id;
383+
auto [adapted_inst_id, adapted_type_id] =
384+
ExprAsType(context, node_id, adapted_type_expr_id);
385385
adapted_type_id = context.AsCompleteType(
386386
adapted_type_id,
387387
[&] {
@@ -405,13 +405,13 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
405405

406406
// Extend the class scope with the adapted type's scope if requested.
407407
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
408-
auto extended_scope_id = SemIR::NameScopeId::Invalid;
408+
auto extended_scope_inst_id = SemIR::InstId::Invalid;
409409
if (adapted_type_id == SemIR::TypeId::Error) {
410410
// Recover by not extending any scope. We instead set has_error to true
411411
// below.
412412
} else if (auto* adapted_class_info =
413413
TryGetAsClass(context, adapted_type_id)) {
414-
extended_scope_id = adapted_class_info->scope_id;
414+
extended_scope_inst_id = adapted_inst_id;
415415
CARBON_CHECK(adapted_class_info->scope_id.is_valid(),
416416
"Complete class should have a scope");
417417
} else {
@@ -420,8 +420,8 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
420420
}
421421

422422
auto& class_scope = context.name_scopes().Get(class_info.scope_id);
423-
if (extended_scope_id.is_valid()) {
424-
class_scope.extended_scopes.push_back(extended_scope_id);
423+
if (extended_scope_inst_id.is_valid()) {
424+
class_scope.extended_scopes.push_back(extended_scope_inst_id);
425425
} else {
426426
class_scope.has_error = true;
427427
}
@@ -448,9 +448,11 @@ struct BaseInfo {
448448

449449
SemIR::TypeId type_id;
450450
SemIR::NameScopeId scope_id;
451+
SemIR::InstId inst_id;
451452
};
452453
constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error,
453-
.scope_id = SemIR::NameScopeId::Invalid};
454+
.scope_id = SemIR::NameScopeId::Invalid,
455+
.inst_id = SemIR::InstId::Invalid};
454456
} // namespace
455457

456458
// Diagnoses an attempt to derive from a final type.
@@ -496,7 +498,9 @@ static auto CheckBaseType(Context& context, Parse::NodeId node_id,
496498

497499
CARBON_CHECK(base_class_info->scope_id.is_valid(),
498500
"Complete class should have a scope");
499-
return {.type_id = base_type_id, .scope_id = base_class_info->scope_id};
501+
return {.type_id = base_type_id,
502+
.scope_id = base_class_info->scope_id,
503+
.inst_id = base_type_inst_id};
500504
}
501505

502506
auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
@@ -560,7 +564,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
560564
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
561565
auto& class_scope = context.name_scopes().Get(class_info.scope_id);
562566
if (base_info.scope_id.is_valid()) {
563-
class_scope.extended_scopes.push_back(base_info.scope_id);
567+
class_scope.extended_scopes.push_back(base_info.inst_id);
564568
} else {
565569
class_scope.has_error = true;
566570
}

toolchain/check/handle_impl.cpp

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "toolchain/check/modifiers.h"
1313
#include "toolchain/check/pattern_match.h"
1414
#include "toolchain/parse/typed_nodes.h"
15+
#include "toolchain/sem_ir/generic.h"
1516
#include "toolchain/sem_ir/ids.h"
1617
#include "toolchain/sem_ir/typed_insts.h"
1718

@@ -173,30 +174,20 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
173174
diag.Emit();
174175
}
175176

176-
auto facet_type = context.types().TryGetAs<SemIR::FacetType>(constraint_id);
177-
if (!facet_type) {
177+
if (!context.types().Is<SemIR::FacetType>(constraint_id)) {
178178
context.TODO(node_id, "extending non-facet-type constraint");
179179
parent_scope.has_error = true;
180180
return;
181181
}
182-
const SemIR::FacetTypeInfo& info =
183-
context.facet_types().Get(facet_type->facet_type_id);
184-
for (auto interface_type : info.impls_constraints) {
185-
auto& interface = context.interfaces().Get(interface_type.interface_id);
186-
if (!interface.is_defined()) {
187-
CARBON_DIAGNOSTIC(ExtendUndefinedInterface, Error,
188-
"`extend impl` requires a definition for interface {0}",
189-
InstIdAsType);
190-
auto diag = context.emitter().Build(node_id, ExtendUndefinedInterface,
191-
constraint_inst_id);
192-
context.NoteUndefinedInterface(interface_type.interface_id, diag);
193-
diag.Emit();
194-
parent_scope.has_error = true;
195-
return;
196-
}
197-
198-
parent_scope.extended_scopes.push_back(interface.scope_id);
199-
}
182+
parent_scope.has_error |= !context.TryToDefineType(constraint_id, [&] {
183+
CARBON_DIAGNOSTIC(ExtendUndefinedInterface, Error,
184+
"`extend impl` requires a definition for facet type {0}",
185+
InstIdAsType);
186+
return context.emitter().Build(node_id, ExtendUndefinedInterface,
187+
constraint_inst_id);
188+
});
189+
190+
parent_scope.extended_scopes.push_back(constraint_inst_id);
200191
}
201192

202193
// Pops the parameters of an `impl`, forming a `NameComponent` with no
@@ -333,6 +324,15 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
333324
// For an `extend impl` declaration, mark the impl as extending this `impl`.
334325
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
335326
auto extend_node = introducer.modifier_node_id(ModifierOrder::Decl);
327+
if (impl_info.generic_id.is_valid()) {
328+
SemIR::TypeId type_id = context.insts().Get(constraint_inst_id).type_id();
329+
constraint_inst_id = context.AddInst<SemIR::SpecificConstant>(
330+
context.insts().GetLocId(constraint_inst_id),
331+
{.type_id = type_id,
332+
.inst_id = constraint_inst_id,
333+
.specific_id =
334+
context.generics().GetSelfSpecific(impl_info.generic_id)});
335+
}
336336
ExtendImpl(context, extend_node, node_id, self_type_node, self_type_id,
337337
name.implicit_params_loc_id, constraint_inst_id,
338338
constraint_type_id);

toolchain/check/import_ref.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,12 +1460,11 @@ class ImportRefResolver {
14601460
context_.insts()
14611461
.GetAs<SemIR::BaseDecl>(new_class.base_id)
14621462
.base_type_id);
1463-
const auto& base_class = context_.classes().Get(
1464-
context_.insts().GetAs<SemIR::ClassType>(base_inst_id).class_id);
1465-
new_scope.extended_scopes.push_back(base_class.scope_id);
1463+
new_scope.extended_scopes.push_back(base_inst_id);
14661464
}
1467-
CARBON_CHECK(new_scope.extended_scopes.size() ==
1468-
import_scope.extended_scopes.size());
1465+
// TODO: `extended_scopes` from `extend impl` are currently not imported.
1466+
// CARBON_CHECK(new_scope.extended_scopes.size() ==
1467+
// import_scope.extended_scopes.size());
14691468
}
14701469

14711470
auto TryResolveTypedInst(SemIR::ClassDecl inst,

0 commit comments

Comments
 (0)