Add an interface-with-self generic to each interface and same for constraints (#6667)

Currently each interface has a `Self` facet internally that becomes a
binding to every entity inside the interface: associated constants,
functions, and require decls. Each of these has to be independently
generic as a result. This makes is challenging in extended name lookup
to move into an extended scope of an interface, as we have a specific
for the interface, but the names within require a different specific
that includes a `Self` facet value.

We generalize this relationship by adding a second generic to Interface,
called `generic_with_self`. When we want to work with entities inside
the interface, we move from the interface-without-specific to the
interface-with-self specific by adding a Self to the specific. This is
done independently of any particular entity inside the Interface, as
those entities are now all members of the interface-with-self generic.

Associated constants no longer need a generic of their own, as they do
not have separate generic bindings. Functions retain a generic, but if
the function has no generic arguments, it will have no bindings of its
own now.

Require decls retain a generic so that their specific can be
instantiated separately from the interface. Requiring the interface to
be complete does not require the types in a require decl to be complete
unless it is modified by `extend`. So we allow them to be completed
later by keeping them in a separate generic.

Named constraints look like interfaces and gain the additional inner
generic-with-self, with the same relationship to require decls.

This removes the need for name lookup to perform Substitution of a Self
facet into the extended scope instruction. Instead, the
`SpecificConstant` instruction inserted by a `require` decl is part of
the interface-with-self generic. When looking through a FacetType for
extended scopes, for each interface, we push the scope with the specific
for the interface-with-self. Then the constant value of the
`SpecificConstant` is correctly modified by the provided self
automatically through applying that specific.
This commit is contained in:
Dana Jansens
2026-02-19 16:24:07 +00:00
committed by GitHub
parent e5b094fdad
commit 917a6ea971
419 changed files with 18175 additions and 11940 deletions
+65 -14
View File
@@ -17,6 +17,7 @@
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/name_scope.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
@@ -204,15 +205,47 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context,
return SemIR::ScopeLookupResult::MakeNotFound();
}
// Get the name scope and generic to use for associated entities in `scope`.
// Typically this is None, in which case the input scope should be used, but
// some entities have a separate generic and inner scope used for associated
// entities.
static auto GetAssociatedEntityScope(Context& context,
const SemIR::NameScope& scope)
-> std::pair<SemIR::NameScopeId, SemIR::GenericId> {
auto scope_inst = context.insts().Get(scope.inst_id());
CARBON_KIND_SWITCH(scope_inst) {
case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
const auto& interface =
context.interfaces().Get(interface_decl.interface_id);
return {interface.scope_with_self_id, interface.generic_with_self_id};
}
case CARBON_KIND(SemIR::InterfaceWithSelfDecl _): {
CARBON_FATAL("Expected InterfaceDecl as qualifier scope");
}
case CARBON_KIND(SemIR::NamedConstraintDecl _): {
// ResolveAsScope() does not allow named constraints as a scope qualifier.
CARBON_FATAL(
"Did not expect to find named constraint as scope qualifier");
}
case CARBON_KIND(SemIR::NamedConstraintWithSelfDecl _): {
CARBON_FATAL("Expected NamedConstraintDecl as qualifier scope");
}
default:
return {SemIR::NameScopeId::None, SemIR::GenericId::None};
}
}
// Push a scope corresponding to a name qualifier. For example, for
// `fn Class(T:! type).F(n: i32)` we will push the scope for `Class(T:! type)`
// between the scope containing the declaration of `T` and the scope
// containing the declaration of `n`.
//
// Returns the NameScopeId to use as the parent scope of the next name.
static auto PushNameQualifierScope(Context& context, SemIR::LocId loc_id,
SemIR::InstId scope_inst_id,
SemIR::NameScopeId scope_id,
SemIR::GenericId generic_id,
bool has_error = false) -> void {
bool has_error = false)
-> SemIR::NameScopeId {
// If the qualifier has no parameters, we don't need to keep around a
// parameter scope.
context.scope_stack().PopIfEmpty();
@@ -233,20 +266,35 @@ static auto PushNameQualifierScope(Context& context, SemIR::LocId loc_id,
// providing the definition.
StartGenericDecl(context);
context.scope_stack().PushForEntity(scope_inst_id, scope_id, self_specific_id,
has_error);
const auto& scope = context.name_scopes().Get(scope_id);
context.scope_stack().PushForEntity(scope.inst_id(), scope_id,
self_specific_id, has_error);
// An interface also introduces its 'Self' parameter into scope, despite it
// not being redeclared as part of the qualifier.
if (auto interface_decl =
context.insts().TryGetAs<SemIR::InterfaceDecl>(scope_inst_id)) {
auto& interface = context.interfaces().Get(interface_decl->interface_id);
auto [assoc_entity_scope_id, assoc_entity_generic_id] =
GetAssociatedEntityScope(context, scope);
if (assoc_entity_scope_id.has_value()) {
const auto& assoc_entity_scope =
context.name_scopes().Get(assoc_entity_scope_id);
// InterfaceDecl is the only inst that can be a scope qualifier and that has
// an associated entity scope, the InterfaceWithSelfDecl.
auto interface_decl = context.insts().GetAs<SemIR::InterfaceWithSelfDecl>(
assoc_entity_scope.inst_id());
auto& interface = context.interfaces().Get(interface_decl.interface_id);
// An interface also introduces its 'Self' parameter into the associated
// entity scope, despite it not being redeclared as part of the qualifier.
context.scope_stack().AddCompileTimeBinding();
context.scope_stack().PushCompileTimeBinding(interface.self_param_id);
// Move into the interface-with-self scope.
context.scope_stack().PushForEntity(
assoc_entity_scope.inst_id(), assoc_entity_scope_id,
context.generics().GetSelfSpecific(assoc_entity_generic_id), has_error);
}
// Enter a parameter scope in case the qualified name itself has parameters.
context.scope_stack().PushForSameRegion();
return assoc_entity_scope_id.has_value() ? assoc_entity_scope_id : scope_id;
}
auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
@@ -257,10 +305,9 @@ auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
// Resolve the qualifier as a scope and enter the new scope.
auto [scope_id, generic_id] = ResolveAsScope(name_context, name);
if (scope_id.has_value()) {
PushNameQualifierScope(*context_, name_context.loc_id,
name_context.resolved_inst_id, scope_id, generic_id,
context_->name_scopes().Get(scope_id).has_error());
name_context.parent_scope_id = scope_id;
name_context.parent_scope_id = PushNameQualifierScope(
*context_, name_context.loc_id, scope_id, generic_id,
context_->name_scopes().Get(scope_id).has_error());
} else {
name_context.state = NameContext::State::Error;
}
@@ -435,7 +482,11 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
name_context.resolved_inst_id);
return InvalidResult;
}
return {interface_info.scope_id, interface_info.generic_id};
// The scope and generic of an `I(T:! type)` is the outer
// interface-without-self. That is the generic where parameters appear.
// However when moving to the next qualifier, we need to move to the
// interface-with-self for the associated entity name.
return {interface_info.scope_without_self_id, interface_info.generic_id};
}
case CARBON_KIND(SemIR::Namespace resolved_inst): {
auto scope_id = resolved_inst.name_scope_id;