@@ -48,7 +48,20 @@ pub(super) enum IsNormalizesToHack {
48
48
49
49
#[ derive( Debug , Clone ) ]
50
50
pub ( 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.
51
63
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.
52
65
pub ( super ) goals : Vec < Goal < ' tcx , ty:: Predicate < ' tcx > > > ,
53
66
}
54
67
@@ -163,6 +176,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
163
176
canonical_response,
164
177
) ?;
165
178
179
+ if !has_changed && !nested_goals. is_empty ( ) {
180
+ bug ! ( "an unchanged goal shouldn't have any side-effects on instantiation" ) ;
181
+ }
182
+
166
183
// Check that rerunning this query with its inference constraints applied
167
184
// doesn't result in new inference constraints and has the same result.
168
185
//
@@ -180,9 +197,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
180
197
let canonical_response =
181
198
EvalCtxt :: evaluate_canonical_goal ( self . tcx ( ) , self . search_graph , canonical_goal) ?;
182
199
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
+ ) ;
184
210
}
185
- assert_eq ! ( certainty, canonical_response. value. certainty) ;
186
211
}
187
212
188
213
Ok ( ( has_changed, certainty, nested_goals) )
@@ -262,15 +287,44 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
262
287
let mut has_changed = Err ( Certainty :: Yes ) ;
263
288
264
289
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,
268
317
) {
269
- Ok ( r) => r,
318
+ Ok ( eq_goals) => {
319
+ goals. goals . extend ( eq_goals) ;
320
+ }
270
321
Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
271
322
} ;
272
- new_goals. goals . extend ( nested_goals) ;
273
323
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!
274
328
if goal. predicate . projection_ty
275
329
!= this. resolve_vars_if_possible ( goal. predicate . projection_ty )
276
330
{
@@ -280,40 +334,22 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
280
334
match certainty {
281
335
Certainty :: Yes => { }
282
336
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.
302
339
new_goals. normalizes_to_hack_goal =
303
- Some ( goal. with ( this. tcx ( ) , projection_pred) ) ;
304
-
340
+ Some ( this. resolve_vars_if_possible ( goal) ) ;
305
341
has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
306
342
}
307
343
}
308
344
}
309
345
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 ) {
313
349
Ok ( result) => result,
314
350
Err ( NoSolution ) => return Some ( Err ( NoSolution ) ) ,
315
351
} ;
316
- new_goals. goals . extend ( nested_goals ) ;
352
+ new_goals. goals . extend ( instantiate_goals ) ;
317
353
318
354
if changed {
319
355
has_changed = Ok ( ( ) ) ;
@@ -322,7 +358,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
322
358
match certainty {
323
359
Certainty :: Yes => { }
324
360
Certainty :: Maybe ( _) => {
325
- new_goals. goals . push ( nested_goal ) ;
361
+ new_goals. goals . push ( goal ) ;
326
362
has_changed = has_changed. map_err ( |c| c. unify_and ( certainty) ) ;
327
363
}
328
364
}
0 commit comments