Skip to content

Commit 485fd38

Browse files
authored
Rollup merge of rust-lang#129896 - lcnr:bail-on-unknowable, r=jackh726
do not attempt to prove unknowable goals In case a goal is unknowable, we previously still checked all other possible ways to prove this goal, even though its final result is already guaranteed to be ambiguous. By ignoring all other candidates in that case we can avoid a lot of unnecessary work, fixing the performance regression in typenum found in rust-lang#121848. This is already the behavior in the old solver. This could in theory cause future-compatability issues as considering fewer goals unknowable may end up causing performance regressions/hangs. I am quite confident that this will not be an issue. r? ``@compiler-errors``
2 parents 2f6e855 + 6188aae commit 485fd38

File tree

5 files changed

+101
-109
lines changed

5 files changed

+101
-109
lines changed

Diff for: compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

+32-34
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ where
304304

305305
let mut candidates = vec![];
306306

307+
if self.solver_mode() == SolverMode::Coherence {
308+
if let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal) {
309+
return vec![candidate];
310+
}
311+
}
307312
self.assemble_impl_candidates(goal, &mut candidates);
308313

309314
self.assemble_builtin_impl_candidates(goal, &mut candidates);
@@ -314,11 +319,8 @@ where
314319

315320
self.assemble_param_env_candidates(goal, &mut candidates);
316321

317-
match self.solver_mode() {
318-
SolverMode::Normal => self.discard_impls_shadowed_by_env(goal, &mut candidates),
319-
SolverMode::Coherence => {
320-
self.assemble_coherence_unknowable_candidates(goal, &mut candidates)
321-
}
322+
if self.solver_mode() == SolverMode::Normal {
323+
self.discard_impls_shadowed_by_env(goal, &mut candidates);
322324
}
323325

324326
candidates
@@ -682,38 +684,34 @@ where
682684
/// also consider impls which may get added in a downstream or sibling crate
683685
/// or which an upstream impl may add in a minor release.
684686
///
685-
/// To do so we add an ambiguous candidate in case such an unknown impl could
686-
/// apply to the current goal.
687+
/// To do so we return a single ambiguous candidate in case such an unknown
688+
/// impl could apply to the current goal.
687689
#[instrument(level = "trace", skip_all)]
688-
fn assemble_coherence_unknowable_candidates<G: GoalKind<D>>(
690+
fn consider_coherence_unknowable_candidate<G: GoalKind<D>>(
689691
&mut self,
690692
goal: Goal<I, G>,
691-
candidates: &mut Vec<Candidate<I>>,
692-
) {
693-
let cx = self.cx();
694-
695-
candidates.extend(self.probe_trait_candidate(CandidateSource::CoherenceUnknowable).enter(
696-
|ecx| {
697-
let trait_ref = goal.predicate.trait_ref(cx);
698-
if ecx.trait_ref_is_knowable(goal.param_env, trait_ref)? {
699-
Err(NoSolution)
700-
} else {
701-
// While the trait bound itself may be unknowable, we may be able to
702-
// prove that a super trait is not implemented. For this, we recursively
703-
// prove the super trait bounds of the current goal.
704-
//
705-
// We skip the goal itself as that one would cycle.
706-
let predicate: I::Predicate = trait_ref.upcast(cx);
707-
ecx.add_goals(
708-
GoalSource::Misc,
709-
elaborate::elaborate(cx, [predicate])
710-
.skip(1)
711-
.map(|predicate| goal.with(cx, predicate)),
712-
);
713-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
714-
}
715-
},
716-
))
693+
) -> Result<Candidate<I>, NoSolution> {
694+
self.probe_trait_candidate(CandidateSource::CoherenceUnknowable).enter(|ecx| {
695+
let cx = ecx.cx();
696+
let trait_ref = goal.predicate.trait_ref(cx);
697+
if ecx.trait_ref_is_knowable(goal.param_env, trait_ref)? {
698+
Err(NoSolution)
699+
} else {
700+
// While the trait bound itself may be unknowable, we may be able to
701+
// prove that a super trait is not implemented. For this, we recursively
702+
// prove the super trait bounds of the current goal.
703+
//
704+
// We skip the goal itself as that one would cycle.
705+
let predicate: I::Predicate = trait_ref.upcast(cx);
706+
ecx.add_goals(
707+
GoalSource::Misc,
708+
elaborate::elaborate(cx, [predicate])
709+
.skip(1)
710+
.map(|predicate| goal.with(cx, predicate)),
711+
);
712+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
713+
}
714+
})
717715
}
718716

719717
/// If there's a where-bound for the current goal, do not use any impl candidates

Diff for: compiler/rustc_trait_selection/src/traits/coherence.rs

+66-72
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::infer::outlives::env::OutlivesEnvironment;
2929
use crate::infer::InferOk;
3030
use crate::solve::inspect::{InspectGoal, ProofTreeInferCtxtExt, ProofTreeVisitor};
3131
use crate::solve::{deeply_normalize_for_diagnostics, inspect};
32+
use crate::traits::query::evaluate_obligation::InferCtxtExt;
3233
use crate::traits::select::IntercrateAmbiguityCause;
3334
use crate::traits::{
3435
util, FulfillmentErrorCode, NormalizeExt, Obligation, ObligationCause, PredicateObligation,
@@ -624,14 +625,13 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a, 'tcx> {
624625
// at ambiguous goals, as for others the coherence unknowable candidate
625626
// was irrelevant.
626627
match goal.result() {
627-
Ok(Certainty::Maybe(_)) => {}
628628
Ok(Certainty::Yes) | Err(NoSolution) => return,
629+
Ok(Certainty::Maybe(_)) => {}
629630
}
630631

631-
let Goal { param_env, predicate } = goal.goal();
632-
633632
// For bound predicates we simply call `infcx.enter_forall`
634633
// and then prove the resulting predicate as a nested goal.
634+
let Goal { param_env, predicate } = goal.goal();
635635
let trait_ref = match predicate.kind().no_bound_vars() {
636636
Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(tr))) => tr.trait_ref,
637637
Some(ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj)))
@@ -645,7 +645,11 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a, 'tcx> {
645645
_ => return,
646646
};
647647

648-
// Add ambiguity causes for reservation impls.
648+
if trait_ref.references_error() {
649+
return;
650+
}
651+
652+
let mut candidates = goal.candidates();
649653
for cand in goal.candidates() {
650654
if let inspect::ProbeKind::TraitCandidate {
651655
source: CandidateSource::Impl(def_id),
@@ -664,78 +668,68 @@ impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a, 'tcx> {
664668
}
665669
}
666670

667-
// Add ambiguity causes for unknowable goals.
668-
let mut ambiguity_cause = None;
669-
for cand in goal.candidates() {
670-
if let inspect::ProbeKind::TraitCandidate {
671-
source: CandidateSource::CoherenceUnknowable,
672-
result: Ok(_),
673-
} = cand.kind()
674-
{
675-
let lazily_normalize_ty = |mut ty: Ty<'tcx>| {
676-
if matches!(ty.kind(), ty::Alias(..)) {
677-
let ocx = ObligationCtxt::new(infcx);
678-
ty = ocx
679-
.structurally_normalize(&ObligationCause::dummy(), param_env, ty)
680-
.map_err(|_| ())?;
681-
if !ocx.select_where_possible().is_empty() {
682-
return Err(());
683-
}
684-
}
685-
Ok(ty)
686-
};
671+
// We also look for unknowable candidates. In case a goal is unknowable, there's
672+
// always exactly 1 candidate.
673+
let Some(cand) = candidates.pop() else {
674+
return;
675+
};
687676

688-
infcx.probe(|_| {
689-
match trait_ref_is_knowable(infcx, trait_ref, lazily_normalize_ty) {
690-
Err(()) => {}
691-
Ok(Ok(())) => warn!("expected an unknowable trait ref: {trait_ref:?}"),
692-
Ok(Err(conflict)) => {
693-
if !trait_ref.references_error() {
694-
// Normalize the trait ref for diagnostics, ignoring any errors if this fails.
695-
let trait_ref =
696-
deeply_normalize_for_diagnostics(infcx, param_env, trait_ref);
697-
698-
let self_ty = trait_ref.self_ty();
699-
let self_ty = self_ty.has_concrete_skeleton().then(|| self_ty);
700-
ambiguity_cause = Some(match conflict {
701-
Conflict::Upstream => {
702-
IntercrateAmbiguityCause::UpstreamCrateUpdate {
703-
trait_ref,
704-
self_ty,
705-
}
706-
}
707-
Conflict::Downstream => {
708-
IntercrateAmbiguityCause::DownstreamCrate {
709-
trait_ref,
710-
self_ty,
711-
}
712-
}
713-
});
714-
}
715-
}
716-
}
717-
})
718-
} else {
719-
match cand.result() {
720-
// We only add an ambiguity cause if the goal would otherwise
721-
// result in an error.
722-
//
723-
// FIXME: While this matches the behavior of the
724-
// old solver, it is not the only way in which the unknowable
725-
// candidates *weaken* coherence, they can also force otherwise
726-
// successful normalization to be ambiguous.
727-
Ok(Certainty::Maybe(_) | Certainty::Yes) => {
728-
ambiguity_cause = None;
729-
break;
730-
}
731-
Err(NoSolution) => continue,
677+
let inspect::ProbeKind::TraitCandidate {
678+
source: CandidateSource::CoherenceUnknowable,
679+
result: Ok(_),
680+
} = cand.kind()
681+
else {
682+
return;
683+
};
684+
685+
let lazily_normalize_ty = |mut ty: Ty<'tcx>| {
686+
if matches!(ty.kind(), ty::Alias(..)) {
687+
let ocx = ObligationCtxt::new(infcx);
688+
ty = ocx
689+
.structurally_normalize(&ObligationCause::dummy(), param_env, ty)
690+
.map_err(|_| ())?;
691+
if !ocx.select_where_possible().is_empty() {
692+
return Err(());
732693
}
733694
}
734-
}
695+
Ok(ty)
696+
};
735697

736-
if let Some(ambiguity_cause) = ambiguity_cause {
737-
self.causes.insert(ambiguity_cause);
738-
}
698+
infcx.probe(|_| {
699+
let conflict = match trait_ref_is_knowable(infcx, trait_ref, lazily_normalize_ty) {
700+
Err(()) => return,
701+
Ok(Ok(())) => {
702+
warn!("expected an unknowable trait ref: {trait_ref:?}");
703+
return;
704+
}
705+
Ok(Err(conflict)) => conflict,
706+
};
707+
708+
// It is only relevant that a goal is unknowable if it would have otherwise
709+
// failed.
710+
let non_intercrate_infcx = infcx.fork_with_intercrate(false);
711+
if non_intercrate_infcx.predicate_may_hold(&Obligation::new(
712+
infcx.tcx,
713+
ObligationCause::dummy(),
714+
param_env,
715+
predicate,
716+
)) {
717+
return;
718+
}
719+
720+
// Normalize the trait ref for diagnostics, ignoring any errors if this fails.
721+
let trait_ref = deeply_normalize_for_diagnostics(infcx, param_env, trait_ref);
722+
let self_ty = trait_ref.self_ty();
723+
let self_ty = self_ty.has_concrete_skeleton().then(|| self_ty);
724+
self.causes.insert(match conflict {
725+
Conflict::Upstream => {
726+
IntercrateAmbiguityCause::UpstreamCrateUpdate { trait_ref, self_ty }
727+
}
728+
Conflict::Downstream => {
729+
IntercrateAmbiguityCause::DownstreamCrate { trait_ref, self_ty }
730+
}
731+
});
732+
});
739733
}
740734
}
741735

Diff for: compiler/rustc_type_ir/src/solve/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub enum Reveal {
5858
All,
5959
}
6060

61-
#[derive(Debug, Clone, Copy)]
61+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6262
pub enum SolverMode {
6363
/// Ordinary trait solving, using everywhere except for coherence.
6464
Normal,

Diff for: tests/ui/coherence/normalize-for-errors.next.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ LL |
77
LL | impl<S: Iterator> MyTrait<S> for (Box<<(MyType,) as Mirror>::Assoc>, S::Item) {}
88
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `(Box<(MyType,)>, <_ as Iterator>::Item)`
99
|
10-
= note: upstream crates may add a new impl of trait `std::clone::Clone` for type `(MyType,)` in future versions
10+
= note: upstream crates may add a new impl of trait `std::clone::Clone` for type `std::boxed::Box<(MyType,)>` in future versions
1111
= note: upstream crates may add a new impl of trait `std::marker::Copy` for type `std::boxed::Box<(MyType,)>` in future versions
1212

1313
error: aborting due to 1 previous error

Diff for: tests/ui/coherence/normalize-for-errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ impl<S: Iterator> MyTrait<S> for (Box<<(MyType,) as Mirror>::Assoc>, S::Item) {}
1818
//~^ ERROR conflicting implementations of trait `MyTrait<_>` for type `(Box<(MyType,)>,
1919
//~| NOTE conflicting implementation for `(Box<(MyType,)>,
2020
//~| NOTE upstream crates may add a new impl of trait `std::marker::Copy` for type `std::boxed::Box<(MyType,)>` in future versions
21-
//[next]~| NOTE upstream crates may add a new impl of trait `std::clone::Clone` for type `(MyType,)` in future versions
21+
//[next]~| NOTE upstream crates may add a new impl of trait `std::clone::Clone` for type `std::boxed::Box<(MyType,)>` in future versions
2222

2323
fn main() {}

0 commit comments

Comments
 (0)