trait_sel: prefer only nested alias bounds

This commit is contained in:
David Wood 2025-06-27 10:11:28 +00:00
parent c50aebba78
commit 676d9cfa8b
No known key found for this signature in database
11 changed files with 138 additions and 57 deletions

View file

@ -6,6 +6,7 @@ use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::DefId;
use rustc_macros::{HashStable, TypeVisitable};
use rustc_query_system::cache::Cache;
use rustc_type_ir::solve::AliasBoundKind;
use self::EvaluationResult::*;
use super::{SelectionError, SelectionResult};
@ -116,8 +117,13 @@ pub enum SelectionCandidate<'tcx> {
/// This is a trait matching with a projected type as `Self`, and we found
/// an applicable bound in the trait definition. The `usize` is an index
/// into the list returned by `tcx.item_bounds`.
ProjectionCandidate(usize),
/// into the list returned by `tcx.item_bounds` and the `AliasBoundKind`
/// is whether this is candidate from recursion on the self type of a
/// projection.
ProjectionCandidate {
idx: usize,
kind: AliasBoundKind,
},
/// Implementation of a `Fn`-family trait by one of the anonymous types
/// generated for an `||` expression.

View file

@ -9,7 +9,7 @@ use derive_where::derive_where;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::search_graph::CandidateHeadUsages;
use rustc_type_ir::solve::SizedTraitKind;
use rustc_type_ir::solve::{AliasBoundKind, SizedTraitKind};
use rustc_type_ir::{
self as ty, Interner, TypeFlags, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Upcast,
@ -27,11 +27,6 @@ use crate::solve::{
has_no_inference_or_external_constraints,
};
enum AliasBoundKind {
SelfBounds,
NonSelfBounds,
}
/// A candidate is a possible way to prove a goal.
///
/// It consists of both the `source`, which describes how that goal would be proven,
@ -451,7 +446,7 @@ where
matches!(
c.source,
CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)
| CandidateSource::AliasBound
| CandidateSource::AliasBound(_)
) && has_no_inference_or_external_constraints(c.result)
})
{
@ -711,7 +706,7 @@ where
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
{
candidates.push(Candidate {
source: CandidateSource::AliasBound,
source: CandidateSource::AliasBound(consider_self_bounds),
result,
head_usages: CandidateHeadUsages::default(),
});
@ -735,7 +730,7 @@ where
{
candidates.extend(G::probe_and_consider_implied_clause(
self,
CandidateSource::AliasBound,
CandidateSource::AliasBound(consider_self_bounds),
goal,
assumption,
[],
@ -750,7 +745,7 @@ where
{
candidates.extend(G::probe_and_consider_implied_clause(
self,
CandidateSource::AliasBound,
CandidateSource::AliasBound(consider_self_bounds),
goal,
assumption,
[],
@ -1030,7 +1025,7 @@ where
item_bound.fold_with(&mut ReplaceOpaque { cx: self.cx(), alias_ty, self_ty });
candidates.extend(G::probe_and_match_goal_against_assumption(
self,
CandidateSource::AliasBound,
CandidateSource::AliasBound(AliasBoundKind::SelfBounds),
goal,
assumption,
|ecx| {

View file

@ -4,8 +4,8 @@
use rustc_type_ir::fast_reject::DeepRejectCtxt;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::solve::SizedTraitKind;
use rustc_type_ir::solve::inspect::ProbeKind;
use rustc_type_ir::solve::{AliasBoundKind, SizedTraitKind};
use rustc_type_ir::{self as ty, Interner, TypingMode, elaborate};
use tracing::instrument;
@ -96,7 +96,7 @@ where
) {
candidates.extend(Self::probe_and_match_goal_against_assumption(
ecx,
CandidateSource::AliasBound,
CandidateSource::AliasBound(AliasBoundKind::SelfBounds),
goal,
clause,
|ecx| {

View file

@ -4,7 +4,9 @@ use rustc_type_ir::data_structures::IndexSet;
use rustc_type_ir::fast_reject::DeepRejectCtxt;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::solve::{CandidatePreferenceMode, CanonicalResponse, SizedTraitKind};
use rustc_type_ir::solve::{
AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, SizedTraitKind,
};
use rustc_type_ir::{
self as ty, Interner, Movability, PredicatePolarity, TraitPredicate, TraitRef,
TypeVisitableExt as _, TypingMode, Upcast as _, elaborate,
@ -1381,22 +1383,21 @@ where
return Ok((candidate.result, Some(TraitGoalProvenVia::Misc)));
}
let potential_alias_bound_response =
candidates.iter().any(|c| matches!(c.source, CandidateSource::AliasBound)).then(|| {
let alias_bounds: Vec<_> = candidates
.extract_if(.., |c| matches!(c.source, CandidateSource::AliasBound))
.collect();
if let Some((response, _)) = self.try_merge_candidates(&alias_bounds) {
(response, Some(TraitGoalProvenVia::AliasBound))
} else {
(self.bail_with_ambiguity(&alias_bounds), None)
}
});
// Extract non-nested alias bound candidates, will be preferred over where bounds if
// we're proving an auto-trait, sizedness trait or default trait.
if matches!(candidate_preference_mode, CandidatePreferenceMode::Marker)
&& let Some(alias_bound_response) = potential_alias_bound_response
&& candidates.iter().any(|c| {
matches!(c.source, CandidateSource::AliasBound(AliasBoundKind::SelfBounds))
})
{
return Ok(alias_bound_response);
let alias_bounds: Vec<_> = candidates
.extract_if(.., |c| matches!(c.source, CandidateSource::AliasBound(..)))
.collect();
return if let Some((response, _)) = self.try_merge_candidates(&alias_bounds) {
Ok((response, Some(TraitGoalProvenVia::AliasBound)))
} else {
Ok((self.bail_with_ambiguity(&alias_bounds), None))
};
}
// If there are non-global where-bounds, prefer where-bounds
@ -1446,8 +1447,16 @@ where
};
}
if let Some(response) = potential_alias_bound_response {
return Ok(response);
// Next, prefer any alias bound (nested or otherwise).
if candidates.iter().any(|c| matches!(c.source, CandidateSource::AliasBound(_))) {
let alias_bounds: Vec<_> = candidates
.extract_if(.., |c| matches!(c.source, CandidateSource::AliasBound(_)))
.collect();
return if let Some((response, _)) = self.try_merge_candidates(&alias_bounds) {
Ok((response, Some(TraitGoalProvenVia::AliasBound)))
} else {
Ok((self.bail_with_ambiguity(&alias_bounds), None))
};
}
self.filter_specialized_impls(AllowInferenceConstraints::No, &mut candidates);

View file

@ -126,7 +126,9 @@ fn candidate_should_be_dropped_in_favor_of<'tcx>(
// Prefer dyn candidates over non-dyn candidates. This is necessary to
// handle the unsoundness between `impl<T: ?Sized> Any for T` and `dyn Any: Any`.
(
CandidateSource::Impl(_) | CandidateSource::ParamEnv(_) | CandidateSource::AliasBound,
CandidateSource::Impl(_)
| CandidateSource::ParamEnv(_)
| CandidateSource::AliasBound(_),
CandidateSource::BuiltinImpl(BuiltinImplSource::Object { .. }),
) => true,
@ -175,7 +177,9 @@ fn to_selection<'tcx>(
})
}
CandidateSource::BuiltinImpl(builtin) => ImplSource::Builtin(builtin, nested),
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound => ImplSource::Param(nested),
CandidateSource::ParamEnv(_) | CandidateSource::AliasBound(_) => {
ImplSource::Param(nested)
}
CandidateSource::CoherenceUnknowable => {
span_bug!(span, "didn't expect to select an unknowable candidate")
}

View file

@ -741,7 +741,7 @@ fn assemble_candidates_from_trait_def<'cx, 'tcx>(
let mut ambiguous = false;
let _ = selcx.for_each_item_bound(
obligation.predicate.self_ty(),
|selcx, clause, _| {
|selcx, clause, _, _| {
let Some(clause) = clause.as_projection_clause() else {
return ControlFlow::Continue(());
};

View file

@ -208,7 +208,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let mut distinct_normalized_bounds = FxHashSet::default();
let _ = self.for_each_item_bound::<!>(
placeholder_trait_predicate.self_ty(),
|selcx, bound, idx| {
|selcx, bound, idx, alias_bound_kind| {
let Some(bound) = bound.as_trait_clause() else {
return ControlFlow::Continue(());
};
@ -230,12 +230,16 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
bound.map_bound(|pred| pred.trait_ref),
) {
Ok(None) => {
candidates.vec.push(ProjectionCandidate(idx));
candidates
.vec
.push(ProjectionCandidate { idx, kind: alias_bound_kind });
}
Ok(Some(normalized_trait))
if distinct_normalized_bounds.insert(normalized_trait) =>
{
candidates.vec.push(ProjectionCandidate(idx));
candidates
.vec
.push(ProjectionCandidate { idx, kind: alias_bound_kind });
}
_ => {}
}
@ -825,7 +829,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
ty::Alias(ty::Opaque, alias) => {
if candidates.vec.iter().any(|c| matches!(c, ProjectionCandidate(_))) {
if candidates.vec.iter().any(|c| matches!(c, ProjectionCandidate { .. })) {
// We do not generate an auto impl candidate for `impl Trait`s which already
// reference our auto trait.
//

View file

@ -67,7 +67,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSource::Builtin(BuiltinImplSource::Misc, data)
}
ProjectionCandidate(idx) => {
ProjectionCandidate { idx, .. } => {
let obligations = self.confirm_projection_candidate(obligation, idx)?;
ImplSource::Param(obligations)
}
@ -150,7 +150,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let candidate_predicate = self
.for_each_item_bound(
placeholder_self_ty,
|_, clause, clause_idx| {
|_, clause, clause_idx, _| {
if clause_idx == idx {
ControlFlow::Break(clause)
} else {

View file

@ -32,6 +32,7 @@ use rustc_middle::ty::{
SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingMode, Upcast, elaborate,
may_use_unstable_feature,
};
use rustc_next_trait_solver::solve::AliasBoundKind;
use rustc_span::{Symbol, sym};
use tracing::{debug, instrument, trace};
@ -1628,11 +1629,16 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
pub(super) fn for_each_item_bound<T>(
&mut self,
mut self_ty: Ty<'tcx>,
mut for_each: impl FnMut(&mut Self, ty::Clause<'tcx>, usize) -> ControlFlow<T, ()>,
mut for_each: impl FnMut(
&mut Self,
ty::Clause<'tcx>,
usize,
AliasBoundKind,
) -> ControlFlow<T, ()>,
on_ambiguity: impl FnOnce(),
) -> ControlFlow<T, ()> {
let mut idx = 0;
let mut in_parent_alias_type = false;
let mut alias_bound_kind = AliasBoundKind::SelfBounds;
loop {
let (kind, alias_ty) = match *self_ty.kind() {
@ -1648,14 +1654,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// share the same type as `self_ty`. This is because for truly rigid
// projections, we will never be able to equate, e.g. `<T as Tr>::A`
// with `<<T as Tr>::A as Tr>::A`.
let relevant_bounds = if in_parent_alias_type {
let relevant_bounds = if matches!(alias_bound_kind, AliasBoundKind::NonSelfBounds) {
self.tcx().item_non_self_bounds(alias_ty.def_id)
} else {
self.tcx().item_self_bounds(alias_ty.def_id)
};
for bound in relevant_bounds.instantiate(self.tcx(), alias_ty.args) {
for_each(self, bound, idx)?;
for_each(self, bound, idx, alias_bound_kind)?;
idx += 1;
}
@ -1665,7 +1671,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
return ControlFlow::Continue(());
}
in_parent_alias_type = true;
alias_bound_kind = AliasBoundKind::NonSelfBounds;
}
}
@ -1880,14 +1886,24 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
break;
}
let alias_bound = candidates
.iter()
.filter_map(|c| if let ProjectionCandidate(i) = c.candidate { Some(i) } else { None })
.try_reduce(|c1, c2| if has_non_region_infer { None } else { Some(c1.min(c2)) });
let mut alias_bounds = candidates.iter().filter_map(|c| {
if let ProjectionCandidate { idx, kind } = c.candidate {
Some((idx, kind))
} else {
None
}
});
// Extract non-nested alias bound candidates, will be preferred over where bounds if
// we're proving an auto-trait, sizedness trait or default trait.
if matches!(candidate_preference_mode, CandidatePreferenceMode::Marker) {
match alias_bound {
Some(Some(index)) => return Some(ProjectionCandidate(index)),
match alias_bounds
.clone()
.filter_map(|(idx, kind)| (kind == AliasBoundKind::SelfBounds).then_some(idx))
.try_reduce(|c1, c2| if has_non_region_infer { None } else { Some(c1.min(c2)) })
{
Some(Some(idx)) => {
return Some(ProjectionCandidate { idx, kind: AliasBoundKind::SelfBounds });
}
Some(None) => {}
None => return None,
}
@ -1926,8 +1942,16 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
// fairly arbitrary but once again necessary for backwards compatibility.
// If there are multiple applicable candidates which don't affect type inference,
// choose the one with the lowest index.
match alias_bound {
Some(Some(index)) => return Some(ProjectionCandidate(index)),
match alias_bounds.try_reduce(|(c1, k1), (c2, k2)| {
if has_non_region_infer {
None
} else if c1 < c2 {
Some((c1, k1))
} else {
Some((c2, k2))
}
}) {
Some(Some((idx, kind))) => return Some(ProjectionCandidate { idx, kind }),
Some(None) => {}
None => return None,
}
@ -2016,7 +2040,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
// Non-global param candidates have already been handled, global
// where-bounds get ignored.
ParamCandidate(_) | ImplCandidate(_) => true,
ProjectionCandidate(_) | ObjectCandidate(_) => unreachable!(),
ProjectionCandidate { .. } | ObjectCandidate(_) => unreachable!(),
}) {
return Some(ImplCandidate(def_id));
} else {

View file

@ -189,7 +189,7 @@ pub enum CandidateSource<I: Interner> {
/// let _y = x.clone();
/// }
/// ```
AliasBound,
AliasBound(AliasBoundKind),
/// A candidate that is registered only during coherence to represent some
/// yet-unknown impl that could be produced downstream without violating orphan
/// rules.
@ -207,6 +207,15 @@ pub enum ParamEnvSource {
Global,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[derive(TypeVisitable_Generic, TypeFoldable_Generic)]
pub enum AliasBoundKind {
/// Alias bound from the self type of a projection
SelfBounds,
// Alias bound having recursed on the self type of a projection
NonSelfBounds,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[cfg_attr(
feature = "nightly",

View file

@ -0,0 +1,30 @@
//@ check-pass
//@ compile-flags: --crate-type=lib
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
// Since #120752, also get alias-bound candidates from a nested self-type, so prefering
// alias-bound over where-bound candidates can be incorrect. This test checks that case and that
// we prefer non-nested alias-bound candidates over where-bound candidates over nested alias-bound
// candidates.
trait OtherTrait<'a> {
type Assoc: ?Sized;
}
trait Trait
where
<Self::Assoc as OtherTrait<'static>>::Assoc: Sized,
{
type Assoc: for<'a> OtherTrait<'a>;
}
fn impls_sized<T: Sized>() {}
fn foo<'a, T>()
where
T: Trait,
for<'hr> <T::Assoc as OtherTrait<'hr>>::Assoc: Sized,
{
impls_sized::<<T::Assoc as OtherTrait<'a>>::Assoc>();
}