update project to emulate a projection cache
This commit is contained in:
parent
9a757d6ee4
commit
31ac29d989
3 changed files with 124 additions and 14 deletions
|
@ -3,6 +3,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi
|
|||
use rustc_infer::infer::{InferCtxt, InferOk};
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_infer::traits::ObligationCause;
|
||||
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
|
@ -16,6 +17,7 @@ use super::Goal;
|
|||
/// help.
|
||||
pub(super) trait InferCtxtExt<'tcx> {
|
||||
fn next_ty_infer(&self) -> Ty<'tcx>;
|
||||
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx>;
|
||||
|
||||
fn eq<T: ToTrace<'tcx>>(
|
||||
&self,
|
||||
|
@ -32,6 +34,12 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
|||
span: DUMMY_SP,
|
||||
})
|
||||
}
|
||||
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
|
||||
self.next_const_var(
|
||||
ty,
|
||||
ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP },
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, param_env), ret)]
|
||||
fn eq<T: ToTrace<'tcx>>(
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::traits::{specialization_graph, translate_substs};
|
|||
|
||||
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;
|
||||
|
@ -11,19 +11,112 @@ use rustc_infer::traits::query::NoSolution;
|
|||
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(
|
||||
|
@ -124,14 +217,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
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() {
|
||||
|
@ -178,9 +275,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||
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))
|
||||
})
|
||||
|
@ -217,10 +320,9 @@ fn fetch_eligible_assoc_item_def<'tcx>(
|
|||
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.
|
||||
|
@ -239,5 +341,5 @@ fn fetch_eligible_assoc_item_def<'tcx>(
|
|||
}
|
||||
};
|
||||
|
||||
if eligible { Some(node_item) } else { None }
|
||||
if eligible { Ok(Some(node_item)) } else { Ok(None) }
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with
|
||||
/// We cannot simply store the result of [super::EvalCtxt::compute_goal] as we have to deal with
|
||||
/// coinductive cycles.
|
||||
///
|
||||
/// When we encounter a coinductive cycle, we have to prove the final result of that cycle
|
||||
|
|
Loading…
Add table
Reference in a new issue