Skip to content

Commit e3b4482

Browse files
authored
Make the GetCanonicalFacetOrTypeValue operation more crisp (#6157)
Previously it performed two kinds of operations, with a boolean parameter to control whether it would unwrap FacetValue or not. This made the function hard to explain as "canonicalization". Now the contract of GetCanonicalFacetOrTypeValue is as follows: 1. For a facet value expression, it returns the canonical value of the facet value. 2. For a `<facet value> as type` it returns the canonical value of the `<facet value>`. 3. For other type expressions, it returns the canonical value of the type. 1 and 2 together collapse together two representations of a facet value (as a FacetType or as a TypeType) into a single canonical value, which is important for constant comparison of facet values where the `as type` is not meant to change the result. This is the case in impl lookups and `.Self` comparisons. The step of unwrapping `FacetValue` is only useful in the constant evaluation of `LookupImplWitness` and is used to collapse *symbolic* queries on `FacetValue(T)` and on `T` down to a single canonical value, since they produce the same result later when `T` is replaced with a facet value or type that can provide a concrete witness. This is now extensively documented in the constant evaluation of `LookupImplWitness`. This change came out of a request/discussion in #6115 (see comment #6115 (comment)).
1 parent 81c2b3b commit e3b4482

File tree

9 files changed

+96
-100
lines changed

9 files changed

+96
-100
lines changed

toolchain/check/check_unit.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,9 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
524524
auto poisoned_queries =
525525
std::exchange(context_.poisoned_concrete_impl_lookup_queries(), {});
526526
for (const auto& poison : poisoned_queries) {
527-
auto witness_result =
528-
EvalLookupSingleImplWitness(context_, poison.loc_id, poison.query,
529-
poison.non_canonical_query_self_inst_id,
530-
/*poison_concrete_results=*/false);
527+
auto witness_result = EvalLookupSingleImplWitness(
528+
context_, poison.loc_id, poison.query, poison.query.query_self_inst_id,
529+
/*poison_concrete_results=*/false);
531530
CARBON_CHECK(witness_result.has_concrete_value());
532531
auto found_witness_id = witness_result.concrete_witness();
533532
if (found_witness_id != poison.impl_witness) {

toolchain/check/context.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,14 @@ class Context {
216216
return impl_lookup_stack_;
217217
}
218218

219-
// A concrete impl lookup query and its result.
219+
// An impl lookup query that resulted in a concrete witness from finding an
220+
// `impl` declaration (not though a facet value), and its result. Used to look
221+
// for conflicting `impl` declarations.
220222
struct PoisonedConcreteImplLookupQuery {
221223
// The location the LookupImplWitness originated from.
222224
SemIR::LocId loc_id;
223225
// The query for a witness of an impl for an interface.
224226
SemIR::LookupImplWitness query;
225-
SemIR::InstId non_canonical_query_self_inst_id;
226227
// The resulting ImplWitness.
227228
SemIR::InstId impl_witness;
228229
};

toolchain/check/convert.cpp

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -828,29 +828,21 @@ static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
828828
static auto DiagnoseConversionFailureToConstraintValue(
829829
Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
830830
SemIR::TypeId target_type_id) -> void {
831-
CARBON_DCHECK(target_type_id == SemIR::TypeType::TypeId ||
832-
context.types().Is<SemIR::FacetType>(target_type_id));
833-
834-
auto type_of_expr_id = context.insts().Get(expr_id).type_id();
835-
CARBON_CHECK(context.types().IsFacetType(type_of_expr_id));
836-
// If the source type is/has a facet value, then we can include its
837-
// FacetType in the diagnostic to help explain what interfaces the
838-
// source type implements.
839-
auto facet_value_inst_id = SemIR::InstId::None;
840-
if (auto facet_access_type =
841-
context.insts().TryGetAs<SemIR::FacetAccessType>(expr_id)) {
842-
facet_value_inst_id = facet_access_type->facet_value_inst_id;
843-
} else if (context.types().Is<SemIR::FacetType>(type_of_expr_id)) {
844-
facet_value_inst_id = expr_id;
845-
}
831+
CARBON_CHECK(context.types().IsFacetType(target_type_id));
832+
833+
// If the source type is/has a facet value (converted with `as type` or
834+
// otherwise), then we can include its `FacetType` in the diagnostic to help
835+
// explain what interfaces the source type implements.
836+
auto const_expr_id = GetCanonicalFacetOrTypeValue(context, expr_id);
837+
auto const_expr_type_id = context.insts().Get(const_expr_id).type_id();
846838

847-
if (facet_value_inst_id.has_value()) {
839+
if (context.types().Is<SemIR::FacetType>(const_expr_type_id)) {
848840
CARBON_DIAGNOSTIC(ConversionFailureFacetToFacet, Error,
849841
"cannot convert type {0} that implements {1} into type "
850842
"implementing {2}",
851-
InstIdAsType, TypeOfInstId, SemIR::TypeId);
843+
InstIdAsType, SemIR::TypeId, SemIR::TypeId);
852844
context.emitter().Emit(loc_id, ConversionFailureFacetToFacet, expr_id,
853-
facet_value_inst_id, target_type_id);
845+
const_expr_type_id, target_type_id);
854846
} else {
855847
CARBON_DIAGNOSTIC(ConversionFailureTypeToFacet, Error,
856848
"cannot convert type {0} into type implementing {1}",

toolchain/check/eval.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2173,14 +2173,13 @@ auto TryEvalTypedInst<SemIR::BindSymbolicName>(EvalContext& eval_context,
21732173

21742174
// Returns whether `const_id` is the same constant facet value as
21752175
// `facet_value_inst_id`.
2176+
//
2177+
// Compares with the canonical facet value of `const_id`, dropping any `as type`
2178+
// conversions.
21762179
static auto IsSameFacetValue(Context& context, SemIR::ConstantId const_id,
21772180
SemIR::InstId facet_value_inst_id) -> bool {
2178-
if (auto facet_access_type = context.insts().TryGetAs<SemIR::FacetAccessType>(
2179-
context.constant_values().GetInstId(const_id))) {
2180-
const_id =
2181-
context.constant_values().Get(facet_access_type->facet_value_inst_id);
2182-
}
2183-
return const_id == context.constant_values().Get(facet_value_inst_id);
2181+
auto canon_const_id = GetCanonicalFacetOrTypeValue(context, const_id);
2182+
return canon_const_id == context.constant_values().Get(facet_value_inst_id);
21842183
}
21852184

21862185
// TODO: Convert this to an EvalConstantInst function. This will require

toolchain/check/eval_inst.cpp

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -241,27 +241,48 @@ auto EvalConstantInst(Context& /*context*/, SemIR::FunctionDecl inst)
241241
auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
242242
SemIR::LookupImplWitness inst) -> ConstantEvalResult {
243243
// The self value is canonicalized in order to produce a canonical
244-
// LookupImplWitness instruction. We save the non-canonical instruction as it
245-
// may be a concrete `FacetValue` that contains a concrete witness.
246-
auto non_canonical_query_self_inst_id = inst.query_self_inst_id;
247-
inst.query_self_inst_id =
248-
GetCanonicalizedFacetOrTypeValue(context, inst.query_self_inst_id);
249-
250-
auto result = EvalLookupSingleImplWitness(
251-
context, SemIR::LocId(inst_id), inst, non_canonical_query_self_inst_id,
252-
/*poison_concrete_results=*/true);
244+
// LookupImplWitness instruction, avoiding multiple constant values for
245+
// `<facet value>` and `<facet value>` as type, which always have the same
246+
// lookup result.
247+
auto self_facet_value_inst_id =
248+
GetCanonicalFacetOrTypeValue(context, inst.query_self_inst_id);
249+
250+
// When we look for a witness in the (facet) type of self, we may get a
251+
// concrete witness from a `FacetValue` (which is `self_facet_value_inst_id`)
252+
// in which case this instruction evaluates to that witness.
253+
//
254+
// If we only get a symbolic witness result though, then this instruction
255+
// evaluates to a `LookupImplWitness`. Since there was no concrete result in
256+
// the `FacetValue`, we don't need to preserve it. By looking through the
257+
// `FacetValue` at the type value it wraps to generate a more canonical value
258+
// for a symbolic `LookupImplWitness`. This makes us produce the same constant
259+
// value for symbolic lookups in `FacetValue(T)` and `T`, since they will
260+
// always have the same lookup result later, when `T` is replaced in a
261+
// specific by something that can provide a concrete witness.
262+
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
263+
self_facet_value_inst_id)) {
264+
inst.query_self_inst_id =
265+
GetCanonicalFacetOrTypeValue(context, facet_value->type_inst_id);
266+
} else {
267+
inst.query_self_inst_id = self_facet_value_inst_id;
268+
}
269+
270+
auto result = EvalLookupSingleImplWitness(context, SemIR::LocId(inst_id),
271+
inst, self_facet_value_inst_id,
272+
/*poison_concrete_results=*/true);
253273
if (!result.has_value()) {
254274
// We use NotConstant to communicate back to impl lookup that the lookup
255275
// failed. This can not happen for a deferred symbolic lookup in a generic
256276
// eval block, since we only add the deferred lookup instruction (being
257277
// evaluated here) to the SemIR if the lookup succeeds.
258278
return ConstantEvalResult::NotConstant;
259279
}
260-
if (!result.has_concrete_value()) {
261-
return ConstantEvalResult::NewSamePhase(inst);
280+
if (result.has_concrete_value()) {
281+
return ConstantEvalResult::Existing(
282+
context.constant_values().Get(result.concrete_witness()));
262283
}
263-
return ConstantEvalResult::Existing(
264-
context.constant_values().Get(result.concrete_witness()));
284+
285+
return ConstantEvalResult::NewSamePhase(inst);
265286
}
266287

267288
auto EvalConstantInst(Context& context, SemIR::InstId inst_id,

toolchain/check/impl_lookup.cpp

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ static auto GetWitnessIdForImpl(Context& context, SemIR::LocId loc_id,
239239
// to the facet value here, and if the query was a FacetAccessType we did the
240240
// same there so they still match.
241241
deduced_self_const_id =
242-
GetCanonicalizedFacetOrTypeValue(context, deduced_self_const_id);
242+
GetCanonicalFacetOrTypeValue(context, deduced_self_const_id);
243243
if (query_self_const_id != deduced_self_const_id) {
244244
return EvalImplLookupResult::MakeNone();
245245
}
@@ -299,34 +299,15 @@ static auto GetWitnessIdForImpl(Context& context, SemIR::LocId loc_id,
299299
}
300300
}
301301

302-
// Unwraps a FacetAccessType to move from a value of type `TypeType` to a facet
303-
// value of type `FacetType` if possible.
304-
//
305-
// Generally `GetCanonicalizedFacetOrTypeValue()` is what you want to call
306-
// instead, as this only does part of that operation, potentially returning a
307-
// non-canonical facet value.
308-
static auto UnwrapFacetAccessType(Context& context, SemIR::InstId inst_id)
309-
-> SemIR::InstId {
310-
if (auto access = context.insts().TryGetAs<SemIR::FacetAccessType>(inst_id)) {
311-
return access->facet_value_inst_id;
312-
}
313-
return inst_id;
314-
}
315-
316302
// Finds a lookup result from `query_self_inst_id` if it is a facet value that
317303
// names the query interface in its facet type. Note that `query_self_inst_id`
318304
// is allowed to be a non-canonical facet value in order to find a concrete
319305
// witness, so it's not referenced as a constant value.
320306
static auto LookupImplWitnessInSelfFacetValue(
321-
Context& context, SemIR::InstId query_self_inst_id,
307+
Context& context, SemIR::InstId self_facet_value_inst_id,
322308
SemIR::SpecificInterface query_specific_interface) -> EvalImplLookupResult {
323-
// Unwrap FacetAccessType without getting the canonical facet value from the
324-
// self value, as we want to preserve the non-canonical `FacetValue`
325-
// instruction which can contain the concrete witness.
326-
query_self_inst_id = UnwrapFacetAccessType(context, query_self_inst_id);
327-
328309
auto facet_type = context.types().TryGetAs<SemIR::FacetType>(
329-
context.insts().Get(query_self_inst_id).type_id());
310+
context.insts().Get(self_facet_value_inst_id).type_id());
330311
if (!facet_type) {
331312
return EvalImplLookupResult::MakeNone();
332313
}
@@ -346,8 +327,8 @@ static auto LookupImplWitnessInSelfFacetValue(
346327
}
347328
auto index = (*it).index();
348329

349-
if (auto facet_value =
350-
context.insts().TryGetAs<SemIR::FacetValue>(query_self_inst_id)) {
330+
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
331+
self_facet_value_inst_id)) {
351332
auto witness_id =
352333
context.inst_blocks().Get(facet_value->witnesses_block_id)[index];
353334
if (context.insts().Is<SemIR::ImplWitness>(witness_id)) {
@@ -546,7 +527,7 @@ static auto GetOrAddLookupImplWitness(Context& context, SemIR::LocId loc_id,
546527
static auto TypeCanDestroy(Context& context,
547528
SemIR::ConstantId query_self_const_id) -> bool {
548529
auto inst = context.insts().Get(context.constant_values().GetInstId(
549-
GetCanonicalizedFacetOrTypeValue(context, query_self_const_id)));
530+
GetCanonicalFacetOrTypeValue(context, query_self_const_id)));
550531

551532
// For facet values, look if the FacetType provides the same.
552533
if (auto facet_type =
@@ -865,14 +846,14 @@ static auto CollectCandidateImplsForQuery(
865846

866847
auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id,
867848
SemIR::LookupImplWitness eval_query,
868-
SemIR::InstId non_canonical_query_self_inst_id,
849+
SemIR::InstId self_facet_value_inst_id,
869850
bool poison_concrete_results)
870851
-> EvalImplLookupResult {
871852
auto query_specific_interface =
872853
context.specific_interfaces().Get(eval_query.query_specific_interface_id);
873854

874855
auto facet_lookup_result = LookupImplWitnessInSelfFacetValue(
875-
context, non_canonical_query_self_inst_id, query_specific_interface);
856+
context, self_facet_value_inst_id, query_specific_interface);
876857
if (facet_lookup_result.has_concrete_value()) {
877858
return facet_lookup_result;
878859
}
@@ -955,8 +936,6 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id,
955936
context.poisoned_concrete_impl_lookup_queries().push_back(
956937
{.loc_id = loc_id,
957938
.query = eval_query,
958-
.non_canonical_query_self_inst_id =
959-
non_canonical_query_self_inst_id,
960939
.impl_witness = result.concrete_witness()});
961940
}
962941
return result;

toolchain/check/impl_lookup.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class [[nodiscard]] EvalImplLookupResult {
104104
// have found that and not caused us to defer lookup to here.
105105
auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id,
106106
SemIR::LookupImplWitness eval_query,
107-
SemIR::InstId non_canonical_query_self_inst_id,
107+
SemIR::InstId self_facet_value_inst_id,
108108
bool poison_concrete_results)
109109
-> EvalImplLookupResult;
110110

toolchain/check/type.cpp

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -238,32 +238,29 @@ auto GetUnboundElementType(Context& context, SemIR::TypeInstId class_type_id,
238238
element_type_id);
239239
}
240240

241-
auto GetCanonicalizedFacetOrTypeValue(Context& context, SemIR::InstId inst_id)
241+
auto GetCanonicalFacetOrTypeValue(Context& context, SemIR::InstId inst_id)
242242
-> SemIR::InstId {
243-
// We can have FacetAccessType of a FacetValue, and a FacetValue of a
244-
// FacetAccessType, but they don't nest indefinitely.
245-
if (auto access = context.insts().TryGetAs<SemIR::FacetAccessType>(inst_id)) {
246-
inst_id = access->facet_value_inst_id;
243+
if (inst_id == SemIR::ErrorInst::InstId) {
244+
return inst_id;
247245
}
248-
if (auto value = context.insts().TryGetAs<SemIR::FacetValue>(inst_id)) {
249-
inst_id = value->type_inst_id;
250246

251-
if (auto access =
252-
context.insts().TryGetAs<SemIR::FacetAccessType>(inst_id)) {
253-
inst_id = access->facet_value_inst_id;
254-
}
255-
}
247+
CARBON_CHECK(
248+
context.types().IsFacetType(context.insts().Get(inst_id).type_id()),
249+
"{0}", context.insts().Get(inst_id).type_id());
256250

257-
CARBON_CHECK(!context.insts().Is<SemIR::FacetAccessType>(inst_id));
258-
CARBON_CHECK(!context.insts().Is<SemIR::FacetValue>(inst_id));
251+
auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);
252+
253+
if (auto access =
254+
context.insts().TryGetAs<SemIR::FacetAccessType>(const_inst_id)) {
255+
return access->facet_value_inst_id;
256+
}
259257

260-
return context.constant_values().GetConstantInstId(inst_id);
258+
return const_inst_id;
261259
}
262260

263-
auto GetCanonicalizedFacetOrTypeValue(Context& context,
264-
SemIR::ConstantId const_id)
261+
auto GetCanonicalFacetOrTypeValue(Context& context, SemIR::ConstantId const_id)
265262
-> SemIR::ConstantId {
266-
return context.constant_values().Get(GetCanonicalizedFacetOrTypeValue(
263+
return context.constant_values().Get(GetCanonicalFacetOrTypeValue(
267264
context, context.constant_values().GetInstId(const_id)));
268265
}
269266

toolchain/check/type.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,26 @@ auto GetPatternType(Context& context, SemIR::TypeId scrutinee_type_id)
107107
auto GetUnboundElementType(Context& context, SemIR::TypeInstId class_type_id,
108108
SemIR::TypeInstId element_type_id) -> SemIR::TypeId;
109109

110-
// Convert a facet value or type value instruction to a canonical facet or type
111-
// value instruction.
110+
// Given a facet value or a type value, get the canonical facet value if
111+
// possible, or return the canonical value of the input type expression if it
112+
// has no canonical facet value.
112113
//
113-
// Type values are already canonical and are returned unchanged, except for
114-
// `FacetAccessType` which is unwrapped to find the facet value it refers to.
114+
// A facet value can be appear in two ways: as a facet value of type
115+
// `FacetType`, or through an `as type` conversion which has type `TypeType` but
116+
// still refers to the original facet value. While both have canonical values of
117+
// their own, in cases that want to work with the facet value when possible,
118+
// this collapses the two cases back together by undoing the `as type`
119+
// conversion.
115120
//
116-
// For facet values, unwraps `FacetValue` instructions to get to an underlying
117-
// canonical type instruction.
118-
auto GetCanonicalizedFacetOrTypeValue(Context& context, SemIR::InstId inst_id)
121+
// This extra canonicalization step is important for constant comparison of
122+
// facet values, when the `as type` conversion is not required to compare as a
123+
// different value.
124+
//
125+
// For type expressions other than `<facet value> as type`, the canonical type
126+
// value is returned.
127+
auto GetCanonicalFacetOrTypeValue(Context& context, SemIR::InstId inst_id)
119128
-> SemIR::InstId;
120-
auto GetCanonicalizedFacetOrTypeValue(Context& context,
121-
SemIR::ConstantId const_id)
129+
auto GetCanonicalFacetOrTypeValue(Context& context, SemIR::ConstantId const_id)
122130
-> SemIR::ConstantId;
123131

124132
} // namespace Carbon::Check

0 commit comments

Comments
 (0)