Skip to content

Commit 1ffb1af

Browse files
Rollup merge of rust-lang#109679 - compiler-errors:normalizes-to-hack-2, r=lcnr,BoxyUwU
Freshen normalizes-to hack goal RHS in the evaluate loop Ensure that we repeatedly equate the unconstrained RHS of the normalizes-to hack goal with the *actual* RHS of the goal, even if the normalizes-to goal loops several times and thus we replace the unconstrained RHS var repeatedly. Alternative to rust-lang#109583.
2 parents bc7976e + 0542b0d commit 1ffb1af

File tree

2 files changed

+70
-43
lines changed

2 files changed

+70
-43
lines changed

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+69-33
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,20 @@ pub(super) enum IsNormalizesToHack {
6767

6868
#[derive(Debug, Clone)]
6969
pub(super) struct NestedGoals<'tcx> {
70+
/// This normalizes-to goal that is treated specially during the evaluation
71+
/// loop. In each iteration we take the RHS of the projection, replace it with
72+
/// a fresh inference variable, and only after evaluating that goal do we
73+
/// equate the fresh inference variable with the actual RHS of the predicate.
74+
///
75+
/// This is both to improve caching, and to avoid using the RHS of the
76+
/// projection predicate to influence the normalizes-to candidate we select.
77+
///
78+
/// This is not a 'real' nested goal. We must not forget to replace the RHS
79+
/// with a fresh inference variable when we evaluate this goal. That can result
80+
/// in a trait solver cycle. This would currently result in overflow but can be
81+
/// can be unsound with more powerful coinduction in the future.
7082
pub(super) normalizes_to_hack_goal: Option<Goal<'tcx, ty::ProjectionPredicate<'tcx>>>,
83+
/// The rest of the goals which have not yet processed or remain ambiguous.
7184
pub(super) goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
7285
}
7386

@@ -182,6 +195,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
182195
canonical_response,
183196
)?;
184197

198+
if !has_changed && !nested_goals.is_empty() {
199+
bug!("an unchanged goal shouldn't have any side-effects on instantiation");
200+
}
201+
185202
// Check that rerunning this query with its inference constraints applied
186203
// doesn't result in new inference constraints and has the same result.
187204
//
@@ -199,9 +216,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
199216
let canonical_response =
200217
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
201218
if !canonical_response.value.var_values.is_identity() {
202-
bug!("unstable result: {goal:?} {canonical_goal:?} {canonical_response:?}");
219+
bug!(
220+
"unstable result: re-canonicalized goal={canonical_goal:#?} \
221+
response={canonical_response:#?}"
222+
);
223+
}
224+
if certainty != canonical_response.value.certainty {
225+
bug!(
226+
"unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
227+
response={canonical_response:#?}"
228+
);
203229
}
204-
assert_eq!(certainty, canonical_response.value.certainty);
205230
}
206231

207232
Ok((has_changed, certainty, nested_goals))
@@ -281,15 +306,44 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
281306
let mut has_changed = Err(Certainty::Yes);
282307

283308
if let Some(goal) = goals.normalizes_to_hack_goal.take() {
284-
let (_, certainty, nested_goals) = match this.evaluate_goal(
285-
IsNormalizesToHack::Yes,
286-
goal.with(this.tcx(), ty::Binder::dummy(goal.predicate)),
309+
// Replace the goal with an unconstrained infer var, so the
310+
// RHS does not affect projection candidate assembly.
311+
let unconstrained_rhs = this.next_term_infer_of_kind(goal.predicate.term);
312+
let unconstrained_goal = goal.with(
313+
this.tcx(),
314+
ty::Binder::dummy(ty::ProjectionPredicate {
315+
projection_ty: goal.predicate.projection_ty,
316+
term: unconstrained_rhs,
317+
}),
318+
);
319+
320+
let (_, certainty, instantiate_goals) =
321+
match this.evaluate_goal(IsNormalizesToHack::Yes, unconstrained_goal) {
322+
Ok(r) => r,
323+
Err(NoSolution) => return Some(Err(NoSolution)),
324+
};
325+
new_goals.goals.extend(instantiate_goals);
326+
327+
// Finally, equate the goal's RHS with the unconstrained var.
328+
// We put the nested goals from this into goals instead of
329+
// next_goals to avoid needing to process the loop one extra
330+
// time if this goal returns something -- I don't think this
331+
// matters in practice, though.
332+
match this.eq_and_get_goals(
333+
goal.param_env,
334+
goal.predicate.term,
335+
unconstrained_rhs,
287336
) {
288-
Ok(r) => r,
337+
Ok(eq_goals) => {
338+
goals.goals.extend(eq_goals);
339+
}
289340
Err(NoSolution) => return Some(Err(NoSolution)),
290341
};
291-
new_goals.goals.extend(nested_goals);
292342

343+
// We only look at the `projection_ty` part here rather than
344+
// looking at the "has changed" return from evaluate_goal,
345+
// because we expect the `unconstrained_rhs` part of the predicate
346+
// to have changed -- that means we actually normalized successfully!
293347
if goal.predicate.projection_ty
294348
!= this.resolve_vars_if_possible(goal.predicate.projection_ty)
295349
{
@@ -299,40 +353,22 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
299353
match certainty {
300354
Certainty::Yes => {}
301355
Certainty::Maybe(_) => {
302-
let goal = this.resolve_vars_if_possible(goal);
303-
304-
// The rhs of this `normalizes-to` must always be an unconstrained infer var as it is
305-
// the hack used by `normalizes-to` to ensure that every `normalizes-to` behaves the same
306-
// regardless of the rhs.
307-
//
308-
// However it is important not to unconditionally replace the rhs with a new infer var
309-
// as otherwise we may replace the original unconstrained infer var with a new infer var
310-
// and never propagate any constraints on the new var back to the original var.
311-
let term = this
312-
.term_is_fully_unconstrained(goal)
313-
.then_some(goal.predicate.term)
314-
.unwrap_or_else(|| {
315-
this.next_term_infer_of_kind(goal.predicate.term)
316-
});
317-
let projection_pred = ty::ProjectionPredicate {
318-
term,
319-
projection_ty: goal.predicate.projection_ty,
320-
};
356+
// We need to resolve vars here so that we correctly
357+
// deal with `has_changed` in the next iteration.
321358
new_goals.normalizes_to_hack_goal =
322-
Some(goal.with(this.tcx(), projection_pred));
323-
359+
Some(this.resolve_vars_if_possible(goal));
324360
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
325361
}
326362
}
327363
}
328364

329-
for nested_goal in goals.goals.drain(..) {
330-
let (changed, certainty, nested_goals) =
331-
match this.evaluate_goal(IsNormalizesToHack::No, nested_goal) {
365+
for goal in goals.goals.drain(..) {
366+
let (changed, certainty, instantiate_goals) =
367+
match this.evaluate_goal(IsNormalizesToHack::No, goal) {
332368
Ok(result) => result,
333369
Err(NoSolution) => return Some(Err(NoSolution)),
334370
};
335-
new_goals.goals.extend(nested_goals);
371+
new_goals.goals.extend(instantiate_goals);
336372

337373
if changed {
338374
has_changed = Ok(());
@@ -341,7 +377,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
341377
match certainty {
342378
Certainty::Yes => {}
343379
Certainty::Maybe(_) => {
344-
new_goals.goals.push(nested_goal);
380+
new_goals.goals.push(goal);
345381
has_changed = has_changed.map_err(|c| c.unify_and(certainty));
346382
}
347383
}

compiler/rustc_trait_selection/src/solve/project_goals.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
3434
let candidates = self.assemble_and_evaluate_candidates(goal);
3535
self.merge_candidates(candidates)
3636
} else {
37-
let predicate = goal.predicate;
38-
let unconstrained_rhs = self.next_term_infer_of_kind(predicate.term);
39-
let unconstrained_predicate = ProjectionPredicate {
40-
projection_ty: goal.predicate.projection_ty,
41-
term: unconstrained_rhs,
42-
};
43-
44-
self.set_normalizes_to_hack_goal(goal.with(self.tcx(), unconstrained_predicate));
45-
self.try_evaluate_added_goals()?;
46-
self.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
37+
self.set_normalizes_to_hack_goal(goal);
4738
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
4839
}
4940
}

0 commit comments

Comments
 (0)