diff --git a/src/librustc/infer/mod.rs b/src/librustc/infer/mod.rs index 21af92a25e6..d838b2226e8 100644 --- a/src/librustc/infer/mod.rs +++ b/src/librustc/infer/mod.rs @@ -1160,6 +1160,18 @@ pub fn resolve_type_vars_if_possible(&self, value: &T) -> T value.fold_with(&mut r) } + /// Returns true if `T` contains unresolved type variables. In the + /// process of visiting `T`, this will resolve (where possible) + /// type variables in `T`, but it never constructs the final, + /// resolved type, so it's more efficient than + /// `resolve_type_vars_if_possible()`. + pub fn any_unresolved_type_vars(&self, value: &T) -> bool + where T: TypeFoldable<'tcx> + { + let mut r = resolve::UnresolvedTypeFinder::new(self); + value.visit_with(&mut r) + } + pub fn resolve_type_and_region_vars_if_possible(&self, value: &T) -> T where T: TypeFoldable<'tcx> { diff --git a/src/librustc/infer/resolve.rs b/src/librustc/infer/resolve.rs index 639a330dc6e..10899e42afb 100644 --- a/src/librustc/infer/resolve.rs +++ b/src/librustc/infer/resolve.rs @@ -10,7 +10,7 @@ use super::{InferCtxt, FixupError, FixupResult}; use ty::{self, Ty, TyCtxt, TypeFoldable}; -use ty::fold::TypeFolder; +use ty::fold::{TypeFolder, TypeVisitor}; /////////////////////////////////////////////////////////////////////////// // OPPORTUNISTIC TYPE RESOLVER @@ -80,6 +80,43 @@ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { } } +/////////////////////////////////////////////////////////////////////////// +// UNRESOLVED TYPE FINDER + +/// The unresolved type **finder** walks your type and searches for +/// type variables that don't yet have a value. They get pushed into a +/// vector. It does not construct the fully resolved type (which might +/// involve some hashing and so forth). +pub struct UnresolvedTypeFinder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { + infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, +} + +impl<'a, 'gcx, 'tcx> UnresolvedTypeFinder<'a, 'gcx, 'tcx> { + pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self { + UnresolvedTypeFinder { infcx } + } +} + +impl<'a, 'gcx, 'tcx> TypeVisitor<'tcx> for UnresolvedTypeFinder<'a, 'gcx, 'tcx> { + fn visit_ty(&mut self, t: Ty<'tcx>) -> bool { + let t = self.infcx.shallow_resolve(t); + if t.has_infer_types() { + if let ty::TyInfer(_) = t.sty { + // Since we called `shallow_resolve` above, this must + // be an (as yet...) unresolved inference variable. + true + } else { + // Otherwise, visit its contents. + t.super_visit_with(self) + } + } else { + // Micro-optimize: no inference types at all Can't have unresolved type + // variables, no need to visit the contents. + false + } + } +} + /////////////////////////////////////////////////////////////////////////// // FULL TYPE RESOLUTION diff --git a/src/librustc/traits/project.rs b/src/librustc/traits/project.rs index 512cfee12b0..242281a0c77 100644 --- a/src/librustc/traits/project.rs +++ b/src/librustc/traits/project.rs @@ -25,7 +25,7 @@ use super::util; use hir::def_id::DefId; -use infer::InferOk; +use infer::{InferCtxt, InferOk}; use infer::type_variable::TypeVariableOrigin; use rustc_data_structures::snapshot_map::{Snapshot, SnapshotMap}; use syntax::ast; @@ -416,7 +416,8 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( // bounds. It might be the case that we want two distinct caches, // or else another kind of cache entry. - match infcx.projection_cache.borrow_mut().try_start(cache_key) { + let cache_result = infcx.projection_cache.borrow_mut().try_start(cache_key); + match cache_result { Ok(()) => { } Err(ProjectionCacheEntry::Ambiguous) => { // If we found ambiguity the last time, that generally @@ -466,7 +467,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( projection_ty); selcx.infcx().report_overflow_error(&obligation, false); } - Err(ProjectionCacheEntry::NormalizedTy(ty)) => { + Err(ProjectionCacheEntry::NormalizedTy(mut ty)) => { // If we find the value in the cache, then return it along // with the obligations that went along with it. Note // that, when using a fulfillment context, these @@ -479,6 +480,14 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( debug!("opt_normalize_projection_type: \ found normalized ty `{:?}`", ty); + + // Once we have inferred everything we need to know, we + // can ignore the `obligations` from that point on. + if !infcx.any_unresolved_type_vars(&ty.value) { + infcx.projection_cache.borrow_mut().complete(cache_key); + ty.obligations = vec![]; + } + return Some(ty); } Err(ProjectionCacheEntry::Error) => { @@ -527,7 +536,10 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( obligations, } }; - infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result); + + let cache_value = prune_cache_value_obligations(infcx, &result); + infcx.projection_cache.borrow_mut().insert_ty(cache_key, cache_value); + Some(result) } Ok(ProjectedTy::NoProgress(projected_ty)) => { @@ -538,7 +550,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( value: projected_ty, obligations: vec![] }; - infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result); + infcx.projection_cache.borrow_mut().insert_ty(cache_key, result.clone()); Some(result) } Err(ProjectionTyError::TooManyCandidates) => { @@ -562,6 +574,44 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>( } } +/// If there are unresolved type variables, then we need to include +/// any subobligations that bind them, at least until those type +/// variables are fully resolved. +fn prune_cache_value_obligations<'a, 'gcx, 'tcx>(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, + result: &NormalizedTy<'tcx>) + -> NormalizedTy<'tcx> { + if !infcx.any_unresolved_type_vars(&result.value) { + return NormalizedTy { value: result.value, obligations: vec![] }; + } + + let mut obligations: Vec<_> = + result.obligations + .iter() + .filter(|obligation| match obligation.predicate { + // We found a `T: Foo` predicate, let's check + // if `U` references any unresolved type + // variables. In principle, we only care if this + // projection can help resolve any of the type + // variables found in `result.value` -- but we just + // check for any type variables here, for fear of + // indirect obligations (e.g., we project to `?0`, + // but we have `T: Foo` and `?1: Bar`). + ty::Predicate::Projection(ref data) => + !infcx.any_unresolved_type_vars(&data.ty()), + + // We are only interested in `T: Foo` predicates, whre + // `U` references one of `unresolved_type_vars`. =) + _ => false, + }) + .cloned() + .collect(); + + obligations.shrink_to_fit(); + + NormalizedTy { value: result.value, obligations } +} + /// If we are projecting `::Item`, but `T: Trait` does not /// hold. In various error cases, we cannot generate a valid /// normalized projection. Therefore, we create an inference variable @@ -1493,10 +1543,10 @@ fn try_start(&mut self, key: ProjectionCacheKey<'tcx>) } /// Indicates that `key` was normalized to `value`. - fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) { + fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTy<'tcx>) { debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}", key, value); - let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone())); + let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value)); assert!(!fresh_key, "never started projecting `{:?}`", key); } diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index 1851e1b8d34..1920cdb3f73 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -1017,6 +1017,10 @@ pub fn to_poly_trait_ref(&self, tcx: TyCtxt) -> PolyTraitRef<'tcx> { // levels. ty::Binder(self.0.projection_ty.trait_ref(tcx)) } + + pub fn ty(&self) -> Binder> { + Binder(self.skip_binder().ty) // preserves binding levels + } } pub trait ToPolyTraitRef<'tcx> {