Skip to content

Commit 31ac29d

Browse files
committed
update project to emulate a projection cache
1 parent 9a757d6 commit 31ac29d

File tree

3 files changed

+124
-14
lines changed

3 files changed

+124
-14
lines changed

compiler/rustc_trait_selection/src/solve/infcx_ext.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi
33
use rustc_infer::infer::{InferCtxt, InferOk};
44
use rustc_infer::traits::query::NoSolution;
55
use rustc_infer::traits::ObligationCause;
6+
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
67
use rustc_middle::ty::{self, Ty};
78
use rustc_span::DUMMY_SP;
89

@@ -16,6 +17,7 @@ use super::Goal;
1617
/// help.
1718
pub(super) trait InferCtxtExt<'tcx> {
1819
fn next_ty_infer(&self) -> Ty<'tcx>;
20+
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx>;
1921

2022
fn eq<T: ToTrace<'tcx>>(
2123
&self,
@@ -32,6 +34,12 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
3234
span: DUMMY_SP,
3335
})
3436
}
37+
fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
38+
self.next_const_var(
39+
ty,
40+
ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP },
41+
)
42+
}
3543

3644
#[instrument(level = "debug", skip(self, param_env), ret)]
3745
fn eq<T: ToTrace<'tcx>>(

compiler/rustc_trait_selection/src/solve/project_goals.rs

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::traits::{specialization_graph, translate_substs};
22

33
use super::assembly::{self, Candidate, CandidateSource};
44
use super::infcx_ext::InferCtxtExt;
5-
use super::{Certainty, EvalCtxt, Goal, QueryResult};
5+
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
66
use rustc_errors::ErrorGuaranteed;
77
use rustc_hir::def::DefKind;
88
use rustc_hir::def_id::DefId;
@@ -11,19 +11,112 @@ use rustc_infer::traits::query::NoSolution;
1111
use rustc_infer::traits::specialization_graph::LeafDef;
1212
use rustc_infer::traits::Reveal;
1313
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
14-
use rustc_middle::ty::ProjectionPredicate;
1514
use rustc_middle::ty::TypeVisitable;
1615
use rustc_middle::ty::{self, Ty, TyCtxt};
16+
use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
1717
use rustc_span::DUMMY_SP;
1818
use std::iter;
19+
use std::ops::ControlFlow;
1920

2021
impl<'tcx> EvalCtxt<'_, 'tcx> {
2122
pub(super) fn compute_projection_goal(
2223
&mut self,
2324
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
2425
) -> QueryResult<'tcx> {
25-
let candidates = self.assemble_and_evaluate_candidates(goal);
26-
self.merge_project_candidates(candidates)
26+
// To only compute normalization ones for each projection we only
27+
// normalize if the expected term is an unconstrained inference variable.
28+
//
29+
// E.g. for `<T as Trait>::Assoc = u32` we recursively compute the goal
30+
// `exists<U> <T as Trait>::Assoc = U` and then take the resulting type for
31+
// `U` and equate it with `u32`. This means that we don't need a separate
32+
// projection cache in the solver.
33+
if self.term_is_fully_unconstrained(goal) {
34+
let candidates = self.assemble_and_evaluate_candidates(goal);
35+
self.merge_project_candidates(candidates)
36+
} else {
37+
let predicate = goal.predicate;
38+
let unconstrained_rhs = match predicate.term.unpack() {
39+
ty::TermKind::Ty(_) => self.infcx.next_ty_infer().into(),
40+
ty::TermKind::Const(ct) => self.infcx.next_const_infer(ct.ty()).into(),
41+
};
42+
let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate {
43+
projection_ty: goal.predicate.projection_ty,
44+
term: unconstrained_rhs,
45+
});
46+
let (_has_changed, normalize_certainty) =
47+
self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?;
48+
49+
let nested_eq_goals =
50+
self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
51+
let eval_certainty = self.evaluate_all(nested_eq_goals)?;
52+
self.make_canonical_response(normalize_certainty.unify_and(eval_certainty))
53+
}
54+
}
55+
56+
/// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
57+
///
58+
/// This is the case if the `term` is an inference variable in the innermost universe
59+
/// and does not occur in any other part of the predicate.
60+
fn term_is_fully_unconstrained(&self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>) -> bool {
61+
let infcx = self.infcx;
62+
let term_is_infer = match goal.predicate.term.unpack() {
63+
ty::TermKind::Ty(ty) => {
64+
if let &ty::Infer(ty::TyVar(vid)) = ty.kind() {
65+
match infcx.probe_ty_var(vid) {
66+
Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
67+
Err(universe) => universe == infcx.universe(),
68+
}
69+
} else {
70+
false
71+
}
72+
}
73+
ty::TermKind::Const(ct) => {
74+
if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() {
75+
match self.infcx.probe_const_var(vid) {
76+
Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"),
77+
Err(universe) => universe == infcx.universe(),
78+
}
79+
} else {
80+
false
81+
}
82+
}
83+
};
84+
85+
struct ContainsTerm<'tcx> {
86+
term: ty::Term<'tcx>,
87+
}
88+
impl<'tcx> TypeVisitor<'tcx> for ContainsTerm<'tcx> {
89+
type BreakTy = ();
90+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
91+
if t.needs_infer() {
92+
if ty::Term::from(t) == self.term {
93+
ControlFlow::BREAK
94+
} else {
95+
t.super_visit_with(self)
96+
}
97+
} else {
98+
ControlFlow::CONTINUE
99+
}
100+
}
101+
102+
fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
103+
if c.needs_infer() {
104+
if ty::Term::from(c) == self.term {
105+
ControlFlow::BREAK
106+
} else {
107+
c.super_visit_with(self)
108+
}
109+
} else {
110+
ControlFlow::CONTINUE
111+
}
112+
}
113+
}
114+
115+
let mut visitor = ContainsTerm { term: goal.predicate.term };
116+
117+
term_is_infer
118+
&& goal.predicate.projection_ty.visit_with(&mut visitor).is_continue()
119+
&& goal.param_env.visit_with(&mut visitor).is_continue()
27120
}
28121

29122
fn merge_project_candidates(
@@ -124,14 +217,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
124217
nested_goals.extend(where_clause_bounds);
125218
let trait_ref_certainty = ecx.evaluate_all(nested_goals)?;
126219

220+
// In case the associated item is hidden due to specialization, we have to
221+
// return ambiguity this would otherwise be incomplete, resulting in
222+
// unsoundness during coherence (#105782).
127223
let Some(assoc_def) = fetch_eligible_assoc_item_def(
128224
ecx.infcx,
129225
goal.param_env,
130226
goal_trait_ref,
131227
goal.predicate.def_id(),
132228
impl_def_id
133-
) else {
134-
return Err(NoSolution);
229+
)? else {
230+
let certainty = Certainty::Maybe(MaybeCause::Ambiguity);
231+
return Ok(trait_ref_certainty.unify_and(certainty));
135232
};
136233

137234
if !assoc_def.item.defaultness(tcx).has_value() {
@@ -178,9 +275,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
178275
ty.map_bound(|ty| ty.into())
179276
};
180277

181-
let nested_goals =
182-
ecx.infcx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))?;
183-
let rhs_certainty = ecx.evaluate_all(nested_goals)?;
278+
// The term of our goal should be fully unconstrained, so this should never fail.
279+
//
280+
// It can however be ambiguous when the resolved type is a projection.
281+
let nested_goals = ecx
282+
.infcx
283+
.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
284+
.expect("failed to unify with unconstrained term");
285+
let rhs_certainty =
286+
ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
184287

185288
Ok(trait_ref_certainty.unify_and(rhs_certainty))
186289
})
@@ -217,10 +320,9 @@ fn fetch_eligible_assoc_item_def<'tcx>(
217320
goal_trait_ref: ty::TraitRef<'tcx>,
218321
trait_assoc_def_id: DefId,
219322
impl_def_id: DefId,
220-
) -> Option<LeafDef> {
323+
) -> Result<Option<LeafDef>, NoSolution> {
221324
let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id)
222-
.map_err(|ErrorGuaranteed { .. }| ())
223-
.ok()?;
325+
.map_err(|ErrorGuaranteed { .. }| NoSolution)?;
224326

225327
let eligible = if node_item.is_final() {
226328
// Non-specializable items are always projectable.
@@ -239,5 +341,5 @@ fn fetch_eligible_assoc_item_def<'tcx>(
239341
}
240342
};
241343

242-
if eligible { Some(node_item) } else { None }
344+
if eligible { Ok(Some(node_item)) } else { Ok(None) }
243345
}

compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl<'tcx> SearchGraph<'tcx> {
105105
}
106106
}
107107

108-
/// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with
108+
/// We cannot simply store the result of [super::EvalCtxt::compute_goal] as we have to deal with
109109
/// coinductive cycles.
110110
///
111111
/// When we encounter a coinductive cycle, we have to prove the final result of that cycle

0 commit comments

Comments
 (0)