mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-15 09:38:31 +00:00
A `where` expression can have symbolic references to `.Self` inside it, but those references do not represent symbolic instructions that should be part of an enclosing generic eval block. They are internal details of the facet type, and as such they are PeriodSelfSymbolic, not Symbolic. The WhereExpr evaluation is careful to produce a Symbolic constant value only if it has symbolic dependencies on the LHS of the `where`, which avoids leaking the symbolic nature of `.Self` into he facet type's constant value. But until now, those PeriodSelfSymbolic insts could still leak into the enclosing eval block if they ended up in an operand of a Call expression. A compile-time Call expr evaluates to a TemplateSymbolic phase constant if it's not concrete, which includes PeriodSelfSymbolic, because we don't want to evaluate the Call expr until the `.Self` gets replaced during identify. But that means the Call constant value, and its operands including PeriodSelfSymbolic constants, all end up in the eval block. We fix this by making Call evaluate to a PeriodSelfSymbolic phase if its input phase is PeriodSelfSymbolic. This keeps the RHS of the `where` expression self-contained. If the facet type is concrete, then it doesn't add any instructions to the enclosing eval block (which may not exist at all in a non-generic context). Regardless, when we identify the facet type and replace `.Self`, the dependent Call expression will be rebuilt as well and it will evaluate as appropriate. We also enclose the `where` expression in its own generic scope. This means that instructions which are not part of the facet type do not end up in an eval block. They may be symbolic instructions, and they will not get updated by a specific when it's applied the enclosing generic anymore. But since they are not part of the facet type their substitution is not required. The new test had an incorrectly elided SpecificImplFunction instruction in the resulting semir, as SubstPeriodSelf was using the location of the input canonical instructions as the location for the results which resulted in an instruction without a location. We fix that by using `loc_id_` in the subst rebuild. Formatting now saw the initialization of a value from an `eval fn` call to a function in a witness table, so it needed to be taught to look through ImplWitnessAssociatedConstant (which is what we find in the witness table).
458 lines
19 KiB
C++
458 lines
19 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/period_self.h"
|
|
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/control_flow.h"
|
|
#include "toolchain/check/convert.h"
|
|
#include "toolchain/check/facet_type.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/impl_lookup.h"
|
|
#include "toolchain/check/inst.h"
|
|
#include "toolchain/check/subst.h"
|
|
#include "toolchain/check/type.h"
|
|
#include "toolchain/check/type_completion.h"
|
|
#include "toolchain/sem_ir/inst.h"
|
|
#include "toolchain/sem_ir/inst_kind.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
auto MakePeriodSelfFacetValue(Context& context, SemIR::LocId loc_id,
|
|
SemIR::TypeId self_type_id) -> SemIR::InstId {
|
|
CARBON_CHECK(context.types().IsFacetTypeOrError(self_type_id));
|
|
auto entity_name_id = context.entity_names().AddCanonical(
|
|
{.name_id = SemIR::NameId::PeriodSelf,
|
|
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
|
|
.is_frozen_period_self = true});
|
|
auto inst_id = AddInst<SemIR::SymbolicBinding>(
|
|
context, loc_id,
|
|
{
|
|
.type_id = self_type_id,
|
|
.entity_name_id = entity_name_id,
|
|
// `None` because there is no equivalent non-symbolic value.
|
|
.value_id = SemIR::InstId::None,
|
|
});
|
|
auto existing = context.scope_stack().LookupOrAddName(
|
|
SemIR::NameId::PeriodSelf, inst_id, ScopeIndex::None,
|
|
IsCurrentPositionReachable(context));
|
|
// Shouldn't have any names in newly created scope.
|
|
CARBON_CHECK(!existing.has_value());
|
|
return inst_id;
|
|
}
|
|
|
|
struct GetAsResult {
|
|
SemIR::SymbolicBinding bind;
|
|
bool is_frozen;
|
|
};
|
|
|
|
static auto TryGetAsPeriodSelf(Context& context, SemIR::InstId inst_id,
|
|
bool canonicalize)
|
|
-> std::optional<GetAsResult> {
|
|
auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);
|
|
if (!const_inst_id.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
auto query_inst_id =
|
|
canonicalize ? GetCanonicalFacet(context, const_inst_id) : inst_id;
|
|
if (auto bind =
|
|
context.insts().TryGetAs<SemIR::SymbolicBinding>(query_inst_id)) {
|
|
const auto& entity_name = context.entity_names().Get(bind->entity_name_id);
|
|
if (entity_name.name_id == SemIR::NameId::PeriodSelf) {
|
|
return {{*bind, entity_name.is_frozen_period_self}};
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
class SubstPeriodSelfCallbacks : public SubstInstCallbacks {
|
|
public:
|
|
explicit SubstPeriodSelfCallbacks(
|
|
Context* context, SemIR::LocId loc_id,
|
|
SemIR::ConstantId period_self_replacement_id,
|
|
SubstPeriodSelfRebuildInst rebuild)
|
|
: SubstInstCallbacks(context),
|
|
loc_id_(loc_id),
|
|
period_self_replacement_id_(period_self_replacement_id),
|
|
rebuild_callback_(rebuild) {}
|
|
|
|
auto Subst(SemIR::InstId& inst_id) -> SubstResult override {
|
|
// We need to recurse into facet types that are concrete to find `.Self`,
|
|
// because the top level instruction being substituted could be such a facet
|
|
// type. So we can't early out if the inst has a concrete constant value.
|
|
if (inst_id == SemIR::TypeType::TypeInstId ||
|
|
inst_id == SemIR::ErrorInst::InstId) {
|
|
return FullySubstituted;
|
|
}
|
|
|
|
// Canonicalization not necessary; we are working with the constant
|
|
// value already, and the query self in a witness is already
|
|
// canonicalized.
|
|
if (auto period_self = TryGetAsPeriodSelf(context(), inst_id,
|
|
/*canonicalize=*/false)) {
|
|
if (!period_self->is_frozen) {
|
|
inst_id = GetReplacement(inst_id);
|
|
}
|
|
return FullySubstituted;
|
|
}
|
|
|
|
return SubstOperandsSkipType;
|
|
}
|
|
|
|
auto Rebuild(SemIR::InstId /*orig_inst_id*/, SemIR::Inst new_inst)
|
|
-> SemIR::InstId override {
|
|
if (rebuild_callback_) {
|
|
if (auto inst_id = rebuild_callback_(new_inst); inst_id.has_value()) {
|
|
return inst_id;
|
|
}
|
|
}
|
|
return RebuildNewInst(loc_id_, new_inst);
|
|
}
|
|
|
|
private:
|
|
auto GetReplacement(SemIR::InstId period_self) -> SemIR::InstId {
|
|
auto period_self_type_id = context().insts().Get(period_self).type_id();
|
|
CARBON_CHECK(context().types().Is<SemIR::FacetType>(period_self_type_id));
|
|
|
|
auto replacement_self_inst_id =
|
|
context().constant_values().GetInstId(period_self_replacement_id_);
|
|
auto replacement_type_id =
|
|
context().insts().Get(replacement_self_inst_id).type_id();
|
|
CARBON_CHECK(context().types().IsFacetTypeOrError(replacement_type_id));
|
|
|
|
// If the replacement has the same type as `.Self`, use it directly.
|
|
if (replacement_type_id == period_self_type_id) {
|
|
return replacement_self_inst_id;
|
|
}
|
|
|
|
// If we have already converted the replacement to the type of `.Self`, use
|
|
// our previous conversion.
|
|
if (period_self_type_id == cached_replacement_type_id_) {
|
|
return cached_replacement_id_;
|
|
}
|
|
|
|
// Convert the replacement facet to the type of `.Self`.
|
|
cached_replacement_id_ = ConvertReplacement(
|
|
replacement_self_inst_id, period_self, period_self_type_id);
|
|
cached_replacement_type_id_ = period_self_type_id;
|
|
return cached_replacement_id_;
|
|
}
|
|
|
|
auto ConvertReplacement(SemIR::InstId replacement_self_inst_id,
|
|
SemIR::InstId period_self_inst_id,
|
|
SemIR::TypeId period_self_type_id) -> SemIR::InstId {
|
|
// TODO: If this replacement is in the query self of a LookupImplWitness
|
|
// instruction, it's just going to strip off the FacetValue and
|
|
// FacetAccessType anyway, so we are wasting effort here. We could just
|
|
// directly return the `replacement_self_inst_id`.
|
|
|
|
// Ensure the replacement is a type, which we will need for the return or to
|
|
// construct FacetValue.
|
|
auto replacement_self_type_inst_id = context().types().GetTypeInstId(
|
|
GetFacetAccessType(context(), replacement_self_inst_id));
|
|
if (period_self_type_id == SemIR::TypeType::TypeId) {
|
|
return replacement_self_type_inst_id;
|
|
}
|
|
|
|
// We have a replacement facet (converted to `type`), but we need different
|
|
// interfaces than we had in the facet's type. We will have to synthesize a
|
|
// symbolic witness for each interface.
|
|
//
|
|
// Why is this okay? The type of `.Self` comes from interfaces that are
|
|
// before it (to the left of it) in the facet type. The replacement for
|
|
// `.Self` will have to impl those interfaces in order to match the facet
|
|
// type, so we know that it is valid to construct these witnesses.
|
|
|
|
auto witnesses = MakeWitnessesForPeriodSelfTypeWithoutLookup(
|
|
context(), loc_id_,
|
|
context().constant_values().Get(replacement_self_inst_id),
|
|
context().constant_values().Get(period_self_inst_id));
|
|
if (witnesses.has_error_value()) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
return context().constant_values().GetInstId(
|
|
EvalOrAddInst<SemIR::FacetValue>(
|
|
context(), loc_id_,
|
|
{
|
|
.type_id = period_self_type_id,
|
|
.type_inst_id = replacement_self_type_inst_id,
|
|
.witnesses_block_id = witnesses.inst_block_id(),
|
|
}));
|
|
}
|
|
|
|
SemIR::LocId loc_id_;
|
|
SemIR::ConstantId period_self_replacement_id_;
|
|
SubstPeriodSelfRebuildInst rebuild_callback_;
|
|
|
|
// The last output of GetReplacement().
|
|
SemIR::InstId cached_replacement_id_ = SemIR::InstId::None;
|
|
// The type of the last output of GetReplacement(). If the type of `.Self`
|
|
// matches, we can reuse the `cached_replacement_id_`.
|
|
SemIR::TypeId cached_replacement_type_id_ = SemIR::TypeId::None;
|
|
};
|
|
|
|
auto SubstPeriodSelf(Context& context, SemIR::LocId loc_id,
|
|
SemIR::ConstantId const_id,
|
|
SemIR::ConstantId period_self_replacement_id,
|
|
SubstPeriodSelfRebuildInst rebuild) -> SemIR::ConstantId {
|
|
// Don't replace `.Self` with itself; that is cyclical.
|
|
//
|
|
// If the types differ, we would try to convert the replacement to a `.Self`
|
|
// of the desired type in `const_id`, which is what we already have, so
|
|
// there's nothing we need to do. But trying to do that conversion recurses
|
|
// when the type of the `.Self` contains a `.Self`.
|
|
if (IsPeriodSelf(context, context.constant_values().GetInstId(
|
|
period_self_replacement_id))) {
|
|
return const_id;
|
|
}
|
|
|
|
SubstPeriodSelfCallbacks callbacks(&context, loc_id,
|
|
period_self_replacement_id, rebuild);
|
|
auto subst_id = SubstInst(
|
|
context, context.constant_values().GetInstId(const_id), callbacks);
|
|
return context.constant_values().Get(subst_id);
|
|
}
|
|
|
|
static auto SubstPeriodSelfInSpecific(
|
|
Context& context, SemIR::LocId loc_id, SemIR::SpecificId specific_id,
|
|
SemIR::ConstantId period_self_replacement_id,
|
|
SubstPeriodSelfRebuildInst rebuild) -> SemIR::SpecificId {
|
|
if (!specific_id.has_value()) {
|
|
return specific_id;
|
|
}
|
|
|
|
const auto& specific = context.specifics().Get(specific_id);
|
|
|
|
// Substitute into the specific without having to construct a FacetType
|
|
// instruction just to hold the specific interface inside a constant id.
|
|
llvm::SmallVector<SemIR::InstId> args(
|
|
context.inst_blocks().Get(specific.args_id));
|
|
for (auto& arg_id : args) {
|
|
auto const_id = context.constant_values().Get(arg_id);
|
|
const_id = SubstPeriodSelf(context, loc_id, const_id,
|
|
period_self_replacement_id, rebuild);
|
|
arg_id = context.constant_values().GetInstId(const_id);
|
|
}
|
|
return MakeSpecific(context, loc_id, specific.generic_id, args);
|
|
}
|
|
|
|
auto SubstPeriodSelf(Context& context, SemIR::LocId loc_id,
|
|
SemIR::SpecificInterface interface,
|
|
SemIR::ConstantId period_self_replacement_id,
|
|
SubstPeriodSelfRebuildInst rebuild)
|
|
-> SemIR::SpecificInterface {
|
|
interface.specific_id =
|
|
SubstPeriodSelfInSpecific(context, loc_id, interface.specific_id,
|
|
period_self_replacement_id, rebuild);
|
|
return interface;
|
|
}
|
|
auto SubstPeriodSelf(Context& context, SemIR::LocId loc_id,
|
|
SemIR::SpecificNamedConstraint constraint,
|
|
SemIR::ConstantId period_self_replacement_id,
|
|
SubstPeriodSelfRebuildInst rebuild)
|
|
-> SemIR::SpecificNamedConstraint {
|
|
constraint.specific_id =
|
|
SubstPeriodSelfInSpecific(context, loc_id, constraint.specific_id,
|
|
period_self_replacement_id, rebuild);
|
|
return constraint;
|
|
}
|
|
|
|
auto SubstPeriodSelfInFacetType(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId self_inst_id,
|
|
SemIR::TypeInstId facet_type_inst_id)
|
|
-> SemIR::TypeInstId {
|
|
auto canon_facet_type_inst_id =
|
|
context.constant_values().GetConstantInstId(facet_type_inst_id);
|
|
if (canon_facet_type_inst_id == SemIR::ErrorInst::TypeInstId) {
|
|
return SemIR::ErrorInst::TypeInstId;
|
|
}
|
|
|
|
auto period_self_replacement_id = context.constant_values().Get(self_inst_id);
|
|
|
|
auto orig_facet_type =
|
|
context.insts().GetAs<SemIR::FacetType>(canon_facet_type_inst_id);
|
|
const auto& orig_declared_facet_type = context.declared_facet_types().Get(
|
|
orig_facet_type.declared_facet_type_id);
|
|
|
|
auto replace_interface = [&](SemIR::SpecificInterface si) {
|
|
return SubstPeriodSelf(context, loc_id, si, period_self_replacement_id);
|
|
};
|
|
auto replace_constraint = [&](SemIR::SpecificNamedConstraint sc) {
|
|
return SubstPeriodSelf(context, loc_id, sc, period_self_replacement_id);
|
|
};
|
|
auto replace_type_impls_interface =
|
|
[&](SemIR::DeclaredFacetType::TypeImplsInterface impls)
|
|
-> SemIR::DeclaredFacetType::TypeImplsInterface {
|
|
auto self = SubstPeriodSelf(context, loc_id,
|
|
context.constant_values().Get(impls.self_type),
|
|
period_self_replacement_id);
|
|
auto interface = SubstPeriodSelf(context, loc_id, impls.specific_interface,
|
|
period_self_replacement_id);
|
|
return {context.constant_values().GetInstId(self), interface};
|
|
};
|
|
auto replace_type_impls_constraint =
|
|
[&](SemIR::DeclaredFacetType::TypeImplsNamedConstraint impls)
|
|
-> SemIR::DeclaredFacetType::TypeImplsNamedConstraint {
|
|
auto self = SubstPeriodSelf(context, loc_id,
|
|
context.constant_values().Get(impls.self_type),
|
|
period_self_replacement_id);
|
|
auto constraint =
|
|
SubstPeriodSelf(context, loc_id, impls.specific_named_constraint,
|
|
period_self_replacement_id);
|
|
return {context.constant_values().GetInstId(self), constraint};
|
|
};
|
|
auto replace_rewrite = [&](SemIR::DeclaredFacetType::RewriteConstraint r)
|
|
-> SemIR::DeclaredFacetType::RewriteConstraint {
|
|
auto lhs = SubstPeriodSelf(context, loc_id,
|
|
context.constant_values().Get(r.lhs_id),
|
|
period_self_replacement_id);
|
|
auto rhs = SubstPeriodSelf(context, loc_id,
|
|
context.constant_values().Get(r.rhs_id),
|
|
period_self_replacement_id);
|
|
return {context.constant_values().GetInstId(lhs),
|
|
context.constant_values().GetInstId(rhs)};
|
|
};
|
|
|
|
SemIR::DeclaredFacetType declared_facet_type;
|
|
llvm::append_range(
|
|
declared_facet_type.extend_constraints,
|
|
llvm::map_range(orig_declared_facet_type.extend_constraints,
|
|
replace_interface));
|
|
llvm::append_range(
|
|
declared_facet_type.extend_named_constraints,
|
|
llvm::map_range(orig_declared_facet_type.extend_named_constraints,
|
|
replace_constraint));
|
|
llvm::append_range(
|
|
declared_facet_type.self_impls_constraints,
|
|
llvm::map_range(orig_declared_facet_type.self_impls_constraints,
|
|
replace_interface));
|
|
llvm::append_range(
|
|
declared_facet_type.self_impls_named_constraints,
|
|
llvm::map_range(orig_declared_facet_type.self_impls_named_constraints,
|
|
replace_constraint));
|
|
llvm::append_range(
|
|
declared_facet_type.type_impls_interfaces,
|
|
llvm::map_range(orig_declared_facet_type.type_impls_interfaces,
|
|
replace_type_impls_interface));
|
|
llvm::append_range(
|
|
declared_facet_type.type_impls_named_constraints,
|
|
llvm::map_range(orig_declared_facet_type.type_impls_named_constraints,
|
|
replace_type_impls_constraint));
|
|
llvm::append_range(
|
|
declared_facet_type.rewrite_constraints,
|
|
llvm::map_range(orig_declared_facet_type.rewrite_constraints,
|
|
replace_rewrite));
|
|
|
|
declared_facet_type.Canonicalize();
|
|
if (declared_facet_type == orig_declared_facet_type) {
|
|
// Nothing was substituted, keep the original instruction.
|
|
//
|
|
// It is noteworthy that we keep the non-canonical instruction here, since
|
|
// it may have a symbolic value (which is attached to a generic, and can be
|
|
// updated by specifics). Returning the canonical facet type instruction
|
|
// would lose the attachment to the generic which would be incorrect.
|
|
return facet_type_inst_id;
|
|
}
|
|
|
|
return AddTypeInst<SemIR::FacetType>(
|
|
context, loc_id,
|
|
{.type_id = SemIR::TypeType::TypeId,
|
|
.declared_facet_type_id =
|
|
context.declared_facet_types().Add(declared_facet_type)});
|
|
}
|
|
|
|
auto IsPeriodSelf(Context& context, SemIR::InstId inst_id, bool canonicalize)
|
|
-> bool {
|
|
return TryGetAsPeriodSelf(context, inst_id, canonicalize).has_value();
|
|
}
|
|
|
|
class FreezeAndThawCallbacks : public SubstInstCallbacks {
|
|
public:
|
|
explicit FreezeAndThawCallbacks(Context* context, bool match_frozen)
|
|
: SubstInstCallbacks(context), match_frozen_(match_frozen) {}
|
|
auto Subst(SemIR::InstId& inst_id) -> SubstResult override {
|
|
if (inst_id == SemIR::TypeType::TypeInstId ||
|
|
inst_id == SemIR::ErrorInst::InstId) {
|
|
return FullySubstituted;
|
|
}
|
|
|
|
if (auto found = cache_.Lookup(inst_id)) {
|
|
inst_id = found.value();
|
|
return FullySubstituted;
|
|
}
|
|
|
|
// No need to canonicalize, Subst will recurse to find it and we want to
|
|
// preserve structure.
|
|
if (auto period_self =
|
|
TryGetAsPeriodSelf(context(), inst_id, /*canonicalize=*/false)) {
|
|
auto entity_name =
|
|
context().entity_names().Get(period_self->bind.entity_name_id);
|
|
if (!!entity_name.is_frozen_period_self == match_frozen_) {
|
|
entity_name.is_frozen_period_self = !entity_name.is_frozen_period_self;
|
|
auto bind = period_self->bind;
|
|
bind.entity_name_id =
|
|
context().entity_names().AddCanonical(entity_name);
|
|
auto subst_id = Rebuild(inst_id, bind);
|
|
cache_.Insert(inst_id, subst_id);
|
|
inst_id = subst_id;
|
|
// The type of `.Self` may contain another `.Self` as in `Z(.Self) where
|
|
// .Self ...` so we would need to SubstOperands still to get to them.
|
|
// But we just leave them as frozen. When identifying a facet type and
|
|
// substituting in, we will replace the `.Self` value here, which means
|
|
// its frozen type is never used.
|
|
return FullySubstituted;
|
|
}
|
|
}
|
|
|
|
return SubstOperands;
|
|
}
|
|
|
|
auto ReuseUnchanged(SemIR::InstId orig_inst_id) -> SemIR::InstId override {
|
|
cache_.Insert(orig_inst_id, orig_inst_id);
|
|
return orig_inst_id;
|
|
}
|
|
|
|
auto Rebuild(SemIR::InstId orig_inst_id, SemIR::Inst new_inst)
|
|
-> SemIR::InstId override {
|
|
auto inserted = cache_.Insert(orig_inst_id, [&] {
|
|
if (context().constant_values().GetConstantInstId(orig_inst_id) ==
|
|
orig_inst_id) {
|
|
return RebuildNewInst(SemIR::LocId(orig_inst_id), new_inst);
|
|
} else {
|
|
return AddInst(context(), SemIR::LocIdAndInst::RuntimeVerified(
|
|
context().sem_ir(),
|
|
SemIR::LocId(orig_inst_id), new_inst));
|
|
}
|
|
});
|
|
return inserted.value();
|
|
}
|
|
|
|
private:
|
|
// If true, we are finding frozen `.Self` and thawing them. If false, then the
|
|
// inverse.
|
|
bool match_frozen_;
|
|
|
|
// Track replacements that have been done, so that we avoid re-evaluating
|
|
// the same instructions repeatedly. Without this, a facet type can create a
|
|
// quadratic number of evaluations, as each one produces many more,
|
|
// repeatedly.
|
|
Map<SemIR::InstId, SemIR::InstId, 16> cache_;
|
|
};
|
|
|
|
auto ThawPeriodSelf(Context& context, SemIR::InstId inst_id) -> SemIR::InstId {
|
|
FreezeAndThawCallbacks callbacks(&context, /*match_frozen=*/true);
|
|
return SubstInst(context, inst_id, callbacks);
|
|
}
|
|
|
|
auto FreezePeriodSelf(Context& context, SemIR::ConstantId const_id)
|
|
-> SemIR::ConstantId {
|
|
FreezeAndThawCallbacks callbacks(&context, /*match_frozen=*/false);
|
|
auto inst_id = SubstInst(
|
|
context, context.constant_values().GetInstId(const_id), callbacks);
|
|
return context.constant_values().Get(inst_id);
|
|
}
|
|
|
|
} // namespace Carbon::Check
|