Skip to content

Commit 3d77c44

Browse files
authored
Compare ImplWitnessAccess into Self as canonical constants (#5883)
This makes all `.Self` references in a facet type canonically the same (which will remain true iff they refer to the same `Self` type in the future), removing the need to do more complex comparisons between them using the EntityName, interface, and index. This allows the comparison of types containing `.Self` references to be done correctly regardless of where the `.Self` appears, as such type expressions will all be canonically equal if they otherwise equal now, regardless of whether they are written in the context where `.Self` could have seen different `Self` facet types. In order to retain access to constraints on a base `.Self` facet type, in the case of applying `where` to an existing facet type, we: - Give the base facet type as a `RequirementBaseFacetType` constraint so that eval of `WhereExpr` can find and copy all the constraints off of it. - Introduce eager/early rewrite constraint resolution, which allows a constraint to eagerly resolve access to earlier rewrite constraints (`where .A = () and .B = .A` is eagerly transformed into `where .A = () and .B = ()`) before the full constraint resolution step. This allows use of rewrite constraints in larger type expressions, such as `where .A = () and .B = C(.A)` and `C` will know that the argument is `()`.
1 parent edcc24e commit 3d77c44

39 files changed

+557
-206
lines changed

toolchain/check/context.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ class Context {
229229
return poisoned_concrete_impl_lookup_queries_;
230230
}
231231

232+
// A stack that tracks the rewrite constraints from a `where` expression being
233+
// checked. The back of the stack is the currently checked `where` expression.
234+
auto rewrites_stack()
235+
-> llvm::SmallVector<Map<SemIR::ConstantId, SemIR::InstId>>& {
236+
return rewrites_stack_;
237+
}
238+
232239
// --------------------------------------------------------------------------
233240
// Directly expose SemIR::File data accessors for brevity in calls.
234241
// --------------------------------------------------------------------------
@@ -423,6 +430,11 @@ class Context {
423430
// results at the end of the file. Any difference is diagnosed.
424431
llvm::SmallVector<PoisonedConcreteImplLookupQuery>
425432
poisoned_concrete_impl_lookup_queries_;
433+
434+
// A map from an ImplWitnessAccess on the LHS of a rewrite constraint to its
435+
// value on the RHS. Used during checking of a `where` expression to allow
436+
// constraints to access values from earlier constraints.
437+
llvm::SmallVector<Map<SemIR::ConstantId, SemIR::InstId>> rewrites_stack_;
426438
};
427439

428440
} // namespace Carbon::Check

toolchain/check/eval.cpp

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "toolchain/check/facet_type.h"
1919
#include "toolchain/check/generic.h"
2020
#include "toolchain/check/import_ref.h"
21+
#include "toolchain/check/name_lookup.h"
2122
#include "toolchain/check/type.h"
2223
#include "toolchain/check/type_completion.h"
2324
#include "toolchain/diagnostics/diagnostic.h"
@@ -637,6 +638,13 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
637638
// canonical instruction, so that in a `WhereExpr` we can work with the
638639
// `ImplWitnessAccess` references to `.Self` on the LHS of the constraints
639640
// rather than the value of the associated constant they reference.
641+
//
642+
// This also implies that we may find `ImplWitnessAccessSubstituted`
643+
// instructions in the LHS and RHS of these constraints, which are preserved
644+
// to maintain them as an unresolved reference to an associated constant, but
645+
// which must be handled gracefully during resolution. They will be replaced
646+
// with the constant value of the `ImplWitnessAccess` below when they are
647+
// substituted with a constant value.
640648
info.rewrite_constraints = orig.rewrite_constraints;
641649
if (!ResolveFacetTypeRewriteConstraints(eval_context.context(), loc_id,
642650
info.rewrite_constraints)) {
@@ -2049,31 +2057,32 @@ auto TryEvalTypedInst<SemIR::WhereExpr>(EvalContext& eval_context,
20492057
auto typed_inst = inst.As<SemIR::WhereExpr>();
20502058

20512059
Phase phase = Phase::Concrete;
2052-
SemIR::TypeId base_facet_type_id =
2053-
eval_context.GetTypeOfInst(typed_inst.period_self_id);
2054-
SemIR::Inst base_facet_inst =
2055-
eval_context.types().GetAsInst(base_facet_type_id);
20562060
SemIR::FacetTypeInfo info = {.other_requirements = false};
20572061

2058-
// `where` provides that the base facet is an error, `type`, or a facet
2059-
// type.
2060-
if (auto facet_type = base_facet_inst.TryAs<SemIR::FacetType>()) {
2061-
info = eval_context.facet_types().Get(facet_type->facet_type_id);
2062-
} else if (base_facet_type_id == SemIR::ErrorInst::TypeId) {
2063-
return SemIR::ErrorInst::ConstantId;
2064-
} else {
2065-
CARBON_CHECK(base_facet_type_id == SemIR::TypeType::TypeId,
2066-
"Unexpected type_id: {0}, inst: {1}", base_facet_type_id,
2067-
base_facet_inst);
2068-
}
2069-
20702062
// Add the constraints from the `WhereExpr` instruction into `info`.
20712063
if (typed_inst.requirements_id.has_value()) {
20722064
auto insts = eval_context.inst_blocks().Get(typed_inst.requirements_id);
20732065
for (auto inst_id : insts) {
2074-
if (auto rewrite =
2075-
eval_context.insts().TryGetAs<SemIR::RequirementRewrite>(
2066+
if (auto base =
2067+
eval_context.insts().TryGetAs<SemIR::RequirementBaseFacetType>(
20762068
inst_id)) {
2069+
if (base->base_type_inst_id == SemIR::ErrorInst::TypeInstId) {
2070+
return SemIR::ErrorInst::ConstantId;
2071+
}
2072+
2073+
if (auto base_facet_type =
2074+
eval_context.insts().TryGetAs<SemIR::FacetType>(
2075+
base->base_type_inst_id)) {
2076+
const auto& base_info =
2077+
eval_context.facet_types().Get(base_facet_type->facet_type_id);
2078+
info.extend_constraints.append(base_info.extend_constraints);
2079+
info.self_impls_constraints.append(base_info.self_impls_constraints);
2080+
info.rewrite_constraints.append(base_info.rewrite_constraints);
2081+
info.other_requirements |= base_info.other_requirements;
2082+
}
2083+
} else if (auto rewrite =
2084+
eval_context.insts().TryGetAs<SemIR::RequirementRewrite>(
2085+
inst_id)) {
20772086
info.rewrite_constraints.push_back(
20782087
{.lhs_id = rewrite->lhs_id, .rhs_id = rewrite->rhs_id});
20792088
} else if (auto impls =

toolchain/check/eval_inst.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,13 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
333333
return ConstantEvalResult::NewSamePhase(inst);
334334
}
335335

336+
auto EvalConstantInst(Context& context,
337+
SemIR::ImplWitnessAccessSubstituted inst)
338+
-> ConstantEvalResult {
339+
return ConstantEvalResult::Existing(
340+
context.constant_values().Get(inst.value_id));
341+
}
342+
336343
auto EvalConstantInst(Context& context,
337344
SemIR::ImplWitnessAssociatedConstant inst)
338345
-> ConstantEvalResult {

toolchain/check/facet_type.cpp

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ namespace Carbon::Check {
2424

2525
auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
2626
SemIR::SpecificId specific_id) -> SemIR::FacetType {
27-
SemIR::FacetTypeId facet_type_id = context.facet_types().Add(
27+
auto info =
2828
SemIR::FacetTypeInfo{.extend_constraints = {{interface_id, specific_id}},
29-
.other_requirements = false});
29+
.other_requirements = false};
30+
info.Canonicalize();
31+
SemIR::FacetTypeId facet_type_id = context.facet_types().Add(info);
3032
return {.type_id = SemIR::TypeType::TypeId, .facet_type_id = facet_type_id};
3133
}
3234

@@ -58,6 +60,16 @@ static auto IncompleteFacetTypeDiagnosticBuilder(
5860
}
5961
}
6062

63+
auto GetImplWitnessAccessWithoutSubstitution(Context& context,
64+
SemIR::InstId inst_id)
65+
-> SemIR::InstId {
66+
if (auto inst = context.insts().TryGetAs<SemIR::ImplWitnessAccessSubstituted>(
67+
inst_id)) {
68+
return inst->impl_witness_access_id;
69+
}
70+
return inst_id;
71+
}
72+
6173
auto InitialFacetTypeImplWitness(
6274
Context& context, SemIR::LocId witness_loc_id,
6375
SemIR::TypeInstId facet_type_inst_id, SemIR::TypeInstId self_type_inst_id,
@@ -123,8 +135,8 @@ auto InitialFacetTypeImplWitness(
123135
}
124136

125137
for (auto rewrite : facet_type_info.rewrite_constraints) {
126-
auto access =
127-
context.insts().GetAs<SemIR::ImplWitnessAccess>(rewrite.lhs_id);
138+
auto access = context.insts().GetAs<SemIR::ImplWitnessAccess>(
139+
GetImplWitnessAccessWithoutSubstitution(context, rewrite.lhs_id));
128140
if (!WitnessQueryMatchesInterface(context, access.witness_id,
129141
interface_to_witness)) {
130142
continue;
@@ -252,6 +264,9 @@ auto AllocateFacetTypeImplWitness(Context& context,
252264
}
253265

254266
namespace {
267+
// TODO: This class should go away, and we should just use the constant value of
268+
// the ImplWitnessAccess as a key in AccessRewriteValues, but that requires
269+
// changing its API to work with InstId instead of ImplWitnessAccess.
255270
struct FacetTypeConstraintValue {
256271
SemIR::EntityNameId entity_name_id;
257272
SemIR::ElementIndex access_index;
@@ -280,32 +295,6 @@ static auto GetFacetTypeConstraintValue(Context& context,
280295
return std::nullopt;
281296
}
282297

283-
// Returns true if two values in a rewrite constraint are equivalent. Two
284-
// `ImplWitnessAccess` instructions that refer to the same associated constant
285-
// through the same facet value are treated as equivalent.
286-
static auto CompareFacetTypeConstraintValues(Context& context,
287-
SemIR::InstId lhs_id,
288-
SemIR::InstId rhs_id) -> bool {
289-
if (lhs_id == rhs_id) {
290-
return true;
291-
}
292-
293-
auto lhs_access = context.insts().TryGetAs<SemIR::ImplWitnessAccess>(lhs_id);
294-
auto rhs_access = context.insts().TryGetAs<SemIR::ImplWitnessAccess>(rhs_id);
295-
if (lhs_access && rhs_access) {
296-
auto lhs_access_value = GetFacetTypeConstraintValue(context, *lhs_access);
297-
auto rhs_access_value = GetFacetTypeConstraintValue(context, *rhs_access);
298-
// We do *not* want to get the evaluated result of `ImplWitnessAccess` here,
299-
// we want to keep them as a reference to an associated constant for the
300-
// resolution phase.
301-
return lhs_access_value && rhs_access_value &&
302-
*lhs_access_value == *rhs_access_value;
303-
}
304-
305-
return context.constant_values().GetConstantInstId(lhs_id) ==
306-
context.constant_values().GetConstantInstId(rhs_id);
307-
}
308-
309298
// A mapping of each associated constant (represented as `ImplWitnessAccess`) to
310299
// its value (represented as an `InstId`). Used to track rewrite constraints,
311300
// with the LHS mapping to the resolved value of the RHS.
@@ -349,9 +338,10 @@ class AccessRewriteValues {
349338

350339
auto SetFullyRewritten(Context& context, Value& value, SemIR::InstId inst_id)
351340
-> void {
352-
CARBON_CHECK(
353-
value.state == BeingRewritten ||
354-
CompareFacetTypeConstraintValues(context, value.inst_id, inst_id));
341+
if (value.state == FullyRewritten) {
342+
CARBON_CHECK(context.constant_values().Get(value.inst_id) ==
343+
context.constant_values().Get(inst_id));
344+
}
355345
value = {FullyRewritten, inst_id};
356346
}
357347

@@ -459,13 +449,22 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
459449
// There's no ImplWitnessAccess that we care about inside this
460450
// instruction.
461451
return SubstResult::FullySubstituted;
462-
} else {
463-
// SubstOperands will result in a Rebuild or ReuseUnchanged callback, so
464-
// push the non-ImplWitnessAccess to get proper bracketing, allowing us
465-
// to pop it in the paired callback.
452+
}
453+
if (auto subst =
454+
context().insts().TryGetAs<SemIR::ImplWitnessAccessSubstituted>(
455+
rhs_inst_id)) {
456+
// The reference to an associated constant was eagerly replaced with the
457+
// value of an earlier rewrite constraint, but may need further
458+
// substitution if it contains an `ImplWitnessAccess`.
459+
rhs_inst_id = subst->value_id;
466460
substs_in_progress_.push_back(rhs_inst_id);
467-
return SubstResult::SubstOperands;
461+
return SubstResult::SubstAgain;
468462
}
463+
// SubstOperands will result in a Rebuild or ReuseUnchanged callback, so
464+
// push the non-ImplWitnessAccess to get proper bracketing, allowing us
465+
// to pop it in the paired callback.
466+
substs_in_progress_.push_back(rhs_inst_id);
467+
return SubstResult::SubstOperands;
469468
}
470469

471470
// If the access is going through a nested `ImplWitnessAccess`, that
@@ -581,8 +580,8 @@ auto ResolveFacetTypeRewriteConstraints(
581580
AccessRewriteValues rewrite_values;
582581

583582
for (auto& constraint : rewrites) {
584-
auto lhs_access =
585-
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
583+
auto lhs_access = context.insts().TryGetAs<SemIR::ImplWitnessAccess>(
584+
GetImplWitnessAccessWithoutSubstitution(context, constraint.lhs_id));
586585
if (!lhs_access) {
587586
continue;
588587
}
@@ -591,8 +590,8 @@ auto ResolveFacetTypeRewriteConstraints(
591590
}
592591

593592
for (auto& constraint : rewrites) {
594-
auto lhs_access =
595-
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
593+
auto lhs_access = context.insts().TryGetAs<SemIR::ImplWitnessAccess>(
594+
GetImplWitnessAccessWithoutSubstitution(context, constraint.lhs_id));
596595
if (!lhs_access) {
597596
continue;
598597
}
@@ -610,33 +609,40 @@ auto ResolveFacetTypeRewriteConstraints(
610609
return false;
611610
}
612611

613-
if (lhs_rewrite_value->state == AccessRewriteValues::FullyRewritten &&
614-
!CompareFacetTypeConstraintValues(context, lhs_rewrite_value->inst_id,
615-
rhs_subst_inst_id)) {
616-
if (lhs_rewrite_value->inst_id != SemIR::ErrorInst::InstId) {
617-
CARBON_DIAGNOSTIC(AssociatedConstantWithDifferentValues, Error,
618-
"associated constant {0} given two different "
619-
"values {1} and {2}",
620-
InstIdAsConstant, InstIdAsConstant, InstIdAsConstant);
621-
// Use inst id ordering as a simple proxy for source ordering, to
622-
// try to name the values in the same order they appear in the facet
623-
// type.
624-
auto source_order1 =
625-
lhs_rewrite_value->inst_id.index < rhs_subst_inst_id.index
626-
? lhs_rewrite_value->inst_id
627-
: rhs_subst_inst_id;
628-
auto source_order2 =
629-
lhs_rewrite_value->inst_id.index >= rhs_subst_inst_id.index
630-
? lhs_rewrite_value->inst_id
631-
: rhs_subst_inst_id;
632-
// TODO: It would be nice to note the places where the values are
633-
// assigned but rewrite constraint instructions are from canonical
634-
// constant values, and have no locations. We'd need to store a
635-
// location along with them in the rewrite constraints.
636-
context.emitter().Emit(loc_id, AssociatedConstantWithDifferentValues,
637-
constraint.lhs_id, source_order1, source_order2);
612+
if (lhs_rewrite_value->state == AccessRewriteValues::FullyRewritten) {
613+
auto rhs_existing_const_id =
614+
context.constant_values().Get(lhs_rewrite_value->inst_id);
615+
auto rhs_subst_const_id =
616+
context.constant_values().Get(rhs_subst_inst_id);
617+
if (rhs_subst_const_id != rhs_existing_const_id) {
618+
if (rhs_existing_const_id != SemIR::ErrorInst::ConstantId) {
619+
CARBON_DIAGNOSTIC(AssociatedConstantWithDifferentValues, Error,
620+
"associated constant {0} given two different "
621+
"values {1} and {2}",
622+
InstIdAsConstant, InstIdAsConstant,
623+
InstIdAsConstant);
624+
// Use inst id ordering as a simple proxy for source ordering, to
625+
// try to name the values in the same order they appear in the facet
626+
// type.
627+
auto source_order1 =
628+
lhs_rewrite_value->inst_id.index < rhs_subst_inst_id.index
629+
? lhs_rewrite_value->inst_id
630+
: rhs_subst_inst_id;
631+
auto source_order2 =
632+
lhs_rewrite_value->inst_id.index >= rhs_subst_inst_id.index
633+
? lhs_rewrite_value->inst_id
634+
: rhs_subst_inst_id;
635+
// TODO: It would be nice to note the places where the values are
636+
// assigned but rewrite constraint instructions are from canonical
637+
// constant values, and have no locations. We'd need to store a
638+
// location along with them in the rewrite constraints.
639+
context.emitter().Emit(loc_id, AssociatedConstantWithDifferentValues,
640+
GetImplWitnessAccessWithoutSubstitution(
641+
context, constraint.lhs_id),
642+
source_order1, source_order2);
643+
}
644+
return false;
638645
}
639-
return false;
640646
}
641647

642648
rewrite_values.SetFullyRewritten(context, *lhs_rewrite_value,
@@ -652,8 +658,8 @@ auto ResolveFacetTypeRewriteConstraints(
652658
for (size_t i = 0; i < keep_size;) {
653659
auto& constraint = rewrites[i];
654660

655-
auto lhs_access =
656-
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
661+
auto lhs_access = context.insts().TryGetAs<SemIR::ImplWitnessAccess>(
662+
GetImplWitnessAccessWithoutSubstitution(context, constraint.lhs_id));
657663
if (!lhs_access) {
658664
++i;
659665
continue;

toolchain/check/facet_type.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ namespace Carbon::Check {
1818
auto FacetTypeFromInterface(Context& context, SemIR::InterfaceId interface_id,
1919
SemIR::SpecificId specific_id) -> SemIR::FacetType;
2020

21+
// Given an ImplWitnessAccessSubstituted, returns the InstId of the
22+
// ImplWitnessAccess. Otherwise, returns the input `inst_id` unchanged.
23+
//
24+
// This must be used when accessing the LHS of a rewrite constraint which has
25+
// not yet been resolved in order to preserve which associated constant is being
26+
// rewritten.
27+
auto GetImplWitnessAccessWithoutSubstitution(Context& context,
28+
SemIR::InstId inst_id)
29+
-> SemIR::InstId;
30+
2131
// Creates a impl witness instruction for a facet type. The facet type is
2232
// required to be complete if `is_definition` is true or the facet type has
2333
// rewrites. Otherwise a placeholder witness is created, and

0 commit comments

Comments
 (0)