For a rigid projection, recursively look at the self type's item bounds

This commit is contained in:
Michael Goulet
2024-02-01 23:48:04 +00:00
parent 98aa3624be
commit 22d582a38d
23 changed files with 273 additions and 431 deletions

View File

@@ -6,13 +6,16 @@
//!
//! [rustc dev guide]:https://rustc-dev-guide.rust-lang.org/traits/resolution.html#candidate-assembly
use std::ops::ControlFlow;
use hir::def_id::DefId;
use hir::LangItem;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_infer::traits::ObligationCause;
use rustc_infer::traits::{Obligation, PolyTraitObligation, SelectionError};
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use rustc_middle::ty::{self, ToPolyTraitRef, Ty, TypeVisitableExt};
use crate::traits;
use crate::traits::query::evaluate_obligation::InferCtxtExt;
@@ -158,11 +161,52 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
_ => return,
}
let result = self
.infcx
.probe(|_| self.match_projection_obligation_against_definition_bounds(obligation));
self.infcx.probe(|_| {
let poly_trait_predicate = self.infcx.resolve_vars_if_possible(obligation.predicate);
let placeholder_trait_predicate =
self.infcx.enter_forall_and_leak_universe(poly_trait_predicate);
debug!(?placeholder_trait_predicate);
candidates.vec.extend(result.into_iter().map(|idx| ProjectionCandidate(idx)));
// The bounds returned by `item_bounds` may contain duplicates after
// normalization, so try to deduplicate when possible to avoid
// unnecessary ambiguity.
let mut distinct_normalized_bounds = FxHashSet::default();
self.for_each_item_bound::<!>(
placeholder_trait_predicate.self_ty(),
|selcx, bound, idx| {
let Some(bound) = bound.as_trait_clause() else {
return ControlFlow::Continue(());
};
if bound.polarity() != placeholder_trait_predicate.polarity {
return ControlFlow::Continue(());
}
selcx.infcx.probe(|_| {
match selcx.match_normalize_trait_ref(
obligation,
bound.to_poly_trait_ref(),
placeholder_trait_predicate.trait_ref,
) {
Ok(None) => {
candidates.vec.push(ProjectionCandidate(idx));
}
Ok(Some(normalized_trait))
if distinct_normalized_bounds.insert(normalized_trait) =>
{
candidates.vec.push(ProjectionCandidate(idx));
}
_ => {}
}
});
ControlFlow::Continue(())
},
|| {
// On ambiguity.
candidates.ambiguous = true;
},
);
});
}
/// Given an obligation like `<SomeTrait for T>`, searches the obligations that the caller

View File

@@ -162,20 +162,26 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
self.infcx.enter_forall_and_leak_universe(trait_predicate).trait_ref;
let placeholder_self_ty = placeholder_trait_predicate.self_ty();
let placeholder_trait_predicate = ty::Binder::dummy(placeholder_trait_predicate);
let (def_id, args) = match *placeholder_self_ty.kind() {
// Excluding IATs and type aliases here as they don't have meaningful item bounds.
ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
(def_id, args)
}
_ => bug!("projection candidate for unexpected type: {:?}", placeholder_self_ty),
};
let candidate_predicate =
tcx.item_bounds(def_id).map_bound(|i| i[idx]).instantiate(tcx, args);
let candidate_predicate = self
.for_each_item_bound(
placeholder_self_ty,
|_, clause, clause_idx| {
if clause_idx == idx {
ControlFlow::Break(clause)
} else {
ControlFlow::Continue(())
}
},
|| unreachable!(),
)
.break_value()
.expect("expected to index into clause that exists");
let candidate = candidate_predicate
.as_trait_clause()
.expect("projection candidate is not a trait predicate")
.map_bound(|t| t.trait_ref);
let mut obligations = Vec::new();
let candidate = normalize_with_depth_to(
self,
@@ -194,8 +200,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
.map_err(|_| Unimplemented)
})?);
if let ty::Alias(ty::Projection, ..) = placeholder_self_ty.kind() {
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, args);
// FIXME(compiler-errors): I don't think this is needed.
if let ty::Alias(ty::Projection, alias_ty) = placeholder_self_ty.kind() {
let predicates = tcx.predicates_of(alias_ty.def_id).instantiate_own(tcx, alias_ty.args);
for (predicate, _) in predicates {
let normalized = normalize_with_depth_to(
self,

View File

@@ -52,6 +52,7 @@ use std::cell::{Cell, RefCell};
use std::cmp;
use std::fmt::{self, Display};
use std::iter;
use std::ops::ControlFlow;
pub use rustc_middle::traits::select::*;
use rustc_middle::ty::print::with_no_trimmed_paths;
@@ -1592,71 +1593,41 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
self.infcx.selection_cache.insert((param_env, pred), dep_node, candidate);
}
/// Matches a predicate against the bounds of its self type.
///
/// Given an obligation like `<T as Foo>::Bar: Baz` where the self type is
/// a projection, look at the bounds of `T::Bar`, see if we can find a
/// `Baz` bound. We return indexes into the list returned by
/// `tcx.item_bounds` for any applicable bounds.
#[instrument(level = "debug", skip(self), ret)]
fn match_projection_obligation_against_definition_bounds(
/// Looks at the item bounds of the projection or opaque type.
/// If this is a nested rigid projection, such as
/// `<<T as Tr1>::Assoc as Tr2>::Assoc`, consider the item bounds
/// on both `Tr1::Assoc` and `Tr2::Assoc`, since we may encounter
/// relative bounds on both via the `associated_type_bounds` feature.
pub(super) fn for_each_item_bound<T>(
&mut self,
obligation: &PolyTraitObligation<'tcx>,
) -> smallvec::SmallVec<[usize; 2]> {
let poly_trait_predicate = self.infcx.resolve_vars_if_possible(obligation.predicate);
let placeholder_trait_predicate =
self.infcx.enter_forall_and_leak_universe(poly_trait_predicate);
debug!(?placeholder_trait_predicate);
let tcx = self.infcx.tcx;
let (def_id, args) = match *placeholder_trait_predicate.trait_ref.self_ty().kind() {
ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
(def_id, args)
}
_ => {
span_bug!(
obligation.cause.span,
"match_projection_obligation_against_definition_bounds() called \
but self-ty is not a projection: {:?}",
placeholder_trait_predicate.trait_ref.self_ty()
);
}
};
let bounds = tcx.item_bounds(def_id).instantiate(tcx, args);
// The bounds returned by `item_bounds` may contain duplicates after
// normalization, so try to deduplicate when possible to avoid
// unnecessary ambiguity.
let mut distinct_normalized_bounds = FxHashSet::default();
bounds
.iter()
.enumerate()
.filter_map(|(idx, bound)| {
let bound_predicate = bound.kind();
if let ty::ClauseKind::Trait(pred) = bound_predicate.skip_binder() {
let bound = bound_predicate.rebind(pred.trait_ref);
if self.infcx.probe(|_| {
match self.match_normalize_trait_ref(
obligation,
bound,
placeholder_trait_predicate.trait_ref,
) {
Ok(None) => true,
Ok(Some(normalized_trait))
if distinct_normalized_bounds.insert(normalized_trait) =>
{
true
}
_ => false,
}
}) {
return Some(idx);
}
mut self_ty: Ty<'tcx>,
mut for_each: impl FnMut(&mut Self, ty::Clause<'tcx>, usize) -> ControlFlow<T, ()>,
on_ambiguity: impl FnOnce(),
) -> ControlFlow<T, ()> {
let mut idx = 0;
loop {
let (kind, alias_ty) = match *self_ty.kind() {
ty::Alias(kind @ (ty::Projection | ty::Opaque), alias_ty) => (kind, alias_ty),
ty::Infer(ty::TyVar(_)) => {
on_ambiguity();
return ControlFlow::Continue(());
}
None
})
.collect()
_ => return ControlFlow::Continue(()),
};
for bound in
self.tcx().item_bounds(alias_ty.def_id).instantiate(self.tcx(), alias_ty.args)
{
for_each(self, bound, idx)?;
idx += 1;
}
if kind == ty::Projection {
self_ty = alias_ty.self_ty();
} else {
return ControlFlow::Continue(());
}
}
}
/// Equates the trait in `obligation` with trait bound. If the two traits