Skip to content

Commit 2b7c75d

Browse files
authored
Avoid incorrect conflicting assignment diags in rewrite constraints (#5686)
When building a FacetType from an existing FacetType, don't diagnose rewrite constraints that are compatible with the existing FacetType. To do this, we consider two RHS as identical[1] if they have the same constant value after substituting from available rewrite constraints in the being-constructed FacetType, since the syntactic representation of the RHS is lost during eval. [1] https://docs.google.com/document/d/1Yt-i5AmF76LSvD4TrWRIAE_92kii6j5yFiW-S7ahzlg/edit?tab=t.0#heading=h.qti4vn50zwy
1 parent 1aba7ea commit 2b7c75d

File tree

5 files changed

+83
-78
lines changed

5 files changed

+83
-78
lines changed

toolchain/check/eval.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -634,11 +634,13 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
634634
GetConstantValue(eval_context, interface.specific_id, phase)});
635635
}
636636

637-
// Rewrite constraints are resolved first before evaluation, so that we can
638-
// work with the `ImplWitnessAccess` references to `.Self` without them
639-
// evaluating to a value. It also ensures that any errors inserted during
640-
// resolution will be seen by GetConstantValueIgnoringPeriodSelf() which will
641-
// update the phase accordingly.
637+
// Rewrite constraints are resolved first before replacing them with their
638+
// canonical instruction, so that in a `WhereExpr` we can work with the
639+
// `ImplWitnessAccess` references to `.Self` on the LHS of the constraints
640+
// rather than the value of the associated constant they reference. It also
641+
// ensures that any errors inserted during resolution will be seen by
642+
// GetConstantValueIgnoringPeriodSelf() which will update the phase
643+
// accordingly.
642644
info.rewrite_constraints = orig.rewrite_constraints;
643645
ResolveFacetTypeRewriteConstraints(eval_context.context(), loc_id,
644646
info.rewrite_constraints);

toolchain/check/eval_inst.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "toolchain/check/type.h"
1717
#include "toolchain/check/type_completion.h"
1818
#include "toolchain/diagnostics/diagnostic.h"
19+
#include "toolchain/parse/typed_nodes.h"
1920
#include "toolchain/sem_ir/expr_info.h"
2021
#include "toolchain/sem_ir/ids.h"
2122
#include "toolchain/sem_ir/pattern.h"

toolchain/check/facet_type.cpp

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -381,12 +381,8 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
381381
substituting_constraint_(substituting_constraint) {}
382382

383383
auto Subst(SemIR::InstId& rhs_inst_id) const -> bool override {
384-
if (context().constant_values().Get(rhs_inst_id).is_concrete()) {
385-
return true;
386-
}
387-
388384
if (!context().insts().Is<SemIR::ImplWitnessAccess>(rhs_inst_id)) {
389-
return false;
385+
return context().constant_values().Get(rhs_inst_id).is_concrete();
390386
}
391387

392388
// TODO: We could consider something better than linear search here, such as
@@ -413,11 +409,13 @@ class SubstImplWitnessAccessCallbacks : public SubstInstCallbacks {
413409
} else {
414410
rhs_inst_id = search_constraint.rhs_id;
415411
}
416-
return true;
417412
}
418413
}
419414

420-
return false;
415+
// Never recurse into ImplWitnessAccess, we don't want to substitute into
416+
// FacetTypes found within. We only substitute ImplWitnessAccesses that
417+
// appear directly on the RHS.
418+
return true;
421419
}
422420

423421
auto Rebuild(SemIR::InstId /*orig_inst_id*/, SemIR::Inst new_inst) const
@@ -439,6 +437,43 @@ auto ResolveFacetTypeRewriteConstraints(
439437
return;
440438
}
441439

440+
while (true) {
441+
bool applied_rewrite = false;
442+
443+
for (auto& constraint : rewrites) {
444+
if (constraint.lhs_id == SemIR::ErrorInst::InstId ||
445+
constraint.rhs_id == SemIR::ErrorInst::InstId) {
446+
continue;
447+
}
448+
449+
auto lhs_access =
450+
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
451+
if (!lhs_access) {
452+
continue;
453+
}
454+
455+
// Replace any `ImplWitnessAccess` in the RHS of this constraint with the
456+
// RHS of another constraint that sets the value of the associated
457+
// constant being accessed in the RHS.
458+
auto subst_inst_id =
459+
SubstInst(context, constraint.rhs_id,
460+
SubstImplWitnessAccessCallbacks(&context, loc_id, rewrites,
461+
&constraint));
462+
if (subst_inst_id != constraint.rhs_id) {
463+
constraint.rhs_id = subst_inst_id;
464+
if (constraint.rhs_id != SemIR::ErrorInst::InstId) {
465+
// If the RHS is replaced with a non-error value, we need to do
466+
// another pass so that the new RHS value can continue to propagate.
467+
applied_rewrite = true;
468+
}
469+
}
470+
}
471+
472+
if (!applied_rewrite) {
473+
break;
474+
}
475+
}
476+
442477
// We sort the constraints so that we can find different values being written
443478
// to the same LHS by looking at consecutive rewrite constraints.
444479
//
@@ -509,43 +544,6 @@ auto ResolveFacetTypeRewriteConstraints(
509544
next.rhs_id = SemIR::ErrorInst::InstId;
510545
}
511546
}
512-
513-
while (true) {
514-
bool applied_rewrite = false;
515-
516-
for (auto& constraint : rewrites) {
517-
if (constraint.lhs_id == SemIR::ErrorInst::InstId ||
518-
constraint.rhs_id == SemIR::ErrorInst::InstId) {
519-
continue;
520-
}
521-
522-
auto lhs_access =
523-
context.insts().TryGetAs<SemIR::ImplWitnessAccess>(constraint.lhs_id);
524-
if (!lhs_access) {
525-
continue;
526-
}
527-
528-
// Replace any `ImplWitnessAccess` in the RHS of this constraint with the
529-
// RHS of another constraint that sets the value of the associated
530-
// constant being accessed in the RHS.
531-
auto subst_inst_id =
532-
SubstInst(context, constraint.rhs_id,
533-
SubstImplWitnessAccessCallbacks(&context, loc_id, rewrites,
534-
&constraint));
535-
if (subst_inst_id != constraint.rhs_id) {
536-
constraint.rhs_id = subst_inst_id;
537-
if (constraint.rhs_id != SemIR::ErrorInst::InstId) {
538-
// If the RHS is replaced with a non-error value, we need to do
539-
// another pass so that the new RHS value can continue to propagate.
540-
applied_rewrite = true;
541-
}
542-
}
543-
}
544-
545-
if (!applied_rewrite) {
546-
break;
547-
}
548-
}
549547
}
550548

551549
} // namespace Carbon::Check

toolchain/check/testdata/facet/facet_assoc_const.carbon

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -206,24 +206,16 @@ fn G(T:! M where .X = () and .Y = ()) {
206206
F(T);
207207
}
208208

209-
// --- fail_todo_repeated_with_bitand.carbon
209+
// --- repeated_with_bitand.carbon
210210
library "[[@TEST_NAME]]";
211211

212212
interface M { let X:! type; let Y:! type; }
213213

214-
// CHECK:STDERR: fail_todo_repeated_with_bitand.carbon:[[@LINE+4]]:10: error: associated constant `.(M.X)` given two different values `.(M.Y)` and `()` [AssociatedConstantWithDifferentValues]
215-
// CHECK:STDERR: fn F(T:! (M where .X = .Y) & (M where .X = .Y and .Y = ())) -> T.X {
216-
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
217-
// CHECK:STDERR:
218-
fn F(T:! (M where .X = .Y) & (M where .X = .Y and .Y = ())) -> T.X {
214+
fn F1(T:! (M where .X = .Y) & (M where .X = .Y and .Y = ())) -> T.X {
219215
return ();
220216
}
221217

222-
// CHECK:STDERR: fail_todo_repeated_with_bitand.carbon:[[@LINE+4]]:10: error: associated constant `.(M.X)` given two different values `.(M.Y)` and `()` [AssociatedConstantWithDifferentValues]
223-
// CHECK:STDERR: fn F(T:! (M where .X = .Y and .Y = ()) & (M where .X = .Y)) -> T.X {
224-
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
225-
// CHECK:STDERR:
226-
fn F(T:! (M where .X = .Y and .Y = ()) & (M where .X = .Y)) -> T.X {
218+
fn F2(T:! (M where .X = .Y and .Y = ()) & (M where .X = .Y)) -> T.X {
227219
return ();
228220
}
229221

toolchain/check/testdata/facet/nested_facet_types.carbon

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ interface Z {
4343

4444
fn F(FF:! ((Z where .T = .U) where .T = .U) where .T = .U) {}
4545

46-
// --- fail_todo_nested_facet_types_same_two_associated.carbon
46+
// --- nested_facet_types_same_two_associated.carbon
4747
library "[[@TEST_NAME]]";
4848

4949
interface Z {
@@ -52,12 +52,6 @@ interface Z {
5252
let V:! type;
5353
}
5454

55-
// TODO: There should not be a diagnostic here.
56-
//
57-
// CHECK:STDERR: fail_todo_nested_facet_types_same_two_associated.carbon:[[@LINE+4]]:12: error: associated constant `.(Z.T)` given two different values `.(Z.V)` and `.(Z.U)` [AssociatedConstantWithDifferentValues]
58-
// CHECK:STDERR: fn F(FF:! ((Z where .T = .U and .U = .V) where .T = .U and .U = .V) where .T = .U and .U = .V) {}
59-
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60-
// CHECK:STDERR:
6155
fn F(FF:! ((Z where .T = .U and .U = .V) where .T = .U and .U = .V) where .T = .U and .U = .V) {}
6256

6357
// --- fail_nested_facet_types_different.carbon
@@ -183,21 +177,39 @@ interface Z {
183177
// CHECK:STDERR:
184178
fn F(FF:! (((Z where .V = {}) where .T = .U) where .S = .U) & (Z where .S = {})) {}
185179

186-
// --- fail_todo_repeated_with_value_available.carbon
180+
// --- repeated_with_value_available.carbon
187181
library "[[@TEST_NAME]]";
188182

189183
interface Z { let X:! type; let Y:! type; }
190184

191-
// Verify that the `.Y = ()` does not get applied to `.X = .Y` prematurely, as
192-
// that would lead to `.X = () and .X = .Y` when combined with the outer `where`
193-
// expression, which would be an error.
194-
//
195-
// TODO: There should be no diagnostic here.
196-
//
197-
// CHECK:STDERR: fail_todo_repeated_with_value_available.carbon:[[@LINE+4]]:10: error: associated constant `.(Z.X)` given two different values `()` and `.(Z.Y)` [AssociatedConstantWithDifferentValues]
198-
// CHECK:STDERR: fn F(T:! ((Z where .Y = ()) where .X = .Y) where .X = .Y) -> T.X {
199-
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
200-
// CHECK:STDERR:
201185
fn F(T:! ((Z where .Y = ()) where .X = .Y) where .X = .Y) -> T.X {
202186
return ();
203187
}
188+
189+
class C(T:! type) { adapt (); }
190+
191+
fn F1(T:! (Z where .Y = C(()) and .X = C(.Y)) where .X = C(C(()))) -> T.X {
192+
return () as C(C(()));
193+
}
194+
195+
fn F2(T:! ((Z where .Y = C(())) where .X = C(.Y)) where .X = C(C(()))) -> T.X {
196+
return () as C(C(()));
197+
}
198+
199+
fn F3(T:! Z where .Y = C(()) and .X = C(.Y) and .X = C(C(()))) -> T.X {
200+
return () as C(C(()));
201+
}
202+
203+
// --- conflicting_syntax_same_value.carbon
204+
205+
library "[[@TEST_NAME]]";
206+
207+
interface Z { let X:! type; let Y:! type; }
208+
209+
fn F1(T:! ((Z where .Y = ()) where .X = .Y) where .X = ()) -> T.X {
210+
return ();
211+
}
212+
213+
fn F2(T:! ((Z where .Y = ()) where .X = ()) where .X = .Y) -> T.X {
214+
return ();
215+
}

0 commit comments

Comments
 (0)