@@ -67,7 +67,20 @@ pub(super) enum IsNormalizesToHack {
67
67
68
68
#[ derive( Debug , Clone ) ]
69
69
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.
70
82
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.
71
84
pub ( super ) goals : Vec < Goal < ' tcx , ty:: Predicate < ' tcx > > > ,
72
85
}
73
86
@@ -182,6 +195,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
182
195
canonical_response,
183
196
) ?;
184
197
198
+ if !has_changed && !nested_goals. is_empty ( ) {
199
+ bug ! ( "an unchanged goal shouldn't have any side-effects on instantiation" ) ;
200
+ }
201
+
185
202
// Check that rerunning this query with its inference constraints applied
186
203
// doesn't result in new inference constraints and has the same result.
187
204
//
@@ -199,9 +216,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
199
216
let canonical_response =
200
217
EvalCtxt :: evaluate_canonical_goal ( self . tcx ( ) , self . search_graph , canonical_goal) ?;
201
218
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
+ ) ;
203
229
}
204
- assert_eq ! ( certainty, canonical_response. value. certainty) ;
205
230
}
206
231
207
232
Ok ( ( has_changed, certainty, nested_goals) )
@@ -281,15 +306,44 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
281
306
let mut has_changed = Err ( Certainty :: Yes ) ;
282
307
283
308
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,
287
336
) {
288
- Ok ( r) => r,
337
+ Ok ( eq_goals) => {
338
+ goals. goals . extend ( eq_goals) ;
339
+ }
289
340
Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
290
341
} ;
291
- new_goals. goals . extend ( nested_goals) ;
292
342
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!
293
347
if goal. predicate . projection_ty
294
348
!= this. resolve_vars_if_possible ( goal. predicate . projection_ty )
295
349
{
@@ -299,40 +353,22 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
299
353
match certainty {
300
354
Certainty :: Yes => { }
301
355
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.
321
358
new_goals. normalizes_to_hack_goal =
322
- Some ( goal. with ( this. tcx ( ) , projection_pred) ) ;
323
-
359
+ Some ( this. resolve_vars_if_possible ( goal) ) ;
324
360
has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
325
361
}
326
362
}
327
363
}
328
364
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 ) {
332
368
Ok ( result) => result,
333
369
Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
334
370
} ;
335
- new_goals. goals . extend ( nested_goals ) ;
371
+ new_goals. goals . extend ( instantiate_goals ) ;
336
372
337
373
if changed {
338
374
has_changed = Ok ( ( ) ) ;
@@ -341,7 +377,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
341
377
match certainty {
342
378
Certainty :: Yes => { }
343
379
Certainty :: Maybe ( _) => {
344
- new_goals. goals . push ( nested_goal ) ;
380
+ new_goals. goals . push ( goal ) ;
345
381
has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
346
382
}
347
383
}
0 commit comments