trait_sel: prefer only nested alias bounds
This commit is contained in:
parent
c50aebba78
commit
676d9cfa8b
11 changed files with 138 additions and 57 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
30
tests/ui/sized-hierarchy/prefer-non-nested-alias-bound.rs
Normal file
30
tests/ui/sized-hierarchy/prefer-non-nested-alias-bound.rs
Normal 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>();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue