Files
carbon-lang/toolchain/check/period_self.cpp
T
Dana Jansens 4d1a61de29 Don't consider designators in a nested facet type as constraining the current type (#7139)
The design says:
> We don’t allow a where constraint unless it applies a restriction to
the current type. This means referring to some
[designator](https://docs.carbon-lang.dev/docs/design/generics/details.html#kinds-of-where-constraints),
like .MemberName, or
[.Self](https://docs.carbon-lang.dev/docs/design/generics/details.html#recursive-constraints).
--
https://docs.carbon-lang.dev/docs/design/generics/details.html#constraints-must-use-a-designator

A nested facet type in a constraint does not constrain the current type,
with the exception of the LHS of a nested `where` in an impls
constraint. Diagnose this appropriately by not recursing into unrelated
parts of nested facet types to look for designators.

Before this change, this facet type is accepted:
```carbon
fn F(unused T:! Z where C impls (Y where .Y1 = .Y2)) {}
```

But then no calls to `F` work, since the `.Y1` and `.Y2` designators are
never resolved to anything from the caller, as they do not depend on `T`
in any way.
2026-05-04 16:42:06 +00:00

604 lines
24 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/inst.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.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(self_type_id == SemIR::ErrorInst::TypeId ||
context.types().Is<SemIR::FacetType>(self_type_id));
auto entity_name_id = context.entity_names().AddCanonical({
.name_id = SemIR::NameId::PeriodSelf,
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
});
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;
}
SubstPeriodSelfCallbacks::SubstPeriodSelfCallbacks(
Context* context, SemIR::LocId loc_id,
SemIR::ConstantId period_self_replacement_id, Behaviour behaviour)
: SubstInstCallbacks(context),
loc_id_(loc_id),
period_self_replacement_id_(period_self_replacement_id),
behaviour_(behaviour) {}
auto SubstPeriodSelfCallbacks::Subst(SemIR::InstId& inst_id) -> SubstResult {
// FacetTypes are concrete even if they have `.Self` inside them, but we
// don't recurse into FacetTypes, so we can use this as a base case. This
// avoids infinite recursion on TypeType and ErrorInst.
if (context().constant_values().Get(inst_id).is_concrete()) {
return FullySubstituted;
}
// Don't recurse into nested facet types, even if they are symbolic. Leave
// their `.Self` as is.
if (context().insts().Is<SemIR::FacetType>(inst_id)) {
return FullySubstituted;
}
// Look for implicit use of `.Self` in designators: `.X` is really `.Self.X`.
if (auto access =
context().insts().TryGetAs<SemIR::ImplWitnessAccess>(inst_id)) {
if (auto witness = context().insts().TryGetAs<SemIR::LookupImplWitness>(
access->witness_id)) {
// Canonicalization not necessary; We are working with the constant
// value already, and the query self in a witness is already
// canonicalized.
if (IsPeriodSelf(context(), witness->query_self_inst_id,
/*canonicalize=*/false)) {
auto replacement_id = GetReplacement(witness->query_self_inst_id);
auto new_witness =
Rebuild(access->witness_id,
SemIR::LookupImplWitness{
.type_id = witness->type_id,
.query_self_inst_id = replacement_id,
// Don't replace `.Self` in the interface specific
// here. That is an explicit `.Self` use. We'll
// revisit the instruction for that.
.query_specific_interface_id =
witness->query_specific_interface_id,
});
auto new_access = Rebuild(inst_id, SemIR::ImplWitnessAccess{
.type_id = access->type_id,
.witness_id = new_witness,
.index = access->index,
});
inst_id = new_access;
return SubstAgain;
}
}
}
// Look for explicit use of `.Self`.
if (behaviour_ == Behaviour::All) {
// Canonicalization not necessary; Subst will recurse anyway, so avoid
// extra work for non-matches.
if (IsPeriodSelf(context(), inst_id, /*canonicalize=*/false)) {
inst_id = GetReplacement(inst_id);
return FullySubstituted;
}
}
return SubstOperands;
}
auto SubstPeriodSelfCallbacks::Rebuild(SemIR::InstId orig_inst_id,
SemIR::Inst new_inst) -> SemIR::InstId {
return RebuildNewInst(SemIR::LocId(orig_inst_id), new_inst);
}
auto SubstPeriodSelfCallbacks::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().IsFacetType(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, replacement_type_id, period_self_type_id);
cached_replacement_type_id_ = period_self_type_id;
return cached_replacement_id_;
}
auto SubstPeriodSelfCallbacks::ConvertReplacement(
SemIR::InstId replacement_self_inst_id, SemIR::TypeId replacement_type_id,
SemIR::TypeId period_self_type_id) -> SemIR::InstId {
// TODO: Replace all empty facet types with TypeType.
if (period_self_type_id == GetEmptyFacetType(context())) {
// Convert to an empty facet type (representing TypeType); we don't need
// any witnesses.
return ConvertToValueOfType(context(), loc_id_, replacement_self_inst_id,
period_self_type_id);
}
// We have a facet or a type, but we need more interfaces in the facet 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.
// Make the replacement into a type, which we will need for the FacetValue.
if (context().types().Is<SemIR::FacetType>(replacement_type_id)) {
replacement_self_inst_id = context().constant_values().GetInstId(
EvalOrAddInst<SemIR::FacetAccessType>(
context(), loc_id_,
{.type_id = SemIR::TypeType::TypeId,
.facet_value_inst_id = replacement_self_inst_id}));
}
auto period_self_facet_type =
context().types().GetAs<SemIR::FacetType>(period_self_type_id);
auto identified_period_self_type_id = RequireIdentifiedFacetType(
context(), loc_id_,
context().constant_values().Get(replacement_self_inst_id),
period_self_facet_type, [&](auto& /*builder*/) {
// The facet type containing this `.Self` should have already been
// identified, which would ensure that the type of `.Self` can be
// identified since it can only depend on things to the left of it
// inside the same facet type.
CARBON_FATAL("could not identify type of `.Self`");
});
const auto& identified_period_self_type =
context().identified_facet_types().Get(identified_period_self_type_id);
auto required_impls = identified_period_self_type.required_impls();
llvm::SmallVector<SemIR::InstId> witnesses;
witnesses.reserve(required_impls.size());
for (const auto& req : required_impls) {
witnesses.push_back(context().constant_values().GetInstId(
EvalOrAddInst<SemIR::LookupImplWitness>(
context(), loc_id_,
{.type_id =
GetSingletonType(context(), SemIR::WitnessType::TypeInstId),
.query_self_inst_id =
context().constant_values().GetInstId(req.self_facet_value),
.query_specific_interface_id = context().specific_interfaces().Add(
req.specific_interface)})));
}
return context().constant_values().GetInstId(EvalOrAddInst<SemIR::FacetValue>(
context(), loc_id_,
{
.type_id = period_self_type_id,
.type_inst_id =
context().types().GetAsTypeInstId(replacement_self_inst_id),
.witnesses_block_id = context().inst_blocks().Add(witnesses),
}));
}
auto SubstPeriodSelf(Context& context, SubstPeriodSelfCallbacks& callbacks,
SemIR::ConstantId const_id) -> 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 (auto bind_type =
context.constant_values().TryGetInstAs<SemIR::SymbolicBinding>(
GetCanonicalFacetOrTypeValue(
context, callbacks.period_self_replacement_id()))) {
if (context.entity_names().Get(bind_type->entity_name_id).name_id ==
SemIR::NameId::PeriodSelf) {
return const_id;
}
}
auto subst_id = SubstInst(
context, context.constant_values().GetInstId(const_id), callbacks);
return context.constant_values().Get(subst_id);
}
static auto SubstPeriodSelfInSpecific(Context& context,
SubstPeriodSelfCallbacks& callbacks,
SemIR::SpecificId specific_id)
-> 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, callbacks, const_id);
arg_id = context.constant_values().GetInstId(const_id);
}
return MakeSpecific(context, callbacks.loc_id(), specific.generic_id, args);
}
auto SubstPeriodSelf(Context& context, SubstPeriodSelfCallbacks& callbacks,
SemIR::SpecificInterface interface)
-> SemIR::SpecificInterface {
interface.specific_id =
SubstPeriodSelfInSpecific(context, callbacks, interface.specific_id);
return interface;
}
auto SubstPeriodSelf(Context& context, SubstPeriodSelfCallbacks& callbacks,
SemIR::SpecificNamedConstraint constraint)
-> SemIR::SpecificNamedConstraint {
constraint.specific_id =
SubstPeriodSelfInSpecific(context, callbacks, constraint.specific_id);
return constraint;
}
auto IsPeriodSelf(Context& context, SemIR::InstId inst_id, bool canonicalize)
-> bool {
auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);
if (!const_inst_id.has_value()) {
return false;
}
auto query_inst_id =
canonicalize ? GetCanonicalFacetOrTypeValue(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);
return entity_name.name_id == SemIR::NameId::PeriodSelf;
}
return false;
}
class SearchNonCanonicalForExplicitPeriodSelf : public SubstInstCallbacks {
public:
explicit SearchNonCanonicalForExplicitPeriodSelf(Context* context,
SemIR::LocId* found)
: SubstInstCallbacks(context), found_(found) {}
auto Subst(SemIR::InstId& inst_id) -> SubstResult override {
if (found_->has_value()) {
return FullySubstituted;
}
auto const_inst_id = context().constant_values().GetConstantInstId(inst_id);
if (const_inst_id == SemIR::TypeType::TypeInstId) {
// Recursion base case. TypeType has type TypeType.
return FullySubstituted;
}
if (context().insts().Is<SemIR::FacetType>(const_inst_id)) {
// Don't look for `.Self` in nested facet types, they aren't replaced
// with a facet value and just remain as abstract. WhereExprs evaluate
// to a FacetType but are handled outside of Subst.
return FullySubstituted;
}
if (auto name_ref = context().insts().TryGetAs<SemIR::NameRef>(inst_id)) {
// Canonicalization not necessary; NameRef contains the SymbolicBinding
// directly, not an `as type` conversion.
if (IsPeriodSelf(context(), name_ref->value_id,
/*canonicalize=*/false)) {
// `.Self` does not have a location, the NameRef pointing to it does.
*found_ = SemIR::LocId(inst_id);
return FullySubstituted;
}
}
return SubstOperands;
}
auto Rebuild(SemIR::InstId /*orig_inst_id*/, SemIR::Inst /*new_inst*/)
-> SemIR::InstId override {
CARBON_FATAL();
}
private:
SemIR::LocId* found_;
};
class SearchCanonicalForExplicitPeriodSelf : public SubstInstCallbacks {
public:
explicit SearchCanonicalForExplicitPeriodSelf(Context* context, bool* found)
: SubstInstCallbacks(context), found_(found) {}
auto Subst(SemIR::InstId& inst_id) -> SubstResult override {
if (*found_) {
return FullySubstituted;
}
auto const_inst_id = context().constant_values().GetConstantInstId(inst_id);
if (const_inst_id == SemIR::TypeType::TypeInstId) {
// Recursion base case. TypeType has type TypeType.
return FullySubstituted;
}
if (context().insts().Is<SemIR::FacetType>(const_inst_id)) {
// Don't look for `.Self` in nested facet types, they aren't replaced
// with a facet value and just remain as abstract. WhereExprs evaluate
// to a FacetType but are handled outside of Subst.
return FullySubstituted;
}
if (auto access = context().insts().TryGetAs<SemIR::ImplWitnessAccess>(
const_inst_id)) {
if (auto lookup = context().insts().TryGetAs<SemIR::LookupImplWitness>(
access->witness_id)) {
// Canonicalization not necessary; We are working with the constant
// value already, and the query self in a witness is already
// canonicalized.
if (IsPeriodSelf(context(), lookup->query_self_inst_id,
/*canonicalize=*/false)) {
// An implicit `.Self` in a member designator is always allowed.
return FullySubstituted;
}
}
}
// Canonicalization not necessary; Subst will recurse anyway, so avoid
// extra work for non-matches.
if (IsPeriodSelf(context(), const_inst_id, /*canonicalize=*/false)) {
*found_ = true;
return FullySubstituted;
}
return SubstOperands;
}
auto Rebuild(SemIR::InstId /*orig_inst_id*/, SemIR::Inst /*new_inst*/)
-> SemIR::InstId override {
CARBON_FATAL();
}
private:
bool* found_;
};
static auto ReportAmbiguousPeriodSelf(Context& context, SemIR::LocId loc_id)
-> void {
CARBON_DIAGNOSTIC(AmbiguousPeriodSelf, Error,
"`.Self` is ambiguous after nested `where` in `<type> "
"impls ...` clause.");
context.emitter().Emit(loc_id, AmbiguousPeriodSelf);
}
// Searches a type for a reference to `.Self`. Types are canonical, so they
// only contain canonical values/inststructions, which have no location of
// their own.
//
// The search excludes ImplWitnessAccess into `.Self`, which represents a
// designator like `.X`.
//
// The search does not recurse into FacetTypes, as some can include valid
// references to the top level `.Self`, or abstract `.Self` references that
// are not replaced. FacetTypes are handled by the higher level search.
//
// Returns true if found, and diagnosed.
static auto SearchTypeForPeriodSelf(Context& context, SemIR::LocId loc_id,
SemIR::TypeId type_id) -> bool {
bool found_canonical = false;
SearchCanonicalForExplicitPeriodSelf callbacks(&context, &found_canonical);
auto canonical_inst_id = context.types().GetTypeInstId(type_id);
SubstInst(context, canonical_inst_id, callbacks);
// The type has no locations internally, as it stores canonical
// instructions. If we find any `.Self` reference, we report the entire
// type.
if (found_canonical) {
ReportAmbiguousPeriodSelf(context, loc_id);
return true;
}
return false;
}
// Searches a facet type for a reference to `.Self`. FacetTypes are canonical,
// so they only contain canonical values/inststructions, which have no
// location of their own.
//
// The search excludes ImplWitnessAccess into `.Self`, which represents a
// designator like `.X`.
//
// Returns true if found, and diagnosed.
static auto SearchFacetTypeForPeriodSelf(Context& context, SemIR::LocId loc_id,
SemIR::FacetTypeId facet_type_id)
-> bool {
bool found_canonical = false;
SearchCanonicalForExplicitPeriodSelf callbacks(&context, &found_canonical);
const auto& info = context.facet_types().Get(facet_type_id);
// The LHS of a `WhereExpr` only has extend constraints.
for (auto extend : info.extend_constraints) {
auto block_id = context.specifics().GetArgsOrEmpty(extend.specific_id);
for (auto inst_id : context.inst_blocks().GetOrEmpty(block_id)) {
SubstInst(context, inst_id, callbacks);
}
}
for (auto extend : info.extend_named_constraints) {
auto block_id = context.specifics().GetArgsOrEmpty(extend.specific_id);
for (auto inst_id : context.inst_blocks().GetOrEmpty(block_id)) {
SubstInst(context, inst_id, callbacks);
}
}
// The facet type has no locations internally, as it stores canonical
// instructions. If we find any `.Self` reference, we report the entire
// facet type.
if (found_canonical) {
ReportAmbiguousPeriodSelf(context, loc_id);
return true;
}
return false;
}
// Searches a non-canonical instruction for an explicitly written use of
// `.Self`, which is represented as a NameRef instruction.
//
// Returns true if found, and diagnosed.
static auto SearchNonCanonicalInstForPeriodSelf(Context& context,
SemIR::InstId inst_id) -> bool {
auto found = SemIR::LocId::None;
SearchNonCanonicalForExplicitPeriodSelf callbacks(&context, &found);
SubstInst(context, inst_id, callbacks);
if (found.has_value()) {
ReportAmbiguousPeriodSelf(context, found);
return true;
}
return false;
}
auto FindAndDiagnoseAmbiguousPeriodSelf(Context& context,
SemIR::InstId impls_lhs_id,
SemIR::InstId impls_rhs_id) -> bool {
// Look for errors up front. We don't need to look for them in the rest of
// the function.
if (context.constant_values().Get(impls_lhs_id) ==
SemIR::ErrorInst::ConstantId ||
context.constant_values().Get(impls_rhs_id) ==
SemIR::ErrorInst::ConstantId) {
return false;
}
if (IsPeriodSelf(context, impls_lhs_id)) {
// `.Self impls X where ...` does not restrict any use of `.Self` on the
// RHS of the `where` since the `.Self` on the LHS of `where` did not
// introduce any ambiguity. A `.Self` on the RHS of the `where` applies to
// the same thing as on the LHS of the `impls`.
return false;
}
struct WorkItem {
SemIR::WhereExpr where_expr;
bool search_lhs;
};
llvm::SmallVector<WorkItem> work;
if (auto where_expr =
context.insts().TryGetAs<SemIR::WhereExpr>(impls_rhs_id)) {
work.push_back({.where_expr = *where_expr, .search_lhs = false});
}
while (!work.empty()) {
auto work_item = work.pop_back_val();
// Look in the non-canonical WhereExpr for explicit references to `.Self`,
// which will be considered as ambiguous.
for (auto inst_id : context.inst_blocks().GetOrEmpty(
work_item.where_expr.requirements_id)) {
auto inst = context.insts().Get(inst_id);
CARBON_KIND_SWITCH(inst) {
case CARBON_KIND(SemIR::RequirementBaseFacetType base): {
if (work_item.search_lhs) {
// If the base type is more than a reference to an interface or
// constraint, such as having specific arguments, it will be a
// FacetType instruction.
if (auto facet_type = context.insts().TryGetAs<SemIR::FacetType>(
base.base_type_inst_id)) {
if (SearchFacetTypeForPeriodSelf(
context, SemIR::LocId(base.base_type_inst_id),
facet_type->facet_type_id)) {
return true;
}
}
}
break;
}
case CARBON_KIND(SemIR::RequirementRewrite rewrite): {
if (SearchNonCanonicalInstForPeriodSelf(context, rewrite.lhs_id)) {
return true;
}
if (SearchNonCanonicalInstForPeriodSelf(context, rewrite.rhs_id)) {
return true;
}
break;
}
case CARBON_KIND(SemIR::RequirementEquivalent equiv): {
if (SearchNonCanonicalInstForPeriodSelf(context, equiv.lhs_id)) {
return true;
}
if (SearchNonCanonicalInstForPeriodSelf(context, equiv.rhs_id)) {
return true;
}
break;
}
case CARBON_KIND(SemIR::RequirementImpls impls): {
if (!IsPeriodSelf(context, impls.lhs_id)) {
if (SearchTypeForPeriodSelf(
context, SemIR::LocId(impls.lhs_id),
context.types().GetTypeIdForTypeInstId(impls.lhs_id))) {
return true;
}
}
CARBON_KIND_SWITCH(context.insts().Get(impls.rhs_id)) {
case CARBON_KIND(SemIR::FacetType facet_type): {
// If the RHS of the `impls` is a complex facet type (such as
// when it has specific arguments) but has no `where`, then it
// will be a FacetType instruction.
if (SearchFacetTypeForPeriodSelf(context,
SemIR::LocId(impls.rhs_id),
facet_type.facet_type_id)) {
return true;
}
break;
}
case CARBON_KIND(SemIR::WhereExpr rhs_where_expr): {
// If the RHS of the `impls` contains a `where`, then it will be
// a WhereExpr instruction.
work.push_back(
{.where_expr = rhs_where_expr, .search_lhs = true});
break;
}
default:
// Otherwise, it's a simple facet type, which is just a
// reference to an interface or constraint. There's nowhere to
// look for a
// `.Self`.
break;
}
break;
}
default:
CARBON_FATAL("unexpected inst {0} in WhereExpr requirements block",
inst);
}
}
}
return false;
}
} // namespace Carbon::Check