rustc_infer/traits/project.rs
1//! Code for projecting associated types out of trait references.
2
3use rustc_data_structures::snapshot_map::{self, SnapshotMapRef, SnapshotMapStorage};
4use rustc_data_structures::undo_log::Rollback;
5use rustc_macros::TypeVisitable;
6use rustc_middle::traits::EvaluationResult;
7use rustc_middle::ty;
8use tracing::{debug, info};
9
10use super::PredicateObligations;
11use crate::infer::snapshot::undo_log::InferCtxtUndoLogs;
12
13pub(crate) type UndoLog<'tcx> =
14 snapshot_map::UndoLog<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>;
15
16#[derive(Clone, TypeVisitable)]
17pub struct MismatchedProjectionTypes<'tcx> {
18 pub err: ty::error::TypeError<'tcx>,
19}
20
21#[derive(Clone)]
22pub struct Normalized<'tcx, T> {
23 pub value: T,
24 pub obligations: PredicateObligations<'tcx>,
25}
26
27pub type NormalizedTerm<'tcx> = Normalized<'tcx, ty::Term<'tcx>>;
28
29impl<'tcx, T> Normalized<'tcx, T> {
30 pub fn with<U>(self, value: U) -> Normalized<'tcx, U> {
31 Normalized { value, obligations: self.obligations }
32 }
33}
34
35// # Cache
36
37/// The projection cache. Unlike the standard caches, this can include
38/// infcx-dependent type variables, therefore we have to roll the
39/// cache back each time we roll a snapshot back, to avoid assumptions
40/// on yet-unresolved inference variables. Types with placeholder
41/// regions also have to be removed when the respective snapshot ends.
42///
43/// Because of that, projection cache entries can be "stranded" and left
44/// inaccessible when type variables inside the key are resolved. We make no
45/// attempt to recover or remove "stranded" entries, but rather let them be
46/// (for the lifetime of the infcx).
47///
48/// Entries in the projection cache might contain inference variables
49/// that will be resolved by obligations on the projection cache entry (e.g.,
50/// when a type parameter in the associated type is constrained through
51/// an "RFC 447" projection on the impl).
52///
53/// When working with a fulfillment context, the derived obligations of each
54/// projection cache entry will be registered on the fulfillcx, so any users
55/// that can wait for a fulfillcx fixed point need not care about this. However,
56/// users that don't wait for a fixed point (e.g., trait evaluation) have to
57/// resolve the obligations themselves to make sure the projected result is
58/// ok and avoid issues like #43132.
59///
60/// If that is done, after evaluation the obligations, it is a good idea to
61/// call `ProjectionCache::complete` to make sure the obligations won't be
62/// re-evaluated and avoid an exponential worst-case.
63//
64// FIXME: we probably also want some sort of cross-infcx cache here to
65// reduce the amount of duplication. Let's see what we get with the Chalk reforms.
66pub struct ProjectionCache<'a, 'tcx> {
67 map: &'a mut SnapshotMapStorage<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>,
68 undo_log: &'a mut InferCtxtUndoLogs<'tcx>,
69}
70
71#[derive(Clone, Default)]
72pub struct ProjectionCacheStorage<'tcx> {
73 map: SnapshotMapStorage<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>,
74}
75
76#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
77pub struct ProjectionCacheKey<'tcx> {
78 term: ty::AliasTerm<'tcx>,
79 param_env: ty::ParamEnv<'tcx>,
80}
81
82impl<'tcx> ProjectionCacheKey<'tcx> {
83 pub fn new(term: ty::AliasTerm<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self {
84 Self { term, param_env }
85 }
86}
87
88#[derive(Clone, Debug)]
89pub enum ProjectionCacheEntry<'tcx> {
90 InProgress,
91 Ambiguous,
92 Recur,
93 Error,
94 NormalizedTerm {
95 ty: NormalizedTerm<'tcx>,
96 /// If we were able to successfully evaluate the corresponding cache
97 /// entry key during predicate evaluation, then this field stores the
98 /// final result obtained from evaluating all of the projection
99 /// sub-obligations. During evaluation, we will skip evaluating the
100 /// cached sub-obligations in `ty` if this field is set. Evaluation
101 /// only cares about the final result, so we don't care about any
102 /// region constraint side-effects produced by evaluating the
103 /// sub-obligations.
104 ///
105 /// Additionally, we will clear out the sub-obligations entirely if we
106 /// ever evaluate the cache entry (along with all its sub obligations)
107 /// to `EvaluatedToOk`. This affects all users of the cache, not just
108 /// evaluation. Since a result of `EvaluatedToOk` means that there were
109 /// no region obligations that need to be tracked, it's fine to forget
110 /// about the sub-obligations - they don't provide any additional
111 /// information. However, we do *not* discard any obligations when we
112 /// see `EvaluatedToOkModuloRegions` - we don't know which
113 /// sub-obligations may introduce region constraints, so we keep them
114 /// all to be safe.
115 ///
116 /// When we are not performing evaluation (e.g. in
117 /// `FulfillmentContext`), we ignore this field, and always re-process
118 /// the cached sub-obligations (which may have been cleared out - see
119 /// the above paragraph). This ensures that we do not lose any regions
120 /// constraints that arise from processing the sub-obligations.
121 complete: Option<EvaluationResult>,
122 },
123}
124
125impl<'tcx> ProjectionCacheStorage<'tcx> {
126 #[inline]
127 pub(crate) fn with_log<'a>(
128 &'a mut self,
129 undo_log: &'a mut InferCtxtUndoLogs<'tcx>,
130 ) -> ProjectionCache<'a, 'tcx> {
131 ProjectionCache { map: &mut self.map, undo_log }
132 }
133}
134
135impl<'tcx> ProjectionCache<'_, 'tcx> {
136 #[inline]
137 fn map(
138 &mut self,
139 ) -> SnapshotMapRef<
140 '_,
141 ProjectionCacheKey<'tcx>,
142 ProjectionCacheEntry<'tcx>,
143 InferCtxtUndoLogs<'tcx>,
144 > {
145 self.map.with_log(self.undo_log)
146 }
147
148 pub fn clear(&mut self) {
149 self.map().clear();
150 }
151
152 /// Try to start normalize `key`; returns an error if
153 /// normalization already occurred (this error corresponds to a
154 /// cache hit, so it's actually a good thing).
155 pub fn try_start(
156 &mut self,
157 key: ProjectionCacheKey<'tcx>,
158 ) -> Result<(), ProjectionCacheEntry<'tcx>> {
159 let mut map = self.map();
160 if let Some(entry) = map.get(&key) {
161 return Err(entry.clone());
162 }
163
164 map.insert(key, ProjectionCacheEntry::InProgress);
165 Ok(())
166 }
167
168 /// Indicates that `key` was normalized to `value`.
169 pub fn insert_term(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTerm<'tcx>) {
170 debug!(
171 "ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
172 key, value
173 );
174 let mut map = self.map();
175 if let Some(ProjectionCacheEntry::Recur) = map.get(&key) {
176 debug!("Not overwriting Recur");
177 return;
178 }
179 let fresh_key =
180 map.insert(key, ProjectionCacheEntry::NormalizedTerm { ty: value, complete: None });
181 assert!(!fresh_key, "never started projecting `{key:?}`");
182 }
183
184 /// Mark the relevant projection cache key as having its derived obligations
185 /// complete, so they won't have to be re-computed (this is OK to do in a
186 /// snapshot - if the snapshot is rolled back, the obligations will be
187 /// marked as incomplete again).
188 pub fn complete(&mut self, key: ProjectionCacheKey<'tcx>, result: EvaluationResult) {
189 let mut map = self.map();
190 match map.get(&key) {
191 Some(ProjectionCacheEntry::NormalizedTerm { ty, complete: _ }) => {
192 info!("ProjectionCacheEntry::complete({:?}) - completing {:?}", key, ty);
193 let mut ty = ty.clone();
194 if result.must_apply_considering_regions() {
195 ty.obligations = PredicateObligations::new();
196 }
197 map.insert(
198 key,
199 ProjectionCacheEntry::NormalizedTerm { ty, complete: Some(result) },
200 );
201 }
202 ref value => {
203 // Type inference could "strand behind" old cache entries. Leave
204 // them alone for now.
205 info!("ProjectionCacheEntry::complete({:?}) - ignoring {:?}", key, value);
206 }
207 };
208 }
209
210 pub fn is_complete(&mut self, key: ProjectionCacheKey<'tcx>) -> Option<EvaluationResult> {
211 self.map().get(&key).and_then(|res| match res {
212 ProjectionCacheEntry::NormalizedTerm { ty: _, complete } => *complete,
213 _ => None,
214 })
215 }
216
217 /// Indicates that trying to normalize `key` resulted in
218 /// ambiguity. No point in trying it again then until we gain more
219 /// type information (in which case, the "fully resolved" key will
220 /// be different).
221 pub fn ambiguous(&mut self, key: ProjectionCacheKey<'tcx>) {
222 let fresh = self.map().insert(key, ProjectionCacheEntry::Ambiguous);
223 assert!(!fresh, "never started projecting `{key:?}`");
224 }
225
226 /// Indicates that while trying to normalize `key`, `key` was required to
227 /// be normalized again. Selection or evaluation should eventually report
228 /// an error here.
229 pub fn recur(&mut self, key: ProjectionCacheKey<'tcx>) {
230 let fresh = self.map().insert(key, ProjectionCacheEntry::Recur);
231 assert!(!fresh, "never started projecting `{key:?}`");
232 }
233
234 /// Indicates that trying to normalize `key` resulted in
235 /// error.
236 pub fn error(&mut self, key: ProjectionCacheKey<'tcx>) {
237 let fresh = self.map().insert(key, ProjectionCacheEntry::Error);
238 assert!(!fresh, "never started projecting `{key:?}`");
239 }
240}
241
242impl<'tcx> Rollback<UndoLog<'tcx>> for ProjectionCacheStorage<'tcx> {
243 fn reverse(&mut self, undo: UndoLog<'tcx>) {
244 self.map.reverse(undo);
245 }
246}