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