]> git.lizzy.rs Git - rust.git/commitdiff
update project to emulate a projection cache
authorlcnr <rust@lcnr.de>
Tue, 17 Jan 2023 12:41:12 +0000 (13:41 +0100)
committerlcnr <rust@lcnr.de>
Wed, 18 Jan 2023 07:11:15 +0000 (08:11 +0100)
compiler/rustc_trait_selection/src/solve/infcx_ext.rs
compiler/rustc_trait_selection/src/solve/project_goals.rs
compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

index f92d64631340a732839b5a487f959eeb78d6c8f3..9b7feb50537871d09d66ef7212a1aed5899095b0 100644 (file)
@@ -3,6 +3,7 @@
 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 @@
 /// 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 @@ fn next_ty_infer(&self) -> Ty<'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>>(
index cf07926f85a2755183d90b01e091f5d81d96c20a..435f2877fb3292bc9ac0cbd881204a9f61039162 100644 (file)
@@ -2,7 +2,7 @@
 
 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(
@@ -124,14 +217,18 @@ fn consider_impl_candidate(
             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 @@ fn consider_impl_candidate(
                 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) }
 }
index 4f48389410bfac28654b97a0e0ee5250f152b9ee..0030e9aa3e5149343098dda7c96c3989f92889ae 100644 (file)
@@ -105,7 +105,7 @@ pub(super) fn try_push_stack(
         }
     }
 
-    /// 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