Skip to content

Commit 85e6f38

Browse files
committed
assert that solver results are stable
1 parent 9c3fe58 commit 85e6f38

File tree

3 files changed

+46
-8
lines changed

3 files changed

+46
-8
lines changed

compiler/rustc_trait_selection/src/solve/mod.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
174174
search_graph: &mut search_graph,
175175
infcx: self,
176176
var_values: CanonicalVarValues::dummy(),
177+
in_projection_eq_hack: false,
177178
}
178179
.evaluate_goal(goal);
179180

@@ -187,6 +188,10 @@ struct EvalCtxt<'a, 'tcx> {
187188
var_values: CanonicalVarValues<'tcx>,
188189

189190
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
191+
192+
/// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
193+
/// see the comment in that method for more details.
194+
in_projection_eq_hack: bool,
190195
}
191196

192197
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
@@ -213,7 +218,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
213218
loop {
214219
let (ref infcx, goal, var_values) =
215220
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
216-
let mut ecx = EvalCtxt { infcx, var_values, search_graph };
221+
let mut ecx =
222+
EvalCtxt { infcx, var_values, search_graph, in_projection_eq_hack: false };
217223
let result = ecx.compute_goal(goal);
218224

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

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

compiler/rustc_trait_selection/src/solve/project_goals.rs

+12-2
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

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)