Files
Richard Smith d4c7ca4221 Support for dependent initialization in templates (#7801)
Add support for deferring initialization as a template action, and
performing the deferred initialization during template instantiation.

This is substantially more complex than other conversion actions, for
two primary reasons:

* The initializer in the generic may have storage arguments as inputs.
We model an initializing expression as having a "slot" where
initialization writes the location that should be initialized by that
initializing expression, and that needs to be an output of the
initialization action.
* Initialization from a tuple or struct literal needs to recurse into
that literal, and the literal will have been spelled in the generic,
meaning we don't have an `InstId` that can be used to name the specific
version of the initializer as input for nested conversions.

These issues are addressed by introducing two new features to the action
machinery:

In addition to `InstAction`, we now have `MultiInstAction`, which is an
action that produces a tuple of instruction values instead of a single
instruction value. Initialization actions produce one instruction for
the final result, which is spliced at the point of initialization, plus
one instruction for each storage argument, which are spliced into the
storage argument slots in the original generic. During initialization,
if we find one of those splices in the storage argument of an
initializing expression, we return the new storage argument back to the
initialization action to be included in the specific, instead of
overwriting the storage argument in the generic.

Actions whose `PerforrmAction` takes a `SpecificId` as input no longer
perform automatic refinement of their operands to specific instructions.
Instead, the action is given control over when and where it performs
that refinement. In `InitializeAction`, we use this freedom to form a
`SpecificInst` for the initializer in the primary output block, and form
a `SpecificInst` for the target in the target block. When detecting
whether we are initializing from a tuple or struct literal, we step over
the `SpecificInst` and track its `SpecificId`, and if necessary create a
new `SpecificInst` wrapping the sub-initializer when we recurse into the
nested element conversion.

Assisted-by: Claude and Gemini via Antigravity
2026-09-24 23:10:18 +00:00

303 lines
12 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/inst.h"
#include "common/vlog.h"
#include "toolchain/check/context.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/import.h"
#include "toolchain/sem_ir/constant.h"
#include "toolchain/sem_ir/expr_info.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst_kind.h"
namespace Carbon::Check {
// Finish producing an instruction. Set its constant value, and register it in
// any applicable instruction lists.
static auto FinishInst(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
DependentInstKind dep_kind = DependentInstKind::None;
// If the instruction has a symbolic constant type, track that we need to
// substitute into it.
if (context.constant_values().DependsOnGenericParameter(
context.types().GetConstantId(inst.type_id()))) {
dep_kind.Add(DependentInstKind::SymbolicType);
}
// If the instruction has a constant value, compute it.
auto const_id = TryEvalInstUnsafe(context, inst_id, inst);
context.constant_values().Set(inst_id, const_id);
if (const_id.is_constant()) {
CARBON_VLOG_TO(context.vlog_stream(), "Constant: {0} -> {1}\n", inst,
context.constant_values().GetInstId(const_id));
// If the constant value is symbolic, track that we need to substitute into
// it.
if (context.constant_values().DependsOnGenericParameter(const_id)) {
dep_kind.Add(DependentInstKind::SymbolicConstant);
}
}
// Template-dependent instructions are handled separately by
// `AddDependentActionInst`.
CARBON_CHECK(
inst.kind().constant_kind() != SemIR::InstConstantKind::InstAction,
"Use AddDependentActionInst to add an action instruction");
// Keep track of dependent instructions.
if (!dep_kind.empty()) {
AttachDependentInstToCurrentGeneric(context,
{.inst_id = inst_id, .kind = dep_kind});
}
}
auto AddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
if (SemIR::GetExprCategory(context.sem_ir(), inst_id) ==
SemIR::ExprCategory::Pattern) {
auto type_id = loc_id_and_inst.inst.type_id();
CARBON_CHECK(type_id == SemIR::ErrorInst::TypeId ||
context.types().Is<SemIR::PatternType>(type_id),
"Unexpected kind for type {0}",
context.types().GetAsInst(type_id));
context.pattern_block_stack().AddInstId(inst_id);
} else {
context.inst_block_stack().AddInstId(inst_id);
}
return inst_id;
}
auto AddInstInNoBlock(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddInst: {0}\n", loc_id_and_inst.inst);
FinishInst(context, inst_id, loc_id_and_inst.inst);
return inst_id;
}
auto AddTemplateConstantInstToEvalBlock(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(),
"AddTemplateConstantInstToEvalBlock: {0}\n",
loc_id_and_inst.inst);
// Set the constant value of this instruction to point back to itself.
auto const_id = context.constant_values().AddSymbolicConstant(
{.inst_id = inst_id,
.generic_id = SemIR::GenericId::None,
.index = SemIR::GenericInstIndex::None,
.dependence = SemIR::ConstantDependence::Template});
context.constant_values().Set(inst_id, const_id);
// Register the instruction to be added to the eval block.
AttachDependentInstToCurrentGeneric(
context, {.inst_id = inst_id, .kind = DependentInstKind::Template});
return inst_id;
}
auto GetOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
CARBON_CHECK(!loc_id_and_inst.inst.kind().has_cleanup());
auto handle_constant_id = [&](SemIR::ConstantId const_id) -> SemIR::InstId {
CARBON_CHECK(const_id.has_value());
// If we didn't produce a constant value for the instruction, we have to add
// the instruction.
if (!const_id.is_constant()) {
return SemIR::InstId::None;
}
if (const_id.is_symbolic()) {
// TODO: Only add this instruction to the eval block, and don't
// re-evaluate it.
return AddInst(context, loc_id_and_inst);
}
CARBON_VLOG_TO(context.vlog_stream(), "GetOrAddInst: constant: {0}\n",
loc_id_and_inst.inst);
return context.constant_values().GetInstId(const_id);
};
// If the instruction is from desugaring, produce its constant value instead
// if possible.
if (loc_id_and_inst.loc_id.is_desugared()) {
switch (loc_id_and_inst.inst.kind().constant_needs_inst_id()) {
case SemIR::InstConstantNeedsInstIdKind::No: {
// Evaluation doesn't need an InstId. Just do it.
auto const_id = TryEvalInstUnsafe(context, SemIR::InstId::None,
loc_id_and_inst.inst);
if (auto result_inst_id = handle_constant_id(const_id);
result_inst_id.has_value()) {
return result_inst_id;
}
break;
}
case SemIR::InstConstantNeedsInstIdKind::DuringEvaluation: {
// Evaluation temporarily needs an InstId. Add one for now.
auto inst_id = AddInstInNoBlock(context, loc_id_and_inst);
auto const_id = context.constant_values().Get(inst_id);
if (auto result_inst_id = handle_constant_id(const_id);
result_inst_id.has_value()) {
// TODO: We didn't end up needing the `inst_id` instruction. Consider
// removing it from `insts` if it's still the most recently added
// instruction.
CARBON_CHECK(result_inst_id != inst_id);
return result_inst_id;
}
context.inst_block_stack().AddInstId(inst_id);
return inst_id;
}
case SemIR::InstConstantNeedsInstIdKind::Permanent: {
// Evaluation needs a permanent InstId. Add the instruction.
break;
}
}
}
// TODO: For an implicit instruction, this reattempts evaluation.
return AddInst(context, loc_id_and_inst);
}
auto EvalOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::ConstantId {
CARBON_CHECK(!loc_id_and_inst.inst.kind().has_cleanup());
switch (loc_id_and_inst.inst.kind().constant_needs_inst_id()) {
case SemIR::InstConstantNeedsInstIdKind::No: {
// Evaluation doesn't need an InstId. Just do it.
return TryEvalInstUnsafe(context, SemIR::InstId::None,
loc_id_and_inst.inst);
}
case SemIR::InstConstantNeedsInstIdKind::DuringEvaluation: {
// Evaluation temporarily needs an InstId. Add one for now. We add the
// instruction outside of a block, and never call `FinishInst` for this
// non-canonical instruction. This means it never gets attached to the
// constant value, and is not added to any enclosing generic context's
// eval block.
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddInst: {0}\n",
loc_id_and_inst.inst);
// TODO: Consider removing `inst_id` from `insts` if it's still the most
// recently added instruction.
return TryEvalInstUnsafe(context, inst_id, loc_id_and_inst.inst);
}
case SemIR::InstConstantNeedsInstIdKind::Permanent: {
// Evaluation needs a permanent InstId. Add the instruction.
auto inst_id = AddInst(context, loc_id_and_inst);
return context.constant_values().Get(inst_id);
}
}
}
auto AddPlaceholderInstInNoBlock(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = context.sem_ir().insts().AddInNoBlock(loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "AddPlaceholderInst: {0}\n",
loc_id_and_inst.inst);
context.constant_values().Set(inst_id, SemIR::ConstantId::None);
return inst_id;
}
auto AddPlaceholderImportedInstInNoBlock(Context& context,
SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddPlaceholderInstInNoBlock(context, loc_id_and_inst);
context.imports().push_back(inst_id);
return inst_id;
}
auto AddPlaceholderInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = AddPlaceholderInstInNoBlock(context, loc_id_and_inst);
context.inst_block_stack().AddInstId(inst_id);
return inst_id;
}
auto ReplaceLocIdAndInstBeforeConstantUse(Context& context,
SemIR::InstId inst_id,
SemIR::LocIdAndInst loc_id_and_inst)
-> void {
context.sem_ir().insts().SetLocIdAndInst(inst_id, loc_id_and_inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
loc_id_and_inst.inst);
FinishInst(context, inst_id, loc_id_and_inst.inst);
}
auto ReplaceInstBeforeConstantUse(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
context.sem_ir().insts().Set(inst_id, inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
inst);
FinishInst(context, inst_id, inst);
}
auto ReplaceInstPreservingConstantValue(Context& context, SemIR::InstId inst_id,
SemIR::Inst inst) -> void {
// Check that the type didn't change: a change of type will change the
// constant value. Replace the type with the attached type.
auto old_type_id = context.insts().GetAttachedType(inst_id);
CARBON_CHECK(context.types().GetUnattachedType(old_type_id) == inst.type_id(),
"Given wrong type for replacement instruction");
inst.SetType(old_type_id);
// Update the instruction.
context.sem_ir().insts().Set(inst_id, inst);
CARBON_VLOG_TO(context.vlog_stream(), "ReplaceInst: {0} -> {1}\n", inst_id,
inst);
// Check the constant value didn't change.
auto old_const_id = context.constant_values().Get(inst_id);
auto new_const_id = TryEvalInstUnsafe(context, inst_id, inst);
CARBON_CHECK(old_const_id == new_const_id);
}
auto SetNamespaceNodeId(Context& context, SemIR::InstId inst_id,
Parse::NodeId node_id) -> void {
context.sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id));
}
auto WrapInstForSpecific(Context& context, SemIR::LocId loc_id,
SemIR::InstId inst_id, SemIR::SpecificId specific_id)
-> WrapInstForSpecificResult {
auto type_id =
SemIR::GetTypeOfInstInSpecific(context.sem_ir(), specific_id, inst_id);
CARBON_CHECK(type_id.has_value(), "Missing type for {0}",
context.insts().Get(inst_id));
// If `inst_id` has a constant value that depends on its specific, store the
// specific too.
if (specific_id.has_value() &&
context.constant_values().Get(inst_id).is_symbolic()) {
inst_id = AddInst<SemIR::SpecificConstant>(
context, loc_id,
{.type_id = type_id, .inst_id = inst_id, .specific_id = specific_id});
}
return {.inst_id = inst_id, .type_id = type_id};
}
auto BuildNameRef(Context& context, SemIR::LocId loc_id, SemIR::NameId name_id,
SemIR::InstId inst_id, SemIR::SpecificId specific_id)
-> SemIR::InstId {
auto [new_inst_id, type_id] =
WrapInstForSpecific(context, loc_id, inst_id, specific_id);
return AddInst<SemIR::NameRef>(
context, loc_id,
{.type_id = type_id, .name_id = name_id, .value_id = new_inst_id});
}
} // namespace Carbon::Check