Skip to content

Commit d65f60d

Browse files
Rollup merge of #107348 - lcnr:project-solve-new, r=compiler-errors
small refactor to new projection code extract `eq_term_and_make_canonical_response` into a helper function which also is another guarantee that the expected term does not influence candidate selection for projections. also change `evaluate_all(vec![single_goal])` to use `evaluate_goal`. the second commit now also adds a `debug_assert!` to `evaluate_goal`.
2 parents 4755c7c + 85e6f38 commit d65f60d

File tree

3 files changed

+86
-45
lines changed

3 files changed

+86
-45
lines changed

compiler/rustc_trait_selection/src/solve/mod.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
161161
search_graph: &mut search_graph,
162162
infcx: self,
163163
var_values: CanonicalVarValues::dummy(),
164+
in_projection_eq_hack: false,
164165
}
165166
.evaluate_goal(goal);
166167

@@ -174,6 +175,10 @@ struct EvalCtxt<'a, 'tcx> {
174175
var_values: CanonicalVarValues<'tcx>,
175176

176177
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
178+
179+
/// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
180+
/// see the comment in that method for more details.
181+
in_projection_eq_hack: bool,
177182
}
178183

179184
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
@@ -209,7 +214,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
209214
loop {
210215
let (ref infcx, goal, var_values) =
211216
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
212-
let mut ecx = EvalCtxt { infcx, var_values, search_graph };
217+
let mut ecx =
218+
EvalCtxt { infcx, var_values, search_graph, in_projection_eq_hack: false };
213219
let result = ecx.compute_goal(goal);
214220

215221
// FIXME: `Response` should be `Copy`
@@ -239,10 +245,28 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
239245
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
240246
let canonical_response =
241247
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
242-
Ok((
243-
!canonical_response.value.var_values.is_identity(),
244-
instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response),
245-
))
248+
249+
let has_changed = !canonical_response.value.var_values.is_identity();
250+
let certainty =
251+
instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response);
252+
253+
// Check that rerunning this query with its inference constraints applied
254+
// doesn't result in new inference constraints and has the same result.
255+
//
256+
// If we have projection goals like `<T as Trait>::Assoc == u32` we recursively
257+
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
258+
// could constrain `U` to `u32` which would cause this check to result in a
259+
// solver cycle.
260+
if cfg!(debug_assertions) && has_changed && !self.in_projection_eq_hack {
261+
let mut orig_values = OriginalQueryValues::default();
262+
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
263+
let canonical_response =
264+
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
265+
assert!(canonical_response.value.var_values.is_identity());
266+
assert_eq!(certainty, canonical_response.value.certainty);
267+
}
268+
269+
Ok((has_changed, certainty))
246270
}
247271

248272
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {

compiler/rustc_trait_selection/src/solve/project_goals.rs

+52-39
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
4545
projection_ty: goal.predicate.projection_ty,
4646
term: unconstrained_rhs,
4747
});
48-
let (_has_changed, normalize_certainty) =
49-
self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?;
48+
let (_has_changed, normalize_certainty) = self.in_projection_eq_hack(|this| {
49+
this.evaluate_goal(goal.with(this.tcx(), unconstrained_predicate))
50+
})?;
5051

5152
let nested_eq_goals =
5253
self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
@@ -55,6 +56,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
5556
}
5657
}
5758

59+
/// This sets a flag used by a debug assert in [`EvalCtxt::evaluate_goal`],
60+
/// see the comment in that method for more details.
61+
fn in_projection_eq_hack<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
62+
self.in_projection_eq_hack = true;
63+
let result = f(self);
64+
self.in_projection_eq_hack = false;
65+
result
66+
}
67+
5868
/// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
5969
///
6070
/// This is the case if the `term` is an inference variable in the innermost universe
@@ -122,6 +132,28 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
122132
&& goal.param_env.visit_with(&mut visitor).is_continue()
123133
}
124134

135+
/// After normalizing the projection to `normalized_alias` with the given
136+
/// `normalization_certainty`, constrain the inference variable `term` to it
137+
/// and return a query response.
138+
fn eq_term_and_make_canonical_response(
139+
&mut self,
140+
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
141+
normalization_certainty: Certainty,
142+
normalized_alias: impl Into<ty::Term<'tcx>>,
143+
) -> QueryResult<'tcx> {
144+
// The term of our goal should be fully unconstrained, so this should never fail.
145+
//
146+
// It can however be ambiguous when the `normalized_alias` contains a projection.
147+
let nested_goals = self
148+
.infcx
149+
.eq(goal.param_env, goal.predicate.term, normalized_alias.into())
150+
.expect("failed to unify with unconstrained term");
151+
let rhs_certainty =
152+
self.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
153+
154+
self.make_canonical_response(normalization_certainty.unify_and(rhs_certainty))
155+
}
156+
125157
fn merge_project_candidates(
126158
&mut self,
127159
mut candidates: Vec<Candidate<'tcx>>,
@@ -218,7 +250,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
218250
.map(|pred| goal.with(tcx, pred));
219251

220252
nested_goals.extend(where_clause_bounds);
221-
let trait_ref_certainty = ecx.evaluate_all(nested_goals)?;
253+
let match_impl_certainty = ecx.evaluate_all(nested_goals)?;
222254

223255
// In case the associated item is hidden due to specialization, we have to
224256
// return ambiguity this would otherwise be incomplete, resulting in
@@ -230,7 +262,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
230262
goal.predicate.def_id(),
231263
impl_def_id
232264
)? else {
233-
return ecx.make_canonical_response(trait_ref_certainty.unify_and(Certainty::AMBIGUOUS));
265+
return ecx.make_canonical_response(match_impl_certainty.unify_and(Certainty::AMBIGUOUS));
234266
};
235267

236268
if !assoc_def.item.defaultness(tcx).has_value() {
@@ -277,17 +309,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
277309
ty.map_bound(|ty| ty.into())
278310
};
279311

280-
// The term of our goal should be fully unconstrained, so this should never fail.
281-
//
282-
// It can however be ambiguous when the resolved type is a projection.
283-
let nested_goals = ecx
284-
.infcx
285-
.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
286-
.expect("failed to unify with unconstrained term");
287-
let rhs_certainty =
288-
ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
289-
290-
ecx.make_canonical_response(trait_ref_certainty.unify_and(rhs_certainty))
312+
ecx.eq_term_and_make_canonical_response(goal, match_impl_certainty, term.subst(tcx, substs))
291313
})
292314
}
293315

@@ -309,18 +331,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
309331
)?;
310332
let subst_certainty = ecx.evaluate_all(nested_goals)?;
311333

312-
// The term of our goal should be fully unconstrained, so this should never fail.
313-
//
314-
// It can however be ambiguous when the resolved type is a projection.
315-
let nested_goals = ecx
316-
.infcx
317-
.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)
318-
.expect("failed to unify with unconstrained term");
319-
let rhs_certainty = ecx
320-
.evaluate_all(nested_goals)
321-
.expect("failed to unify with unconstrained term");
322-
323-
ecx.make_canonical_response(subst_certainty.unify_and(rhs_certainty))
334+
ecx.eq_term_and_make_canonical_response(
335+
goal,
336+
subst_certainty,
337+
assumption_projection_pred.term,
338+
)
324339
})
325340
} else {
326341
Err(NoSolution)
@@ -437,14 +452,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
437452
[ty::GenericArg::from(goal.predicate.self_ty())],
438453
));
439454

440-
let mut nested_goals = ecx.infcx.eq(
441-
goal.param_env,
442-
goal.predicate.term.ty().unwrap(),
455+
let is_sized_certainty = ecx.evaluate_goal(goal.with(tcx, sized_predicate))?.1;
456+
return ecx.eq_term_and_make_canonical_response(
457+
goal,
458+
is_sized_certainty,
443459
tcx.types.unit,
444-
)?;
445-
nested_goals.push(goal.with(tcx, sized_predicate));
446-
447-
return ecx.evaluate_all_and_make_canonical_response(nested_goals);
460+
);
448461
}
449462

450463
ty::Adt(def, substs) if def.is_struct() => {
@@ -456,7 +469,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
456469
tcx,
457470
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
458471
);
459-
return ecx.evaluate_all_and_make_canonical_response(vec![new_goal]);
472+
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
473+
return ecx.make_canonical_response(certainty);
460474
}
461475
}
462476
}
@@ -469,7 +483,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
469483
tcx,
470484
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
471485
);
472-
return ecx.evaluate_all_and_make_canonical_response(vec![new_goal]);
486+
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
487+
return ecx.make_canonical_response(certainty);
473488
}
474489
},
475490

@@ -482,9 +497,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
482497
),
483498
};
484499

485-
let nested_goals =
486-
ecx.infcx.eq(goal.param_env, goal.predicate.term.ty().unwrap(), metadata_ty)?;
487-
ecx.evaluate_all_and_make_canonical_response(nested_goals)
500+
ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, metadata_ty)
488501
})
489502
}
490503

compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ impl<'tcx> SearchGraph<'tcx> {
4545
/// Tries putting the new goal on the stack, returning an error if it is already cached.
4646
///
4747
/// This correctly updates the provisional cache if there is a cycle.
48+
#[instrument(level = "debug", skip(self, tcx), ret)]
4849
pub(super) fn try_push_stack(
4950
&mut self,
5051
tcx: TyCtxt<'tcx>,
@@ -79,8 +80,10 @@ impl<'tcx> SearchGraph<'tcx> {
7980
Entry::Occupied(entry_index) => {
8081
let entry_index = *entry_index.get();
8182

82-
cache.add_dependency_of_leaf_on(entry_index);
8383
let stack_depth = cache.depth(entry_index);
84+
debug!("encountered cycle with depth {stack_depth:?}");
85+
86+
cache.add_dependency_of_leaf_on(entry_index);
8487

8588
self.stack[stack_depth].has_been_used = true;
8689
// NOTE: The goals on the stack aren't the only goals involved in this cycle.
@@ -117,6 +120,7 @@ impl<'tcx> SearchGraph<'tcx> {
117120
/// updated the provisional cache and we have to recompute the current goal.
118121
///
119122
/// FIXME: Refer to the rustc-dev-guide entry once it exists.
123+
#[instrument(level = "debug", skip(self, tcx, actual_goal), ret)]
120124
pub(super) fn try_finalize_goal(
121125
&mut self,
122126
tcx: TyCtxt<'tcx>,

0 commit comments

Comments
 (0)