@@ -67,7 +67,20 @@ pub(super) enum IsNormalizesToHack {
6767
6868#[ derive( Debug , Clone ) ]
6969pub ( 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 }
0 commit comments