Skip to content

Commit 105618e

Browse files
authored
Resolve nested accesses in rewrite constraints (#5872)
A rewrite constraint like `.X = .Y.Z and .Y = .Self and .Z = ()` has a nested `ImplWitnessAccess` `.Y.Z` (technically `(.Self.Y).Z`). The inner access `.Self.Y` needs to be resolved (in this case to `.Self`) before the outer `???.Z` can be resolved as `.Self.Z` which is `()`.
1 parent f0cff61 commit 105618e

File tree

6 files changed

+156
-34
lines changed

6 files changed

+156
-34
lines changed

toolchain/check/eval.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -635,10 +635,7 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
635635
// Rewrite constraints are resolved first before replacing them with their
636636
// canonical instruction, so that in a `WhereExpr` we can work with the
637637
// `ImplWitnessAccess` references to `.Self` on the LHS of the constraints
638-
// rather than the value of the associated constant they reference. It also
639-
// ensures that any errors inserted during resolution will be seen by
640-
// GetConstantValueIgnoringPeriodSelf() which will update the phase
641-
// accordingly.
638+
// rather than the value of the associated constant they reference.
642639
info.rewrite_constraints = orig.rewrite_constraints;
643640
if (!ResolveFacetTypeRewriteConstraints(eval_context.context(), loc_id,
644641
info.rewrite_constraints)) {

toolchain/check/facet_type.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,18 @@ class AccessRewriteValues {
329329

330330
auto InsertNotRewritten(Context& context, SemIR::ImplWitnessAccess access,
331331
SemIR::InstId inst_id) -> void {
332-
map_.insert({GetKey(context, access), {NotRewritten, inst_id}});
332+
map_.insert({*GetKey(context, access), {NotRewritten, inst_id}});
333333
}
334334

335335
// Finds and returns a pointer into the cache for a given ImplWitnessAccess.
336336
// The pointer will be invalidated by mutating the cache. Returns `nullptr`
337337
// if `access` is not found.
338338
auto FindRef(Context& context, SemIR::ImplWitnessAccess access) -> Value* {
339-
auto it = map_.find(GetKey(context, access));
339+
auto key = GetKey(context, access);
340+
if (!key) {
341+
return nullptr;
342+
}
343+
auto it = map_.find(*key);
340344
if (it == map_.end()) {
341345
return nullptr;
342346
}
@@ -391,8 +395,12 @@ class AccessRewriteValues {
391395
}
392396
};
393397

394-
auto GetKey(Context& context, SemIR::ImplWitnessAccess access) -> Key {
395-
return {*GetFacetTypeConstraintValue(context, access)};
398+
// Returns a key for the `access` to an associated context if the access is
399+
// through a facet value. If the access it through another `ImplWitnessAccess`
400+
// then no key is able to be made.
401+
auto GetKey(Context& context, SemIR::ImplWitnessAccess access)
402+
-> std::optional<Key> {
403+
return GetFacetTypeConstraintValue(context, access);
396404
}
397405

398406
// Try avoid heap allocations in the common case where there are a small
@@ -466,6 +474,18 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
466474
}
467475
}
468476

477+
// If the access is going through a nested `ImplWitnessAccess`, that
478+
// access needs to be resolved to a facet value first. If it can't be
479+
// resolved then the outer one can not be either.
480+
if (auto lookup = context().insts().TryGetAs<SemIR::LookupImplWitness>(
481+
rhs_access->witness_id)) {
482+
if (context().insts().Is<SemIR::ImplWitnessAccess>(
483+
lookup->query_self_inst_id)) {
484+
substs_in_progress_.push_back(rhs_inst_id);
485+
return SubstResult::SubstOperandsAndRetry;
486+
}
487+
}
488+
469489
auto* rewrite_value = rewrite_values_->FindRef(context(), *rhs_access);
470490
if (!rewrite_value) {
471491
// The RHS refers to an associated constant for which there is no rewrite
@@ -510,9 +530,9 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
510530
auto subst_inst_id = substs_in_progress_.pop_back_val();
511531
if (auto access = context().insts().TryGetAs<SemIR::ImplWitnessAccess>(
512532
subst_inst_id)) {
513-
auto* rewrite_value = rewrite_values_->FindRef(context(), *access);
514-
CARBON_CHECK(rewrite_value);
515-
rewrite_values_->SetFullyRewritten(context(), *rewrite_value, inst_id);
533+
if (auto* rewrite_value = rewrite_values_->FindRef(context(), *access)) {
534+
rewrite_values_->SetFullyRewritten(context(), *rewrite_value, inst_id);
535+
}
516536
}
517537
return inst_id;
518538
}
@@ -521,10 +541,10 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
521541
auto subst_inst_id = substs_in_progress_.pop_back_val();
522542
if (auto access = context().insts().TryGetAs<SemIR::ImplWitnessAccess>(
523543
subst_inst_id)) {
524-
auto* rewrite_value = rewrite_values_->FindRef(context(), *access);
525-
CARBON_CHECK(rewrite_value);
526-
rewrite_values_->SetFullyRewritten(context(), *rewrite_value,
527-
orig_inst_id);
544+
if (auto* rewrite_value = rewrite_values_->FindRef(context(), *access)) {
545+
rewrite_values_->SetFullyRewritten(context(), *rewrite_value,
546+
orig_inst_id);
547+
}
528548
}
529549
return orig_inst_id;
530550
}

toolchain/check/subst.cpp

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -312,26 +312,33 @@ auto SubstInst(Context& context, SemIR::InstId inst_id,
312312
while (index != -1) {
313313
auto& item = worklist[index];
314314

315-
if (item.is_repeated) {
316-
// Pop the copy of the repeated item when we get back to the repeated
317-
// item, and steal any work that was done there so we don't have to
318-
// Subst() the repeated item again. The pop does not reallocate the
319-
// worklist so does not invalidate `item`.
320-
item.inst_id = worklist.Pop();
321-
}
322-
323315
if (item.is_expanded) {
324316
// Rebuild this item if necessary. Note that this might pop items from the
325317
// worklist but does not reallocate, so does not invalidate `item`.
326-
item.inst_id = Rebuild(context, worklist, item.inst_id, callbacks);
327-
index = item.next_index;
328-
continue;
318+
auto old_inst_id = std::exchange(
319+
item.inst_id, Rebuild(context, worklist, item.inst_id, callbacks));
320+
if (item.is_repeated && old_inst_id != item.inst_id) {
321+
// SubstOperandsAndRetry was returned for the item, and the instruction
322+
// was rebuilt from new operands, so go through Subst() again. Note that
323+
// we've already called Rebuild so we don't want to leave this item as
324+
// repeated, and call back to ReuseUnchanged for it again later unless
325+
// the next call to Subst() asks for that.
326+
item.is_expanded = false;
327+
item.is_repeated = false;
328+
} else {
329+
index = item.next_index;
330+
continue;
331+
}
329332
}
330333

331334
if (item.is_repeated) {
335+
// SubstAgain was returned for the item, and the result of that Subst() is
336+
// at the back of the worklist, which we pop. Note that popping from the
337+
// worklist does not reallocate, so does not invalidate `item`.
338+
//
332339
// When Subst returns SubstAgain, we must call back to Rebuild or
333340
// ReuseUnchanged for that work item.
334-
item.inst_id = callbacks.ReuseUnchanged(item.inst_id);
341+
item.inst_id = callbacks.ReuseUnchanged(worklist.Pop());
335342
index = item.next_index;
336343
continue;
337344
}
@@ -357,6 +364,9 @@ auto SubstInst(Context& context, SemIR::InstId inst_id,
357364
}
358365
case SubstInstCallbacks::SubstResult::SubstOperands:
359366
break;
367+
case SubstInstCallbacks::SubstResult::SubstOperandsAndRetry:
368+
item.is_repeated = true;
369+
break;
360370
}
361371

362372
// Extract the operands of this item into the worklist. Note that this

toolchain/check/subst.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,28 @@ class SubstInstCallbacks {
3232
// Attempt to substitute again on the resulting instruction, acting like
3333
// recursion on the instruction itself.
3434
SubstAgain,
35+
// Attempt to substitute into the operands of the instruction. If the InstId
36+
// returned from Rebuild or ReuseUnchanged differs from the input (typically
37+
// because some operand in the instruction changed), then the new
38+
// instruction will be given to `Subst` again afterward. This allows for the
39+
// uncommon case of substituting from the inside out.
40+
SubstOperandsAndRetry,
3541
};
3642

3743
// Performs any needed substitution into an instruction. The instruction ID
38-
// should be updated as necessary to represent the new instruction. Returns
39-
// FullySubstituted if the resulting instruction ID is fully-substituted.
40-
// Return SubstOperands if substitution may be needed into operands of the
41-
// instruction, or SubstAgain if the replaced instruction itself should have
42-
// substitution applied to it again. When SubstOperands or SubstAgain is
43-
// returned, it results in a call back to Rebuild or ReuseUnchanged when that
44-
// instruction is done being substituted.
44+
// should be updated as necessary to represent the new instruction.
45+
//
46+
// Return FullySubstituted if the resulting instruction ID is
47+
// fully-substituted. Return SubstOperands if substitution may be needed into
48+
// operands of the instruction, or SubstAgain if the replaced instruction
49+
// itself should have substitution applied to it again. Return
50+
// SubstOperandsAndRetry to recurse on the instructions operands and then
51+
// substitute the resulting instruction afterward, if the instruction is
52+
// replaced by a new one (typically due to Rebuild when the operands changed).
53+
//
54+
// When SubstOperands, SubstAgain, or SubstOperandsAndRetry is returned, it
55+
// results in a call back to Rebuild or ReuseUnchanged when that instruction's
56+
// substitution step is complete.
4557
virtual auto Subst(SemIR::InstId& inst_id) -> SubstResult = 0;
4658

4759
// Rebuilds the type of an instruction from the substituted type instruction.

toolchain/check/testdata/facet/facet_assoc_const.carbon

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,59 @@ fn J(T:! Z where .T0 = .T1 and .T1 = (.T2, .T3) and .T2 = .T4 and .T4 = .T5 and
601601
return ((), ());
602602
}
603603

604+
// --- indirection_through_self_rhs.carbon
605+
library "[[@TEST_NAME]]";
606+
607+
interface I {
608+
let I1:! type;
609+
let I2:! type;
610+
}
611+
612+
interface J {
613+
let J1:! I;
614+
}
615+
616+
// The value of .I1 is (), but to know that requires resolving .J1 first then
617+
// .J1.I2.
618+
fn F(T:! I & J where .J1 = .Self and .I1 = .J1.I2 and .I2 = ()) -> T.I1 {
619+
return ();
620+
}
621+
622+
// --- indirection_through_not_self_rhs.carbon
623+
library "[[@TEST_NAME]]";
624+
625+
interface I {
626+
let I1:! type;
627+
let I2:! type;
628+
}
629+
630+
interface J {
631+
let J1:! I;
632+
}
633+
634+
// The value of .I1 is (), but to know that requires resolving .J1 first then
635+
// .J1.I2.
636+
fn F(U:! I where .I2 = (), T:! I & J where .J1 = U and .I1 = .J1.I2) -> T.I1 {
637+
return ();
638+
}
639+
640+
// --- indirection_through_unresolved_access_rhs.carbon
641+
library "[[@TEST_NAME]]";
642+
643+
interface I {
644+
let I1:! type;
645+
let I2:! type;
646+
}
647+
648+
interface J {
649+
let J1:! I;
650+
}
651+
652+
// If we assume the nested `.J1` access will resolve to a facet value, we may
653+
// loop forever trying to resolve the `.I2` access. We should gracefully accept
654+
// that it does not resolve further.
655+
fn F(T:! I & J where .I1 = .J1.I2) {}
656+
604657
// CHECK:STDOUT: --- fail_cycle.carbon
605658
// CHECK:STDOUT:
606659
// CHECK:STDOUT: constants {

toolchain/check/testdata/facet/nested_facet_types.carbon

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ interface Z {
5454

5555
fn F(FF:! ((Z where .T = .U and .U = .V) where .T = .U and .U = .V) where .T = .U and .U = .V) {}
5656

57+
// --- fail_todo_nested_facet_types_same_associated_in_generic_parameter.carbon
58+
library "[[@TEST_NAME]]";
59+
60+
interface Z {
61+
let T:! type;
62+
let U:! type;
63+
}
64+
class C(T:! type) {}
65+
66+
// CHECK:STDERR: fail_todo_nested_facet_types_same_associated_in_generic_parameter.carbon:[[@LINE+4]]:12: error: associated constant `.(Z.T)` given two different values `C(.(Z.U))` and `C(.(Z.U))` [AssociatedConstantWithDifferentValues]
67+
// CHECK:STDERR: fn F(FF:! ((Z where .T = C(.U)) where .T = C(.U)) where .T = C(.U)) {}
68+
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69+
// CHECK:STDERR:
70+
fn F(FF:! ((Z where .T = C(.U)) where .T = C(.U)) where .T = C(.U)) {}
71+
5772
// --- fail_nested_facet_types_different.carbon
5873
library "[[@TEST_NAME]]";
5974

@@ -81,6 +96,21 @@ interface Z {
8196
// CHECK:STDERR:
8297
fn F(FF:! (Z where .T = .U) where .T = {}) {}
8398

99+
// --- fail_nested_facet_types_different_with_associated_in_generic_parameter.carbon
100+
library "[[@TEST_NAME]]";
101+
102+
interface Z {
103+
let T:! type;
104+
let U:! type;
105+
}
106+
class C(T:! type) {}
107+
108+
// CHECK:STDERR: fail_nested_facet_types_different_with_associated_in_generic_parameter.carbon:[[@LINE+4]]:11: error: associated constant `.(Z.T)` given two different values `C(.(Z.U))` and `{}` [AssociatedConstantWithDifferentValues]
109+
// CHECK:STDERR: fn F(FF:! (Z where .T = C(.U)) where .T = {}) {}
110+
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111+
// CHECK:STDERR:
112+
fn F(FF:! (Z where .T = C(.U)) where .T = {}) {}
113+
84114
// --- nested_facet_types_same_with_bitand.carbon
85115
library "[[@TEST_NAME]]";
86116

0 commit comments

Comments
 (0)