@@ -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