255 lines
9.4 KiB
Rust
255 lines
9.4 KiB
Rust
//! Code for projecting associated types out of trait references.
|
|
|
|
use super::PredicateObligation;
|
|
|
|
use crate::infer::InferCtxtUndoLogs;
|
|
|
|
use rustc_data_structures::{
|
|
snapshot_map::{self, SnapshotMapRef, SnapshotMapStorage},
|
|
undo_log::Rollback,
|
|
};
|
|
use rustc_middle::ty::{self, Ty};
|
|
|
|
pub use rustc_middle::traits::{EvaluationResult, Reveal};
|
|
|
|
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: Vec<PredicateObligation<'tcx>>,
|
|
}
|
|
|
|
pub type NormalizedTy<'tcx> = Normalized<'tcx, Ty<'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> {
|
|
ty: ty::AliasTy<'tcx>,
|
|
}
|
|
|
|
impl<'tcx> ProjectionCacheKey<'tcx> {
|
|
pub fn new(ty: ty::AliasTy<'tcx>) -> Self {
|
|
Self { ty }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ProjectionCacheEntry<'tcx> {
|
|
InProgress,
|
|
Ambiguous,
|
|
Recur,
|
|
Error,
|
|
NormalizedTy {
|
|
ty: Normalized<'tcx, ty::Term<'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: Normalized<'tcx, ty::Term<'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::NormalizedTy { 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::NormalizedTy { ty, complete: _ }) => {
|
|
info!("ProjectionCacheEntry::complete({:?}) - completing {:?}", key, ty);
|
|
let mut ty = ty.clone();
|
|
if result.must_apply_considering_regions() {
|
|
ty.obligations = vec![];
|
|
}
|
|
map.insert(key, ProjectionCacheEntry::NormalizedTy { 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::NormalizedTy { 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);
|
|
}
|
|
}
|