diff options
| author | lcnr <rust@lcnr.de> | 2023-01-17 13:41:12 +0100 |
|---|---|---|
| committer | lcnr <rust@lcnr.de> | 2023-01-18 08:11:15 +0100 |
| commit | 31ac29d9899c063fdee7a78988990f2b037bef7b (patch) | |
| tree | c21b72577bbd2e778f8738965624c62a9dd68f6c /compiler/rustc_trait_selection/src | |
| parent | 9a757d6ee4229bd7e022a2f3f26946cf05fddbcf (diff) | |
| download | rust-31ac29d9899c063fdee7a78988990f2b037bef7b.tar.gz rust-31ac29d9899c063fdee7a78988990f2b037bef7b.zip | |
update project to emulate a projection cache
Diffstat (limited to 'compiler/rustc_trait_selection/src')
3 files changed, 124 insertions, 14 deletions
diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs index f92d6463134..9b7feb50537 100644 --- a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -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>>( diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index cf07926f85a..435f2877fb3 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -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) } } diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs index 4f48389410b..0030e9aa3e5 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs @@ -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 |
