Skip to content

Commit 7455aa5

Browse files
committed
Auto merge of #114457 - lcnr:trait_ref_is_knowable-normalize, r=compiler-errors
normalize in `trait_ref_is_knowable` in new solver fixes rust-lang/trait-system-refactor-initiative#51 Alternatively we could avoid normalizing the self type and do this at the end of the `assemble_candidates_via_self_ty` stack by splitting candidates into: - applicable without normalizing self type - applicable for aliases, even if they can be normalized - applicable for stuff which cannot get normalized further I don't think this would have any significant benefits and it also seems non-trivial to avoid normalizing only the self type in `trait_ref_is_knowable`. r? `@compiler-errors`
2 parents cb0c299 + 5176288 commit 7455aa5

12 files changed

+270
-119
lines changed

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

+35-19
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
316316

317317
self.assemble_param_env_candidates(goal, &mut candidates);
318318

319+
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
320+
319321
candidates
320322
}
321323

@@ -363,10 +365,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
363365

364366
self.assemble_object_bound_candidates(goal, &mut candidates);
365367

366-
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
367-
368368
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates, num_steps);
369-
370369
candidates
371370
}
372371

@@ -877,26 +876,43 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
877876
goal: Goal<'tcx, G>,
878877
candidates: &mut Vec<Candidate<'tcx>>,
879878
) {
879+
let tcx = self.tcx();
880880
match self.solver_mode() {
881881
SolverMode::Normal => return,
882-
SolverMode::Coherence => {
883-
let trait_ref = goal.predicate.trait_ref(self.tcx());
884-
match coherence::trait_ref_is_knowable(self.tcx(), trait_ref) {
885-
Ok(()) => {}
886-
Err(_) => match self
887-
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
888-
{
889-
Ok(result) => candidates.push(Candidate {
890-
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
891-
result,
892-
}),
893-
// FIXME: This will be reachable at some point if we're in
894-
// `assemble_candidates_after_normalizing_self_ty` and we get a
895-
// universe error. We'll deal with it at this point.
896-
Err(NoSolution) => bug!("coherence candidate resulted in NoSolution"),
897-
},
882+
SolverMode::Coherence => {}
883+
};
884+
885+
let result = self.probe_candidate("coherence unknowable").enter(|ecx| {
886+
let trait_ref = goal.predicate.trait_ref(tcx);
887+
888+
#[derive(Debug)]
889+
enum FailureKind {
890+
Overflow,
891+
NoSolution(NoSolution),
892+
}
893+
let lazily_normalize_ty = |ty| match ecx.try_normalize_ty(goal.param_env, ty) {
894+
Ok(Some(ty)) => Ok(ty),
895+
Ok(None) => Err(FailureKind::Overflow),
896+
Err(e) => Err(FailureKind::NoSolution(e)),
897+
};
898+
899+
match coherence::trait_ref_is_knowable(tcx, trait_ref, lazily_normalize_ty) {
900+
Err(FailureKind::Overflow) => {
901+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW)
902+
}
903+
Err(FailureKind::NoSolution(NoSolution)) | Ok(Ok(())) => Err(NoSolution),
904+
Ok(Err(_)) => {
905+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
898906
}
899907
}
908+
});
909+
910+
match result {
911+
Ok(result) => candidates.push(Candidate {
912+
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
913+
result,
914+
}),
915+
Err(NoSolution) => {}
900916
}
901917
}
902918

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+49-33
Original file line numberDiff line numberDiff line change
@@ -388,44 +388,60 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
388388
&& is_normalizes_to_hack == IsNormalizesToHack::No
389389
&& !self.search_graph.in_cycle()
390390
{
391-
debug!("rerunning goal to check result is stable");
392-
self.search_graph.reset_encountered_overflow(encountered_overflow);
393-
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
394-
let Ok(new_canonical_response) = EvalCtxt::evaluate_canonical_goal(
395-
self.tcx(),
396-
self.search_graph,
397-
canonical_goal,
398-
// FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
399-
&mut ProofTreeBuilder::new_noop(),
400-
) else {
401-
bug!(
402-
"goal went from {certainty:?} to error: re-canonicalized goal={canonical_goal:#?} \
403-
first_response={canonical_response:#?},
404-
second response was error"
405-
);
406-
};
407-
// We only check for modulo regions as we convert all regions in
408-
// the input to new existentials, even if they're expected to be
409-
// `'static` or a placeholder region.
410-
if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
411-
bug!(
412-
"unstable result: re-canonicalized goal={canonical_goal:#?} \
413-
first_response={canonical_response:#?} \
414-
second_response={new_canonical_response:#?}"
415-
);
416-
}
417-
if certainty != new_canonical_response.value.certainty {
418-
bug!(
419-
"unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
420-
first_response={canonical_response:#?} \
421-
second_response={new_canonical_response:#?}"
422-
);
423-
}
391+
// The nested evaluation has to happen with the original state
392+
// of `encountered_overflow`.
393+
let from_original_evaluation =
394+
self.search_graph.reset_encountered_overflow(encountered_overflow);
395+
self.check_evaluate_goal_stable_result(goal, canonical_goal, canonical_response);
396+
// In case the evaluation was unstable, we manually make sure that this
397+
// debug check does not influence the result of the parent goal.
398+
self.search_graph.reset_encountered_overflow(from_original_evaluation);
424399
}
425400

426401
Ok((has_changed, certainty, nested_goals))
427402
}
428403

404+
fn check_evaluate_goal_stable_result(
405+
&mut self,
406+
goal: Goal<'tcx, ty::Predicate<'tcx>>,
407+
original_input: CanonicalInput<'tcx>,
408+
original_result: CanonicalResponse<'tcx>,
409+
) {
410+
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
411+
let result = EvalCtxt::evaluate_canonical_goal(
412+
self.tcx(),
413+
self.search_graph,
414+
canonical_goal,
415+
// FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
416+
&mut ProofTreeBuilder::new_noop(),
417+
);
418+
419+
macro_rules! fail {
420+
($msg:expr) => {{
421+
let msg = $msg;
422+
warn!(
423+
"unstable result: {msg}\n\
424+
original goal: {original_input:?},\n\
425+
original result: {original_result:?}\n\
426+
re-canonicalized goal: {canonical_goal:?}\n\
427+
second response: {result:?}"
428+
);
429+
return;
430+
}};
431+
}
432+
433+
let Ok(new_canonical_response) = result else { fail!("second response was error") };
434+
// We only check for modulo regions as we convert all regions in
435+
// the input to new existentials, even if they're expected to be
436+
// `'static` or a placeholder region.
437+
if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
438+
fail!("additional constraints from second response")
439+
}
440+
if original_result.value.certainty != new_canonical_response.value.certainty {
441+
fail!("unstable certainty")
442+
}
443+
}
444+
429445
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
430446
let Goal { param_env, predicate } = goal;
431447
let kind = predicate.kind();

compiler/rustc_trait_selection/src/solve/mod.rs

+31
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,37 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
283283

284284
Ok(self.make_ambiguous_response_no_constraints(maybe_cause))
285285
}
286+
287+
/// Normalize a type when it is structually matched on.
288+
///
289+
/// For self types this is generally already handled through
290+
/// `assemble_candidates_after_normalizing_self_ty`, so anything happening
291+
/// in [`EvalCtxt::assemble_candidates_via_self_ty`] does not have to normalize
292+
/// the self type. It is required when structurally matching on any other
293+
/// arguments of a trait goal, e.g. when assembling builtin unsize candidates.
294+
fn try_normalize_ty(
295+
&mut self,
296+
param_env: ty::ParamEnv<'tcx>,
297+
mut ty: Ty<'tcx>,
298+
) -> Result<Option<Ty<'tcx>>, NoSolution> {
299+
for _ in 0..self.local_overflow_limit() {
300+
let ty::Alias(_, projection_ty) = *ty.kind() else {
301+
return Ok(Some(ty));
302+
};
303+
304+
let normalized_ty = self.next_ty_infer();
305+
let normalizes_to_goal = Goal::new(
306+
self.tcx(),
307+
param_env,
308+
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
309+
);
310+
self.add_goal(normalizes_to_goal);
311+
self.try_evaluate_added_goals()?;
312+
ty = self.resolve_vars_if_possible(normalized_ty);
313+
}
314+
315+
Ok(None)
316+
}
286317
}
287318

288319
fn response_no_constraints_raw<'tcx>(

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,13 @@ impl<'tcx> SearchGraph<'tcx> {
134134
/// Resets `encountered_overflow` of the current goal.
135135
///
136136
/// This should only be used for the check in `evaluate_goal`.
137-
pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) {
138-
if encountered_overflow {
139-
self.stack.raw.last_mut().unwrap().encountered_overflow = true;
137+
pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) -> bool {
138+
if let Some(last) = self.stack.raw.last_mut() {
139+
let prev = last.encountered_overflow;
140+
last.encountered_overflow = encountered_overflow;
141+
prev
142+
} else {
143+
false
140144
}
141145
}
142146

compiler/rustc_trait_selection/src/solve/trait_goals.rs

+1-38
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
448448
// We need to normalize the b_ty since it's matched structurally
449449
// in the other functions below.
450450
let b_ty = match ecx
451-
.normalize_non_self_ty(goal.predicate.trait_ref.args.type_at(1), goal.param_env)
451+
.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
452452
{
453453
Ok(Some(b_ty)) => b_ty,
454454
Ok(None) => return vec![misc_candidate(ecx, Certainty::OVERFLOW)],
@@ -927,41 +927,4 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
927927
let candidates = self.assemble_and_evaluate_candidates(goal);
928928
self.merge_candidates(candidates)
929929
}
930-
931-
/// Normalize a non-self type when it is structually matched on when solving
932-
/// a built-in goal.
933-
///
934-
/// This is handled already through `assemble_candidates_after_normalizing_self_ty`
935-
/// for the self type, but for other goals, additional normalization of other
936-
/// arguments may be needed to completely implement the semantics of the trait.
937-
///
938-
/// This is required when structurally matching on any trait argument that is
939-
/// not the self type.
940-
fn normalize_non_self_ty(
941-
&mut self,
942-
mut ty: Ty<'tcx>,
943-
param_env: ty::ParamEnv<'tcx>,
944-
) -> Result<Option<Ty<'tcx>>, NoSolution> {
945-
if !matches!(ty.kind(), ty::Alias(..)) {
946-
return Ok(Some(ty));
947-
}
948-
949-
for _ in 0..self.local_overflow_limit() {
950-
let ty::Alias(_, projection_ty) = *ty.kind() else {
951-
return Ok(Some(ty));
952-
};
953-
954-
let normalized_ty = self.next_ty_infer();
955-
let normalizes_to_goal = Goal::new(
956-
self.tcx(),
957-
param_env,
958-
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
959-
);
960-
self.add_goal(normalizes_to_goal);
961-
self.try_evaluate_added_goals()?;
962-
ty = self.resolve_vars_if_possible(normalized_ty);
963-
}
964-
965-
Ok(None)
966-
}
967930
}

0 commit comments

Comments
 (0)