Only prefer Sized candidates, and only if they certainly hold

This commit is contained in:
Michael Goulet 2025-03-10 16:50:29 +00:00
parent f9696dda6e
commit aebbd42460
7 changed files with 89 additions and 23 deletions

View file

@ -1062,8 +1062,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
candidates: &mut SelectionCandidateSet<'tcx>,
) {
match self.sized_conditions(obligation) {
BuiltinImplConditions::Where(_) => {
candidates.vec.push(SizedCandidate);
BuiltinImplConditions::Where(nested) => {
candidates
.vec
.push(SizedCandidate { has_nested: !nested.skip_binder().is_empty() });
}
BuiltinImplConditions::None => {}
BuiltinImplConditions::Ambiguous => {

View file

@ -40,8 +40,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
candidate: SelectionCandidate<'tcx>,
) -> Result<Selection<'tcx>, SelectionError<'tcx>> {
let mut impl_src = match candidate {
SizedCandidate => {
let data = self.confirm_builtin_candidate(obligation, true);
SizedCandidate { has_nested } => {
let data = self.confirm_builtin_candidate(obligation, has_nested);
ImplSource::Builtin(BuiltinImplSource::Misc, data)
}

View file

@ -1803,25 +1803,19 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
// We prefer `Sized` candidates over everything.
let mut sized_candidates =
candidates.iter().filter(|c| matches!(c.candidate, SizedCandidate));
if let Some(_sized_candidate) = sized_candidates.next() {
candidates.iter().filter(|c| matches!(c.candidate, SizedCandidate { has_nested: _ }));
if let Some(sized_candidate) = sized_candidates.next() {
// There should only ever be a single sized candidate
// as they would otherwise overlap.
debug_assert_eq!(sized_candidates.next(), None);
return Some(SizedCandidate);
}
// We prefer trivial builtin candidates, i.e. builtin impls without any nested
// requirements, over all others. This is a fix for #53123 and prevents winnowing
// from accidentally extending the lifetime of a variable.
let mut trivial_builtin = candidates
.iter()
.filter(|c| matches!(c.candidate, BuiltinCandidate { has_nested: false }));
if let Some(_trivial) = trivial_builtin.next() {
// There should only ever be a single trivial builtin candidate
// as they would otherwise overlap.
debug_assert_eq!(trivial_builtin.next(), None);
return Some(BuiltinCandidate { has_nested: false });
// Only prefer the built-in `Sized` candidate if its nested goals are certain.
// Otherwise, we may encounter failure later on if inference causes this candidate
// to not hold, but a where clause would've applied instead.
if sized_candidate.evaluation.must_apply_modulo_regions() {
return Some(sized_candidate.candidate.clone());
} else {
return None;
}
}
// Before we consider where-bounds, we have to deduplicate them here and also
@ -1950,7 +1944,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
// Don't use impl candidates which overlap with other candidates.
// This should pretty much only ever happen with malformed impls.
if candidates.iter().all(|c| match c.candidate {
SizedCandidate
SizedCandidate { has_nested: _ }
| BuiltinCandidate { has_nested: _ }
| TransmutabilityCandidate
| AutoImplCandidate