Files
carbon-lang/toolchain/check/handle_named_constraint.cpp
T
Dana Jansens d6be20641c Use earlier require decls inside a named constraint to provide witnesses for Self (#6915)
Performing a lookup against `Self` inside the definition of the named
constraint leads to cycles, as described in the document [Self
contradictions in Named
Constraints](https://docs.google.com/document/d/17rn2XmME8o2MM4OJqatSVuMa1iYZ1PAgcNrf0PXR9Q4/edit?tab=t.0).
To prevent those cycles, this change introduces a large refactoring of
impl lookup.

The impl lookup done inside eval is reduced to only performing
monomorphization. That is it:
- Only looks for an provides final witnesses.
- Is not allowed to identify the facet type of the query self.
- Returns either a final witness or None (or an error)

The paths for finding non-final witnesses are now done outside of eval,
directly in the initial `LookupImplWitness()` function. If no final
witness it found through eval, the resulting non-final
`LookupImplWitness` instruction witness is returned. It does not produce
cycles to identify the facet type of query self outside of eval, since
that does not result in repeating the identification when resolving
specifics of the named constraint or require decl.

Move the ArrayStack for Context::require_impls_stack into a new class
which tracks a NamedConstraintId (or InterfaceId) for each frame of
RequireImplsIds, so that in type completion we always can find the
correct frame for a given named constraint which is still being defined,
in order to find the RequireImplsIds in the in-progress definition.
2026-03-26 15:26:56 +00:00

227 lines
9.4 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/context.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/interface.h"
#include "toolchain/check/modifiers.h"
#include "toolchain/check/type.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/named_constraint.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
auto HandleParseNode(Context& context,
Parse::NamedConstraintIntroducerId node_id) -> bool {
// This named constraint is potentially generic.
StartGenericDecl(context);
// Create an instruction block to hold the instructions created as part of the
// named constraint signature, such as generic parameters.
context.inst_block_stack().Push();
// Optional modifiers and the name follow.
context.decl_introducer_state_stack().Push<Lex::TokenKind::Constraint>();
context.decl_name_stack().PushScopeAndStartName();
// Push the bracketing node.
context.node_stack().Push(node_id);
return true;
}
static auto BuildNamedConstraintDecl(Context& context,
Parse::AnyNamedConstraintDeclId node_id,
bool is_definition)
-> std::tuple<SemIR::NamedConstraintId, SemIR::InstId> {
auto name = PopNameComponent(context);
auto name_context = context.decl_name_stack().FinishName(name);
context.node_stack()
.PopAndDiscardSoloNodeId<Parse::NodeKind::NamedConstraintIntroducer>();
// TODO: PopSoloNodeId(`template`) if it's present, and track that in the
// NamedConstraint. Or maybe it should be a modifier, like `abstract class`?
// Process modifiers.
auto [_, parent_scope_inst] =
context.name_scopes().GetInstIfValid(name_context.parent_scope_id);
auto introducer =
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Constraint>();
CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst);
LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Access);
auto decl_block_id = context.inst_block_stack().Pop();
// Add the constraint declaration.
auto constraint_decl = SemIR::NamedConstraintDecl{
SemIR::TypeType::TypeId, SemIR::NamedConstraintId::None, decl_block_id};
auto decl_inst_id = AddPlaceholderInst(context, node_id, constraint_decl);
SemIR::NamedConstraint constraint_info = {
name_context.MakeEntityWithParamsBase(name, decl_inst_id,
/*is_extern=*/false,
SemIR::LibraryNameId::None)};
DiagnoseIfGenericMissingExplicitParameters(context, constraint_info);
// Check whether this is a redeclaration.
SemIR::ScopeLookupResult lookup_result =
context.decl_name_stack().LookupOrAddName(
name_context, decl_inst_id, introducer.modifier_set.GetAccessKind());
if (auto existing_decl = TryGetExistingDecl(context, name, lookup_result,
constraint_info, is_definition)) {
auto existing_constraint_decl =
existing_decl->As<SemIR::NamedConstraintDecl>();
constraint_decl.named_constraint_id =
existing_constraint_decl.named_constraint_id;
constraint_decl.type_id = existing_constraint_decl.type_id;
// TODO: If the new declaration is a definition, keep its parameter
// and implicit parameter lists rather than the ones from the
// previous declaration.
auto prev_decl_generic_id = context.named_constraints()
.Get(constraint_decl.named_constraint_id)
.generic_id;
FinishGenericRedecl(context, prev_decl_generic_id);
} else {
// Create a new named constraint if this isn't a valid redeclaration.
constraint_info.generic_id = BuildGenericDecl(context, decl_inst_id);
constraint_decl.named_constraint_id =
context.named_constraints().Add(constraint_info);
if (constraint_info.has_parameters()) {
constraint_decl.type_id = GetGenericNamedConstraintType(
context, constraint_decl.named_constraint_id,
context.scope_stack().PeekSpecificId());
}
}
// Write the completed NamedConstraintDecl instruction.
ReplaceInstBeforeConstantUse(context, decl_inst_id, constraint_decl);
return {constraint_decl.named_constraint_id, decl_inst_id};
}
auto HandleParseNode(Context& context, Parse::NamedConstraintDeclId node_id)
-> bool {
BuildNamedConstraintDecl(context, node_id, /*is_definition=*/false);
context.decl_name_stack().PopScope();
return true;
}
auto HandleParseNode(Context& context,
Parse::NamedConstraintDefinitionStartId node_id) -> bool {
auto [named_constraint_id, decl_inst_id] =
BuildNamedConstraintDecl(context, node_id, /*is_definition=*/true);
auto& constraint_info = context.named_constraints().Get(named_constraint_id);
// TODO: Support for `template constraint`.
bool is_template = false;
// Track that this declaration is the definition.
CARBON_CHECK(!constraint_info.has_definition_started(),
"Can't merge with defined named constraints.");
constraint_info.definition_id = decl_inst_id;
constraint_info.scope_without_self_id = context.name_scopes().Add(
decl_inst_id, SemIR::NameId::None, constraint_info.parent_scope_id);
StartGenericDefinition(context, constraint_info.generic_id);
context.inst_block_stack().Push();
// Enter the constraint-without-self scope, which is used for the Self
// instruction, since it needs to reference the constraint (without-self)
// generic. Self can't reference the constraint-with-self generic since it's a
// parameter to the generic.
context.scope_stack().PushForEntity(
decl_inst_id, constraint_info.scope_without_self_id,
context.generics().GetSelfSpecific(constraint_info.generic_id));
// Declare and introduce `Self`. We model `Self` as a symbolic binding whose
// type is the named constraint, excluding any other interfaces mentioned by
// `require` declarations. This makes it an empty facet type.
SemIR::TypeId self_type_id = GetNamedConstraintType(
context, named_constraint_id,
context.generics().GetSelfSpecific(constraint_info.generic_id));
constraint_info.self_param_id = AddSelfSymbolicBindingToScope(
context, node_id, self_type_id, constraint_info.scope_without_self_id,
is_template);
// Start the declaration of constraint-with-self.
StartGenericDecl(context);
// Push `Self` as a parameter of the constraint-with-self.
context.scope_stack().PushCompileTimeBinding(constraint_info.self_param_id);
// Add the interface-with-self declaration and build the generic for it. This
// captures the `interface_info.self_param_id` as a parameter of the generic.
auto constraint_with_self_decl = SemIR::NamedConstraintWithSelfDecl{
.named_constraint_id = named_constraint_id};
auto decl_with_self_inst_id =
AddPlaceholderInst(context, node_id, constraint_with_self_decl);
auto generic_with_self_id = BuildGenericDecl(context, decl_with_self_inst_id);
constraint_info.generic_with_self_id = generic_with_self_id;
ReplaceInstBeforeConstantUse(context, decl_with_self_inst_id,
constraint_with_self_decl);
constraint_info.scope_with_self_id =
context.name_scopes().Add(decl_with_self_inst_id, SemIR::NameId::None,
constraint_info.scope_without_self_id);
// Start the definition of constraint-with-self.
StartGenericDefinition(context, constraint_info.generic_with_self_id);
// Enter a scope for the constraint-with-self.
context.scope_stack().PushForEntity(
decl_with_self_inst_id, constraint_info.scope_with_self_id,
context.generics().GetSelfSpecific(constraint_info.generic_with_self_id));
constraint_info.body_block_without_self_id =
context.inst_block_stack().PeekOrAdd();
context.inst_block_stack().Push();
constraint_info.body_block_with_self_id =
context.inst_block_stack().PeekOrAdd();
context.require_impls_stack().Push(named_constraint_id);
context.node_stack().Push(node_id, named_constraint_id);
return true;
}
auto HandleParseNode(Context& context,
Parse::NamedConstraintDefinitionId /*node_id*/) -> bool {
auto named_constraint_id =
context.node_stack()
.Pop<Parse::NodeKind::NamedConstraintDefinitionStart>();
// Pop the body_block_with_self.
context.inst_block_stack().Pop();
auto require_impls_block_id = context.require_impls_blocks().Add(
context.require_impls_stack().PeekTop());
context.require_impls_stack().Pop();
auto& constraint_info = context.named_constraints().Get(named_constraint_id);
if (!constraint_info.complete) {
constraint_info.require_impls_block_id = require_impls_block_id;
// TODO: Do something with `alias` statements in the body of the
// constraint.
constraint_info.complete = true;
}
// Finish the definition of constraint-with-self.
FinishGenericDefinition(context, constraint_info.generic_with_self_id);
// Pop the body_block_without_self.
context.inst_block_stack().Pop();
// Finish the definition of interfconstraintace-without-self.
FinishGenericDefinition(context, constraint_info.generic_id);
// The decl_name_stack and scopes are popped by `ProcessNodeIds`.
return true;
}
} // namespace Carbon::Check