Skip to content

Commit bef07b8

Browse files
committed
Auto merge of #43999 - arielb1:immediate-project, r=nikomatsakis
clear out projection subobligations after they are processed After a projection was processed, its derived subobligations no longer need any processing when encountered, and can be removed. This improves the status of #43787. This is actually complementary to #43938 - that PR fixes selection caching (and @remram44's example, which "accidentally" worked because of the buggy projection caching) while this PR fixes projection caching. r? @nikomatsakis
2 parents 9a59d69 + 7534f73 commit bef07b8

File tree

3 files changed

+112
-19
lines changed

3 files changed

+112
-19
lines changed

src/librustc/traits/fulfill.rs

+3
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
251251
});
252252
debug!("select: outcome={:?}", outcome);
253253

254+
// FIXME: if we kept the original cache key, we could mark projection
255+
// obligations as complete for the projection cache here.
256+
254257
errors.extend(
255258
outcome.errors.into_iter()
256259
.map(|e| to_fulfillment_error(e)));

src/librustc/traits/project.rs

+100-16
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,13 @@ struct ProjectionTyCandidateSet<'tcx> {
122122
///
123123
/// for<...> <T as Trait>::U == V
124124
///
125-
/// If successful, this may result in additional obligations.
125+
/// If successful, this may result in additional obligations. Also returns
126+
/// the projection cache key used to track these additional obligations.
126127
pub fn poly_project_and_unify_type<'cx, 'gcx, 'tcx>(
127128
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
128129
obligation: &PolyProjectionObligation<'tcx>)
129-
-> Result<Option<Vec<PredicateObligation<'tcx>>>, MismatchedProjectionTypes<'tcx>>
130+
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
131+
MismatchedProjectionTypes<'tcx>>
130132
{
131133
debug!("poly_project_and_unify_type(obligation={:?})",
132134
obligation);
@@ -162,7 +164,8 @@ pub fn poly_project_and_unify_type<'cx, 'gcx, 'tcx>(
162164
fn project_and_unify_type<'cx, 'gcx, 'tcx>(
163165
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
164166
obligation: &ProjectionObligation<'tcx>)
165-
-> Result<Option<Vec<PredicateObligation<'tcx>>>, MismatchedProjectionTypes<'tcx>>
167+
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
168+
MismatchedProjectionTypes<'tcx>>
166169
{
167170
debug!("project_and_unify_type(obligation={:?})",
168171
obligation);
@@ -397,6 +400,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
397400
let infcx = selcx.infcx();
398401

399402
let projection_ty = infcx.resolve_type_vars_if_possible(&projection_ty);
403+
let cache_key = ProjectionCacheKey { ty: projection_ty };
400404

401405
debug!("opt_normalize_projection_type(\
402406
projection_ty={:?}, \
@@ -412,7 +416,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
412416
// bounds. It might be the case that we want two distinct caches,
413417
// or else another kind of cache entry.
414418

415-
match infcx.projection_cache.borrow_mut().try_start(projection_ty) {
419+
match infcx.projection_cache.borrow_mut().try_start(cache_key) {
416420
Ok(()) => { }
417421
Err(ProjectionCacheEntry::Ambiguous) => {
418422
// If we found ambiguity the last time, that generally
@@ -523,7 +527,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
523527
obligations,
524528
}
525529
};
526-
infcx.projection_cache.borrow_mut().complete(projection_ty, &result);
530+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
527531
Some(result)
528532
}
529533
Ok(ProjectedTy::NoProgress(projected_ty)) => {
@@ -534,14 +538,14 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
534538
value: projected_ty,
535539
obligations: vec![]
536540
};
537-
infcx.projection_cache.borrow_mut().complete(projection_ty, &result);
541+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
538542
Some(result)
539543
}
540544
Err(ProjectionTyError::TooManyCandidates) => {
541545
debug!("opt_normalize_projection_type: \
542546
too many candidates");
543547
infcx.projection_cache.borrow_mut()
544-
.ambiguous(projection_ty);
548+
.ambiguous(cache_key);
545549
None
546550
}
547551
Err(ProjectionTyError::TraitSelectionError(_)) => {
@@ -552,7 +556,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
552556
// reported later
553557

554558
infcx.projection_cache.borrow_mut()
555-
.error(projection_ty);
559+
.error(cache_key);
556560
Some(normalize_to_error(selcx, param_env, projection_ty, cause, depth))
557561
}
558562
}
@@ -1381,8 +1385,62 @@ fn assoc_ty_def<'cx, 'gcx, 'tcx>(
13811385

13821386
// # Cache
13831387

1388+
/// The projection cache. Unlike the standard caches, this can
1389+
/// include infcx-dependent type variables - therefore, we have to roll
1390+
/// the cache back each time we roll a snapshot back, to avoid assumptions
1391+
/// on yet-unresolved inference variables. Types with skolemized regions
1392+
/// also have to be removed when the respective snapshot ends.
1393+
///
1394+
/// Because of that, projection cache entries can be "stranded" and left
1395+
/// inaccessible when type variables inside the key are resolved. We make no
1396+
/// attempt to recover or remove "stranded" entries, but rather let them be
1397+
/// (for the lifetime of the infcx).
1398+
///
1399+
/// Entries in the projection cache might contain inference variables
1400+
/// that will be resolved by obligations on the projection cache entry - e.g.
1401+
/// when a type parameter in the associated type is constrained through
1402+
/// an "RFC 447" projection on the impl.
1403+
///
1404+
/// When working with a fulfillment context, the derived obligations of each
1405+
/// projection cache entry will be registered on the fulfillcx, so any users
1406+
/// that can wait for a fulfillcx fixed point need not care about this. However,
1407+
/// users that don't wait for a fixed point (e.g. trait evaluation) have to
1408+
/// resolve the obligations themselves to make sure the projected result is
1409+
/// ok and avoid issues like #43132.
1410+
///
1411+
/// If that is done, after evaluation the obligations, it is a good idea to
1412+
/// call `ProjectionCache::complete` to make sure the obligations won't be
1413+
/// re-evaluated and avoid an exponential worst-case.
1414+
///
1415+
/// FIXME: we probably also want some sort of cross-infcx cache here to
1416+
/// reduce the amount of duplication. Let's see what we get with the Chalk
1417+
/// reforms.
13841418
pub struct ProjectionCache<'tcx> {
1385-
map: SnapshotMap<ty::ProjectionTy<'tcx>, ProjectionCacheEntry<'tcx>>,
1419+
map: SnapshotMap<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>,
1420+
}
1421+
1422+
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
1423+
pub struct ProjectionCacheKey<'tcx> {
1424+
ty: ty::ProjectionTy<'tcx>
1425+
}
1426+
1427+
impl<'cx, 'gcx, 'tcx> ProjectionCacheKey<'tcx> {
1428+
pub fn from_poly_projection_predicate(selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
1429+
predicate: &ty::PolyProjectionPredicate<'tcx>)
1430+
-> Option<Self>
1431+
{
1432+
let infcx = selcx.infcx();
1433+
// We don't do cross-snapshot caching of obligations with escaping regions,
1434+
// so there's no cache key to use
1435+
infcx.tcx.no_late_bound_regions(&predicate)
1436+
.map(|predicate| ProjectionCacheKey {
1437+
// We don't attempt to match up with a specific type-variable state
1438+
// from a specific call to `opt_normalize_projection_type` - if
1439+
// there's no precise match, the original cache entry is "stranded"
1440+
// anyway.
1441+
ty: infcx.resolve_type_vars_if_possible(&predicate.projection_ty)
1442+
})
1443+
}
13861444
}
13871445

13881446
#[derive(Clone, Debug)]
@@ -1395,7 +1453,7 @@ enum ProjectionCacheEntry<'tcx> {
13951453

13961454
// NB: intentionally not Clone
13971455
pub struct ProjectionCacheSnapshot {
1398-
snapshot: Snapshot
1456+
snapshot: Snapshot,
13991457
}
14001458

14011459
impl<'tcx> ProjectionCache<'tcx> {
@@ -1414,7 +1472,7 @@ impl<'tcx> ProjectionCache<'tcx> {
14141472
}
14151473

14161474
pub fn rollback_skolemized(&mut self, snapshot: &ProjectionCacheSnapshot) {
1417-
self.map.partial_rollback(&snapshot.snapshot, &|k| k.has_re_skol());
1475+
self.map.partial_rollback(&snapshot.snapshot, &|k| k.ty.has_re_skol());
14181476
}
14191477

14201478
pub fn commit(&mut self, snapshot: ProjectionCacheSnapshot) {
@@ -1424,7 +1482,7 @@ impl<'tcx> ProjectionCache<'tcx> {
14241482
/// Try to start normalize `key`; returns an error if
14251483
/// normalization already occurred (this error corresponds to a
14261484
/// cache hit, so it's actually a good thing).
1427-
fn try_start(&mut self, key: ty::ProjectionTy<'tcx>)
1485+
fn try_start(&mut self, key: ProjectionCacheKey<'tcx>)
14281486
-> Result<(), ProjectionCacheEntry<'tcx>> {
14291487
if let Some(entry) = self.map.get(&key) {
14301488
return Err(entry.clone());
@@ -1435,25 +1493,51 @@ impl<'tcx> ProjectionCache<'tcx> {
14351493
}
14361494

14371495
/// Indicates that `key` was normalized to `value`.
1438-
fn complete(&mut self, key: ty::ProjectionTy<'tcx>, value: &NormalizedTy<'tcx>) {
1439-
debug!("ProjectionCacheEntry::complete: adding cache entry: key={:?}, value={:?}",
1496+
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) {
1497+
debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
14401498
key, value);
14411499
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone()));
14421500
assert!(!fresh_key, "never started projecting `{:?}`", key);
14431501
}
14441502

1503+
/// Mark the relevant projection cache key as having its derived obligations
1504+
/// complete, so they won't have to be re-computed (this is OK to do in a
1505+
/// snapshot - if the snapshot is rolled back, the obligations will be
1506+
/// marked as incomplete again).
1507+
pub fn complete(&mut self, key: ProjectionCacheKey<'tcx>) {
1508+
let ty = match self.map.get(&key) {
1509+
Some(&ProjectionCacheEntry::NormalizedTy(ref ty)) => {
1510+
debug!("ProjectionCacheEntry::complete({:?}) - completing {:?}",
1511+
key, ty);
1512+
ty.value
1513+
}
1514+
ref value => {
1515+
// Type inference could "strand behind" old cache entries. Leave
1516+
// them alone for now.
1517+
debug!("ProjectionCacheEntry::complete({:?}) - ignoring {:?}",
1518+
key, value);
1519+
return
1520+
}
1521+
};
1522+
1523+
self.map.insert(key, ProjectionCacheEntry::NormalizedTy(Normalized {
1524+
value: ty,
1525+
obligations: vec![]
1526+
}));
1527+
}
1528+
14451529
/// Indicates that trying to normalize `key` resulted in
14461530
/// ambiguity. No point in trying it again then until we gain more
14471531
/// type information (in which case, the "fully resolved" key will
14481532
/// be different).
1449-
fn ambiguous(&mut self, key: ty::ProjectionTy<'tcx>) {
1533+
fn ambiguous(&mut self, key: ProjectionCacheKey<'tcx>) {
14501534
let fresh = self.map.insert(key, ProjectionCacheEntry::Ambiguous);
14511535
assert!(!fresh, "never started projecting `{:?}`", key);
14521536
}
14531537

14541538
/// Indicates that trying to normalize `key` resulted in
14551539
/// error.
1456-
fn error(&mut self, key: ty::ProjectionTy<'tcx>) {
1540+
fn error(&mut self, key: ProjectionCacheKey<'tcx>) {
14571541
let fresh = self.map.insert(key, ProjectionCacheEntry::Error);
14581542
assert!(!fresh, "never started projecting `{:?}`", key);
14591543
}

src/librustc/traits/select.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use self::EvaluationResult::*;
1616
use super::coherence;
1717
use super::DerivedObligationCause;
1818
use super::project;
19-
use super::project::{normalize_with_depth, Normalized};
19+
use super::project::{normalize_with_depth, Normalized, ProjectionCacheKey};
2020
use super::{PredicateObligation, TraitObligation, ObligationCause};
2121
use super::{ObligationCauseCode, BuiltinDerivedObligation, ImplDerivedObligation};
2222
use super::{SelectionError, Unimplemented, OutputTypeParameterMismatch};
@@ -665,8 +665,14 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
665665
let project_obligation = obligation.with(data.clone());
666666
match project::poly_project_and_unify_type(self, &project_obligation) {
667667
Ok(Some(subobligations)) => {
668-
self.evaluate_predicates_recursively(previous_stack,
669-
subobligations.iter())
668+
let result = self.evaluate_predicates_recursively(previous_stack,
669+
subobligations.iter());
670+
if let Some(key) =
671+
ProjectionCacheKey::from_poly_projection_predicate(self, data)
672+
{
673+
self.infcx.projection_cache.borrow_mut().complete(key);
674+
}
675+
result
670676
}
671677
Ok(None) => {
672678
EvaluatedToAmbig

0 commit comments

Comments
 (0)