Skip to content

Commit fea32d4

Browse files
authored
Rollup merge of rust-lang#108896 - BoxyUwU:new_solver_add_goal_fn, r=lcnr
new solver: make all goal evaluation able to be automatically rerun It is generally wrong to call `evaluate_goal` multiple times or `evaluate_goal` and `evaluate_all` for the same `QueryResult` without correctly handling rerunning the goals when inference makes progress. Not doing so will result in the assertion in `evaluate_goal` firing because rerunning the goal will lead to a more accurate `QueryResult`. Currently there are lots of places that get this wrong and generally it is complex and error prone to handle correctly everywhere. This PR introduces a way to add goals to the `EvalCtxt` and then run all the added goals in a loop so that `evaluate_goal`/`evaluate_all` is not necessary to call manually. There are a few complications for making everything work "right": 1. the `normalizes-to` hack that replaces the rhs with an unconstrained infer var requires special casing in the new `try_evaluate_added_goals` function similar to how `evaluate_goal`'s assertion special cases that hack. 2. `assemble_candidates_after_normalizing_self_ty`'s normalization step needs to be reran for each candidate otherwise the found candidates will potentially get a more accurate `QueryResult` when rerunning the projection/trait goal which can effect the `QueryResult` of the projection/trait goal. This is implemented via `EvalCtxt::probe`'s closure's `EvalCtxt` inheriting the added goals of the `EvalCtxt` that `probe` is called on, allowing us to add goals in a probe, and then enter a nested probe for each candidate and evaluate added goals which include the normalization step's goals. I made `make_canonical_response` evaluate added goals so that it will be hard to mess up the impl of the solver by forgetting to evaluate added goals. Right now the only way to mess this up would be to call `response_no_constraints` (which from the name is obviously weird). The visibility of `evaluate_goal` means that it can be called from various `compute_x_goal` or candidate assembly functions, this is generally wrong and we should never call `evaluate_goal` manually, instead we should be calling `add_goal`/`add_goals`. This is solved by moving `evaluate_goal` `evaluate_canonical_goal` and `compute_goal` into `eval_ctxt`'s module and making them private so they cannot be called from elsewhere, forcing people to call `add_goal/s` and `evaluate_added_goals_and_make_canonical_resposne`/`try_evaluate_added_goals` --- Other changes: - removed the `&& false` that was introduced to the assertion in `evaluate_goal` in rust-lang#108839 - remove a `!self.did_overflow()` requirement in `search_graph.is_empty()` which causes goals that overflow to ICE - made `EvalCtxt::eq` take `&mut self` and add all the nested goals via `add_goals` instead of returning them as 99% of call sites just immediately called `EvalCtxt::add_goals` manually. r? `@lcnr`
2 parents dffc706 + b85bc19 commit fea32d4

File tree

8 files changed

+531
-443
lines changed

8 files changed

+531
-443
lines changed

compiler/rustc_trait_selection/src/solve/assembly.rs

+20-27
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
224224
if goal.predicate.self_ty().is_ty_var() {
225225
return vec![Candidate {
226226
source: CandidateSource::BuiltinImpl,
227-
result: self.make_canonical_response(Certainty::AMBIGUOUS).unwrap(),
227+
result: self
228+
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
229+
.unwrap(),
228230
}];
229231
}
230232

@@ -261,37 +263,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
261263
let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else {
262264
return
263265
};
264-
self.probe(|this| {
265-
let normalized_ty = this.next_ty_infer();
266+
267+
self.probe(|ecx| {
268+
let normalized_ty = ecx.next_ty_infer();
266269
let normalizes_to_goal = goal.with(
267270
tcx,
268271
ty::Binder::dummy(ty::ProjectionPredicate {
269272
projection_ty,
270273
term: normalized_ty.into(),
271274
}),
272275
);
273-
let normalization_certainty = match this.evaluate_goal(normalizes_to_goal) {
274-
Ok((_, certainty)) => certainty,
275-
Err(NoSolution) => return,
276-
};
277-
let normalized_ty = this.resolve_vars_if_possible(normalized_ty);
278-
279-
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
280-
// This doesn't work as long as we use `CandidateSource` in winnowing.
281-
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
282-
let normalized_candidates = this.assemble_and_evaluate_candidates(goal);
283-
for mut normalized_candidate in normalized_candidates {
284-
normalized_candidate.result =
285-
normalized_candidate.result.unchecked_map(|mut response| {
286-
// FIXME: This currently hides overflow in the normalization step of the self type
287-
// which is probably wrong. Maybe `unify_and` should actually keep overflow as
288-
// we treat it as non-fatal anyways.
289-
response.certainty = response.certainty.unify_and(normalization_certainty);
290-
response
291-
});
292-
candidates.push(normalized_candidate);
276+
ecx.add_goal(normalizes_to_goal);
277+
if let Ok(_) = ecx.try_evaluate_added_goals() {
278+
let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty);
279+
280+
// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
281+
// This doesn't work as long as we use `CandidateSource` in winnowing.
282+
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
283+
candidates.extend(ecx.assemble_and_evaluate_candidates(goal));
293284
}
294-
})
285+
});
295286
}
296287

297288
fn assemble_impl_candidates<G: GoalKind<'tcx>>(
@@ -516,7 +507,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
516507
} else {
517508
Certainty::AMBIGUOUS
518509
};
519-
return self.make_canonical_response(certainty);
510+
return self.evaluate_added_goals_and_make_canonical_response(certainty);
520511
}
521512
}
522513

@@ -538,14 +529,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
538529
}
539530
}
540531

541-
fn discard_reservation_impl(&self, mut candidate: Candidate<'tcx>) -> Candidate<'tcx> {
532+
fn discard_reservation_impl(&mut self, mut candidate: Candidate<'tcx>) -> Candidate<'tcx> {
542533
if let CandidateSource::Impl(def_id) = candidate.source {
543534
if let ty::ImplPolarity::Reservation = self.tcx().impl_polarity(def_id) {
544535
debug!("Selected reservation impl");
545536
// We assemble all candidates inside of a probe so by
546537
// making a new canonical response here our result will
547538
// have no constraints.
548-
candidate.result = self.make_canonical_response(Certainty::AMBIGUOUS).unwrap();
539+
candidate.result = self
540+
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
541+
.unwrap();
549542
}
550543
}
551544

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
4848
/// - `external_constraints`: additional constraints which aren't expressable
4949
/// using simple unification of inference variables.
5050
#[instrument(level = "debug", skip(self))]
51-
pub(super) fn make_canonical_response(&self, certainty: Certainty) -> QueryResult<'tcx> {
51+
pub(super) fn evaluate_added_goals_and_make_canonical_response(
52+
&mut self,
53+
certainty: Certainty,
54+
) -> QueryResult<'tcx> {
55+
let goals_certainty = self.try_evaluate_added_goals()?;
56+
let certainty = certainty.unify_and(goals_certainty);
57+
5258
let external_constraints = self.compute_external_query_constraints()?;
5359

5460
let response = Response { var_values: self.var_values, external_constraints, certainty };
@@ -209,7 +215,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
209215
// FIXME: To deal with #105787 I also expect us to emit nested obligations here at
210216
// some point. We can figure out how to deal with this once we actually have
211217
// an ICE.
212-
let nested_goals = self.eq(param_env, orig, response)?;
218+
let nested_goals = self.eq_and_get_goals(param_env, orig, response)?;
213219
assert!(nested_goals.is_empty(), "{nested_goals:?}");
214220
}
215221

0 commit comments

Comments
 (0)