use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
-use super::{Certainty, EvalCtxt, Goal, QueryResult};
+use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_infer::traits::specialization_graph::LeafDef;
use rustc_infer::traits::Reveal;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
-use rustc_middle::ty::ProjectionPredicate;
use rustc_middle::ty::TypeVisitable;
use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
use rustc_span::DUMMY_SP;
use std::iter;
+use std::ops::ControlFlow;
impl<'tcx> EvalCtxt<'_, 'tcx> {
pub(super) fn compute_projection_goal(
&mut self,
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
) -> QueryResult<'tcx> {
- let candidates = self.assemble_and_evaluate_candidates(goal);
- self.merge_project_candidates(candidates)
+ // To only compute normalization ones for each projection we only
+ // normalize if the expected term is an unconstrained inference variable.
+ //
+ // E.g. for `<T as Trait>::Assoc = u32` we recursively compute the goal
+ // `exists<U> <T as Trait>::Assoc = U` and then take the resulting type for
+ // `U` and equate it with `u32`. This means that we don't need a separate
+ // projection cache in the solver.
+ if self.term_is_fully_unconstrained(goal) {
+ let candidates = self.assemble_and_evaluate_candidates(goal);
+ self.merge_project_candidates(candidates)
+ } else {
+ let predicate = goal.predicate;
+ let unconstrained_rhs = match predicate.term.unpack() {
+ ty::TermKind::Ty(_) => self.infcx.next_ty_infer().into(),
+ ty::TermKind::Const(ct) => self.infcx.next_const_infer(ct.ty()).into(),
+ };
+ let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate {
+ projection_ty: goal.predicate.projection_ty,
+ term: unconstrained_rhs,
+ });
+ let (_has_changed, normalize_certainty) =
+ self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?;
+
+ let nested_eq_goals =
+ self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
+ let eval_certainty = self.evaluate_all(nested_eq_goals)?;
+ self.make_canonical_response(normalize_certainty.unify_and(eval_certainty))
+ }
+ }
+
+ /// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
+ ///
+ /// This is the case if the `term` is an inference variable in the innermost universe
+ /// and does not occur in any other part of the predicate.
+ fn term_is_fully_unconstrained(&self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>) -> bool {
+ let infcx = self.infcx;
+ let term_is_infer = match goal.predicate.term.unpack() {
+ ty::TermKind::Ty(ty) => {
+ if let &ty::Infer(ty::TyVar(vid)) = ty.kind() {
+ match infcx.probe_ty_var(vid) {
+ Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
+ Err(universe) => universe == infcx.universe(),
+ }
+ } else {
+ false
+ }
+ }
+ ty::TermKind::Const(ct) => {
+ if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() {
+ match self.infcx.probe_const_var(vid) {
+ Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
+ Err(universe) => universe == infcx.universe(),
+ }
+ } else {
+ false
+ }
+ }
+ };
+
+ struct ContainsTerm<'tcx> {
+ term: ty::Term<'tcx>,
+ }
+ impl<'tcx> TypeVisitor<'tcx> for ContainsTerm<'tcx> {
+ type BreakTy = ();
+ fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+ if t.needs_infer() {
+ if ty::Term::from(t) == self.term {
+ ControlFlow::BREAK
+ } else {
+ t.super_visit_with(self)
+ }
+ } else {
+ ControlFlow::CONTINUE
+ }
+ }
+
+ fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
+ if c.needs_infer() {
+ if ty::Term::from(c) == self.term {
+ ControlFlow::BREAK
+ } else {
+ c.super_visit_with(self)
+ }
+ } else {
+ ControlFlow::CONTINUE
+ }
+ }
+ }
+
+ let mut visitor = ContainsTerm { term: goal.predicate.term };
+
+ term_is_infer
+ && goal.predicate.projection_ty.visit_with(&mut visitor).is_continue()
+ && goal.param_env.visit_with(&mut visitor).is_continue()
}
fn merge_project_candidates(
nested_goals.extend(where_clause_bounds);
let trait_ref_certainty = ecx.evaluate_all(nested_goals)?;
+ // In case the associated item is hidden due to specialization, we have to
+ // return ambiguity this would otherwise be incomplete, resulting in
+ // unsoundness during coherence (#105782).
let Some(assoc_def) = fetch_eligible_assoc_item_def(
ecx.infcx,
goal.param_env,
goal_trait_ref,
goal.predicate.def_id(),
impl_def_id
- ) else {
- return Err(NoSolution);
+ )? else {
+ let certainty = Certainty::Maybe(MaybeCause::Ambiguity);
+ return Ok(trait_ref_certainty.unify_and(certainty));
};
if !assoc_def.item.defaultness(tcx).has_value() {
ty.map_bound(|ty| ty.into())
};
- let nested_goals =
- ecx.infcx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))?;
- let rhs_certainty = ecx.evaluate_all(nested_goals)?;
+ // The term of our goal should be fully unconstrained, so this should never fail.
+ //
+ // It can however be ambiguous when the resolved type is a projection.
+ let nested_goals = ecx
+ .infcx
+ .eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
+ .expect("failed to unify with unconstrained term");
+ let rhs_certainty =
+ ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
Ok(trait_ref_certainty.unify_and(rhs_certainty))
})
goal_trait_ref: ty::TraitRef<'tcx>,
trait_assoc_def_id: DefId,
impl_def_id: DefId,
-) -> Option<LeafDef> {
+) -> Result<Option<LeafDef>, NoSolution> {
let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id)
- .map_err(|ErrorGuaranteed { .. }| ())
- .ok()?;
+ .map_err(|ErrorGuaranteed { .. }| NoSolution)?;
let eligible = if node_item.is_final() {
// Non-specializable items are always projectable.
}
};
- if eligible { Some(node_item) } else { None }
+ if eligible { Ok(Some(node_item)) } else { Ok(None) }
}