@@ -48,7 +48,20 @@ pub(super) enum IsNormalizesToHack {
4848
4949#[ derive( Debug , Clone ) ]
5050pub ( super ) struct NestedGoals < ' tcx > {
51+ /// This normalizes-to goal that is treated specially during the evaluation
52+ /// loop. In each iteration we take the RHS of the projection, replace it with
53+ /// a fresh inference variable, and only after evaluating that goal do we
54+ /// equate the fresh inference variable with the actual RHS of the predicate.
55+ ///
56+ /// This is both to improve caching, and to avoid using the RHS of the
57+ /// projection predicate to influence the normalizes-to candidate we select.
58+ ///
59+ /// This is not a 'real' nested goal. We must not forget to replace the RHS
60+ /// with a fresh inference variable when we evaluate this goal. That can result
61+ /// in a trait solver cycle. This would currently result in overflow but can be
62+ /// can be unsound with more powerful coinduction in the future.
5163 pub ( super ) normalizes_to_hack_goal : Option < Goal < ' tcx , ty:: ProjectionPredicate < ' tcx > > > ,
64+ /// The rest of the goals which have not yet processed or remain ambiguous.
5265 pub ( super ) goals : Vec < Goal < ' tcx , ty:: Predicate < ' tcx > > > ,
5366}
5467
@@ -163,6 +176,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
163176 canonical_response,
164177 ) ?;
165178
179+ if !has_changed && !nested_goals. is_empty ( ) {
180+ bug ! ( "an unchanged goal shouldn't have any side-effects on instantiation" ) ;
181+ }
182+
166183 // Check that rerunning this query with its inference constraints applied
167184 // doesn't result in new inference constraints and has the same result.
168185 //
@@ -180,9 +197,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
180197 let canonical_response =
181198 EvalCtxt :: evaluate_canonical_goal ( self . tcx ( ) , self . search_graph , canonical_goal) ?;
182199 if !canonical_response. value . var_values . is_identity ( ) {
183- bug ! ( "unstable result: {goal:?} {canonical_goal:?} {canonical_response:?}" ) ;
200+ bug ! (
201+ "unstable result: re-canonicalized goal={canonical_goal:#?} \
202+ response={canonical_response:#?}"
203+ ) ;
204+ }
205+ if certainty != canonical_response. value . certainty {
206+ bug ! (
207+ "unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
208+ response={canonical_response:#?}"
209+ ) ;
184210 }
185- assert_eq ! ( certainty, canonical_response. value. certainty) ;
186211 }
187212
188213 Ok ( ( has_changed, certainty, nested_goals) )
@@ -262,15 +287,44 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
262287 let mut has_changed = Err ( Certainty :: Yes ) ;
263288
264289 if let Some ( goal) = goals. normalizes_to_hack_goal . take ( ) {
265- let ( _, certainty, nested_goals) = match this. evaluate_goal (
266- IsNormalizesToHack :: Yes ,
267- goal. with ( this. tcx ( ) , ty:: Binder :: dummy ( goal. predicate ) ) ,
290+ // Replace the goal with an unconstrained infer var, so the
291+ // RHS does not affect projection candidate assembly.
292+ let unconstrained_rhs = this. next_term_infer_of_kind ( goal. predicate . term ) ;
293+ let unconstrained_goal = goal. with (
294+ this. tcx ( ) ,
295+ ty:: Binder :: dummy ( ty:: ProjectionPredicate {
296+ projection_ty : goal. predicate . projection_ty ,
297+ term : unconstrained_rhs,
298+ } ) ,
299+ ) ;
300+
301+ let ( _, certainty, instantiate_goals) =
302+ match this. evaluate_goal ( IsNormalizesToHack :: Yes , unconstrained_goal) {
303+ Ok ( r) => r,
304+ Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
305+ } ;
306+ new_goals. goals . extend ( instantiate_goals) ;
307+
308+ // Finally, equate the goal's RHS with the unconstrained var.
309+ // We put the nested goals from this into goals instead of
310+ // next_goals to avoid needing to process the loop one extra
311+ // time if this goal returns something -- I don't think this
312+ // matters in practice, though.
313+ match this. eq_and_get_goals (
314+ goal. param_env ,
315+ goal. predicate . term ,
316+ unconstrained_rhs,
268317 ) {
269- Ok ( r) => r,
318+ Ok ( eq_goals) => {
319+ goals. goals . extend ( eq_goals) ;
320+ }
270321 Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
271322 } ;
272- new_goals. goals . extend ( nested_goals) ;
273323
324+ // We only look at the `projection_ty` part here rather than
325+ // looking at the "has changed" return from evaluate_goal,
326+ // because we expect the `unconstrained_rhs` part of the predicate
327+ // to have changed -- that means we actually normalized successfully!
274328 if goal. predicate . projection_ty
275329 != this. resolve_vars_if_possible ( goal. predicate . projection_ty )
276330 {
@@ -280,40 +334,22 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
280334 match certainty {
281335 Certainty :: Yes => { }
282336 Certainty :: Maybe ( _) => {
283- let goal = this. resolve_vars_if_possible ( goal) ;
284-
285- // The rhs of this `normalizes-to` must always be an unconstrained infer var as it is
286- // the hack used by `normalizes-to` to ensure that every `normalizes-to` behaves the same
287- // regardless of the rhs.
288- //
289- // However it is important not to unconditionally replace the rhs with a new infer var
290- // as otherwise we may replace the original unconstrained infer var with a new infer var
291- // and never propagate any constraints on the new var back to the original var.
292- let term = this
293- . term_is_fully_unconstrained ( goal)
294- . then_some ( goal. predicate . term )
295- . unwrap_or_else ( || {
296- this. next_term_infer_of_kind ( goal. predicate . term )
297- } ) ;
298- let projection_pred = ty:: ProjectionPredicate {
299- term,
300- projection_ty : goal. predicate . projection_ty ,
301- } ;
337+ // We need to resolve vars here so that we correctly
338+ // deal with `has_changed` in the next iteration.
302339 new_goals. normalizes_to_hack_goal =
303- Some ( goal. with ( this. tcx ( ) , projection_pred) ) ;
304-
340+ Some ( this. resolve_vars_if_possible ( goal) ) ;
305341 has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
306342 }
307343 }
308344 }
309345
310- for nested_goal in goals. goals . drain ( ..) {
311- let ( changed, certainty, nested_goals ) =
312- match this. evaluate_goal ( IsNormalizesToHack :: No , nested_goal ) {
346+ for goal in goals. goals . drain ( ..) {
347+ let ( changed, certainty, instantiate_goals ) =
348+ match this. evaluate_goal ( IsNormalizesToHack :: No , goal ) {
313349 Ok ( result) => result,
314350 Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
315351 } ;
316- new_goals. goals . extend ( nested_goals ) ;
352+ new_goals. goals . extend ( instantiate_goals ) ;
317353
318354 if changed {
319355 has_changed = Ok ( ( ) ) ;
@@ -322,7 +358,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
322358 match certainty {
323359 Certainty :: Yes => { }
324360 Certainty :: Maybe ( _) => {
325- new_goals. goals . push ( nested_goal ) ;
361+ new_goals. goals . push ( goal ) ;
326362 has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
327363 }
328364 }
0 commit comments