| //! Code for projecting associated types out of trait references. |
| |
| use rustc_data_structures::snapshot_map::{self, SnapshotMapRef, SnapshotMapStorage}; |
| use rustc_data_structures::undo_log::Rollback; |
| use rustc_middle::traits::EvaluationResult; |
| use rustc_middle::ty; |
| use tracing::{debug, info}; |
| |
| use super::PredicateObligations; |
| use crate::infer::snapshot::undo_log::InferCtxtUndoLogs; |
| |
| pub(crate) type UndoLog<'tcx> = |
| snapshot_map::UndoLog<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>; |
| |
| #[derive(Clone)] |
| pub struct MismatchedProjectionTypes<'tcx> { |
| pub err: ty::error::TypeError<'tcx>, |
| } |
| |
| #[derive(Clone)] |
| pub struct Normalized<'tcx, T> { |
| pub value: T, |
| pub obligations: PredicateObligations<'tcx>, |
| } |
| |
| pub type NormalizedTerm<'tcx> = Normalized<'tcx, ty::Term<'tcx>>; |
| |
| impl<'tcx, T> Normalized<'tcx, T> { |
| pub fn with<U>(self, value: U) -> Normalized<'tcx, U> { |
| Normalized { value, obligations: self.obligations } |
| } |
| } |
| |
| // # Cache |
| |
| /// The projection cache. Unlike the standard caches, this can include |
| /// infcx-dependent type variables, therefore we have to roll the |
| /// cache back each time we roll a snapshot back, to avoid assumptions |
| /// on yet-unresolved inference variables. Types with placeholder |
| /// regions also have to be removed when the respective snapshot ends. |
| /// |
| /// Because of that, projection cache entries can be "stranded" and left |
| /// inaccessible when type variables inside the key are resolved. We make no |
| /// attempt to recover or remove "stranded" entries, but rather let them be |
| /// (for the lifetime of the infcx). |
| /// |
| /// Entries in the projection cache might contain inference variables |
| /// that will be resolved by obligations on the projection cache entry (e.g., |
| /// when a type parameter in the associated type is constrained through |
| /// an "RFC 447" projection on the impl). |
| /// |
| /// When working with a fulfillment context, the derived obligations of each |
| /// projection cache entry will be registered on the fulfillcx, so any users |
| /// that can wait for a fulfillcx fixed point need not care about this. However, |
| /// users that don't wait for a fixed point (e.g., trait evaluation) have to |
| /// resolve the obligations themselves to make sure the projected result is |
| /// ok and avoid issues like #43132. |
| /// |
| /// If that is done, after evaluation the obligations, it is a good idea to |
| /// call `ProjectionCache::complete` to make sure the obligations won't be |
| /// re-evaluated and avoid an exponential worst-case. |
| // |
| // FIXME: we probably also want some sort of cross-infcx cache here to |
| // reduce the amount of duplication. Let's see what we get with the Chalk reforms. |
| pub struct ProjectionCache<'a, 'tcx> { |
| map: &'a mut SnapshotMapStorage<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>, |
| undo_log: &'a mut InferCtxtUndoLogs<'tcx>, |
| } |
| |
| #[derive(Clone, Default)] |
| pub struct ProjectionCacheStorage<'tcx> { |
| map: SnapshotMapStorage<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>, |
| } |
| |
| #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] |
| pub struct ProjectionCacheKey<'tcx> { |
| term: ty::AliasTerm<'tcx>, |
| param_env: ty::ParamEnv<'tcx>, |
| } |
| |
| impl<'tcx> ProjectionCacheKey<'tcx> { |
| pub fn new(term: ty::AliasTerm<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { |
| Self { term, param_env } |
| } |
| } |
| |
| #[derive(Clone, Debug)] |
| pub enum ProjectionCacheEntry<'tcx> { |
| InProgress, |
| Ambiguous, |
| Recur, |
| Error, |
| NormalizedTerm { |
| ty: NormalizedTerm<'tcx>, |
| /// If we were able to successfully evaluate the corresponding cache |
| /// entry key during predicate evaluation, then this field stores the |
| /// final result obtained from evaluating all of the projection |
| /// sub-obligations. During evaluation, we will skip evaluating the |
| /// cached sub-obligations in `ty` if this field is set. Evaluation |
| /// only cares about the final result, so we don't care about any |
| /// region constraint side-effects produced by evaluating the |
| /// sub-obligations. |
| /// |
| /// Additionally, we will clear out the sub-obligations entirely if we |
| /// ever evaluate the cache entry (along with all its sub obligations) |
| /// to `EvaluatedToOk`. This affects all users of the cache, not just |
| /// evaluation. Since a result of `EvaluatedToOk` means that there were |
| /// no region obligations that need to be tracked, it's fine to forget |
| /// about the sub-obligations - they don't provide any additional |
| /// information. However, we do *not* discard any obligations when we |
| /// see `EvaluatedToOkModuloRegions` - we don't know which |
| /// sub-obligations may introduce region constraints, so we keep them |
| /// all to be safe. |
| /// |
| /// When we are not performing evaluation (e.g. in |
| /// `FulfillmentContext`), we ignore this field, and always re-process |
| /// the cached sub-obligations (which may have been cleared out - see |
| /// the above paragraph). This ensures that we do not lose any regions |
| /// constraints that arise from processing the sub-obligations. |
| complete: Option<EvaluationResult>, |
| }, |
| } |
| |
| impl<'tcx> ProjectionCacheStorage<'tcx> { |
| #[inline] |
| pub(crate) fn with_log<'a>( |
| &'a mut self, |
| undo_log: &'a mut InferCtxtUndoLogs<'tcx>, |
| ) -> ProjectionCache<'a, 'tcx> { |
| ProjectionCache { map: &mut self.map, undo_log } |
| } |
| } |
| |
| impl<'tcx> ProjectionCache<'_, 'tcx> { |
| #[inline] |
| fn map( |
| &mut self, |
| ) -> SnapshotMapRef< |
| '_, |
| ProjectionCacheKey<'tcx>, |
| ProjectionCacheEntry<'tcx>, |
| InferCtxtUndoLogs<'tcx>, |
| > { |
| self.map.with_log(self.undo_log) |
| } |
| |
| pub fn clear(&mut self) { |
| self.map().clear(); |
| } |
| |
| /// Try to start normalize `key`; returns an error if |
| /// normalization already occurred (this error corresponds to a |
| /// cache hit, so it's actually a good thing). |
| pub fn try_start( |
| &mut self, |
| key: ProjectionCacheKey<'tcx>, |
| ) -> Result<(), ProjectionCacheEntry<'tcx>> { |
| let mut map = self.map(); |
| if let Some(entry) = map.get(&key) { |
| return Err(entry.clone()); |
| } |
| |
| map.insert(key, ProjectionCacheEntry::InProgress); |
| Ok(()) |
| } |
| |
| /// Indicates that `key` was normalized to `value`. |
| pub fn insert_term(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTerm<'tcx>) { |
| debug!( |
| "ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}", |
| key, value |
| ); |
| let mut map = self.map(); |
| if let Some(ProjectionCacheEntry::Recur) = map.get(&key) { |
| debug!("Not overwriting Recur"); |
| return; |
| } |
| let fresh_key = |
| map.insert(key, ProjectionCacheEntry::NormalizedTerm { ty: value, complete: None }); |
| assert!(!fresh_key, "never started projecting `{key:?}`"); |
| } |
| |
| /// Mark the relevant projection cache key as having its derived obligations |
| /// complete, so they won't have to be re-computed (this is OK to do in a |
| /// snapshot - if the snapshot is rolled back, the obligations will be |
| /// marked as incomplete again). |
| pub fn complete(&mut self, key: ProjectionCacheKey<'tcx>, result: EvaluationResult) { |
| let mut map = self.map(); |
| match map.get(&key) { |
| Some(ProjectionCacheEntry::NormalizedTerm { ty, complete: _ }) => { |
| info!("ProjectionCacheEntry::complete({:?}) - completing {:?}", key, ty); |
| let mut ty = ty.clone(); |
| if result.must_apply_considering_regions() { |
| ty.obligations = PredicateObligations::new(); |
| } |
| map.insert( |
| key, |
| ProjectionCacheEntry::NormalizedTerm { ty, complete: Some(result) }, |
| ); |
| } |
| ref value => { |
| // Type inference could "strand behind" old cache entries. Leave |
| // them alone for now. |
| info!("ProjectionCacheEntry::complete({:?}) - ignoring {:?}", key, value); |
| } |
| }; |
| } |
| |
| pub fn is_complete(&mut self, key: ProjectionCacheKey<'tcx>) -> Option<EvaluationResult> { |
| self.map().get(&key).and_then(|res| match res { |
| ProjectionCacheEntry::NormalizedTerm { ty: _, complete } => *complete, |
| _ => None, |
| }) |
| } |
| |
| /// Indicates that trying to normalize `key` resulted in |
| /// ambiguity. No point in trying it again then until we gain more |
| /// type information (in which case, the "fully resolved" key will |
| /// be different). |
| pub fn ambiguous(&mut self, key: ProjectionCacheKey<'tcx>) { |
| let fresh = self.map().insert(key, ProjectionCacheEntry::Ambiguous); |
| assert!(!fresh, "never started projecting `{key:?}`"); |
| } |
| |
| /// Indicates that while trying to normalize `key`, `key` was required to |
| /// be normalized again. Selection or evaluation should eventually report |
| /// an error here. |
| pub fn recur(&mut self, key: ProjectionCacheKey<'tcx>) { |
| let fresh = self.map().insert(key, ProjectionCacheEntry::Recur); |
| assert!(!fresh, "never started projecting `{key:?}`"); |
| } |
| |
| /// Indicates that trying to normalize `key` resulted in |
| /// error. |
| pub fn error(&mut self, key: ProjectionCacheKey<'tcx>) { |
| let fresh = self.map().insert(key, ProjectionCacheEntry::Error); |
| assert!(!fresh, "never started projecting `{key:?}`"); |
| } |
| } |
| |
| impl<'tcx> Rollback<UndoLog<'tcx>> for ProjectionCacheStorage<'tcx> { |
| fn reverse(&mut self, undo: UndoLog<'tcx>) { |
| self.map.reverse(undo); |
| } |
| } |