diff --git a/src/librustc/middle/infer/mod.rs b/src/librustc/middle/infer/mod.rs index 4ff1de422117b..e58934ba98840 100644 --- a/src/librustc/middle/infer/mod.rs +++ b/src/librustc/middle/infer/mod.rs @@ -72,6 +72,12 @@ pub struct InferCtxt<'a, 'tcx: 'a> { pub tables: &'a RefCell>, + // Cache for projections. This cache is snapshotted along with the + // infcx. + // + // Public so that `traits::project` can use it. + pub projection_cache: RefCell>, + // We instantiate UnificationTable with bounds because the // types that might instantiate a general type variable have an // order, represented by its upper and lower bounds. @@ -365,6 +371,7 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a TyCtxt<'tcx>, InferCtxt { tcx: tcx, tables: tables, + projection_cache: RefCell::new(traits::ProjectionCache::new()), type_variables: RefCell::new(type_variable::TypeVariableTable::new()), int_unification_table: RefCell::new(UnificationTable::new()), float_unification_table: RefCell::new(UnificationTable::new()), @@ -516,6 +523,7 @@ fn expected_found(a_is_expected: bool, #[must_use = "once you start a snapshot, you should always consume it"] pub struct CombinedSnapshot { + projection_cache_snapshot: traits::ProjectionCacheSnapshot, type_snapshot: type_variable::Snapshot, int_snapshot: unify::Snapshot, float_snapshot: unify::Snapshot, @@ -725,6 +733,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { fn start_snapshot(&self) -> CombinedSnapshot { CombinedSnapshot { + projection_cache_snapshot: self.projection_cache.borrow_mut().snapshot(), type_snapshot: self.type_variables.borrow_mut().snapshot(), int_snapshot: self.int_unification_table.borrow_mut().snapshot(), float_snapshot: self.float_unification_table.borrow_mut().snapshot(), @@ -734,11 +743,15 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { fn rollback_to(&self, cause: &str, snapshot: CombinedSnapshot) { debug!("rollback_to(cause={})", cause); - let CombinedSnapshot { type_snapshot, + let CombinedSnapshot { projection_cache_snapshot, + type_snapshot, int_snapshot, float_snapshot, region_vars_snapshot } = snapshot; + self.projection_cache + .borrow_mut() + .rollback_to(projection_cache_snapshot); self.type_variables .borrow_mut() .rollback_to(type_snapshot); @@ -754,11 +767,15 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { fn commit_from(&self, snapshot: CombinedSnapshot) { debug!("commit_from!"); - let CombinedSnapshot { type_snapshot, + let CombinedSnapshot { projection_cache_snapshot, + type_snapshot, int_snapshot, float_snapshot, region_vars_snapshot } = snapshot; + self.projection_cache + .borrow_mut() + .commit(projection_cache_snapshot); self.type_variables .borrow_mut() .commit(type_snapshot); @@ -805,7 +822,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { F: FnOnce() -> Result { debug!("commit_regions_if_ok()"); - let CombinedSnapshot { type_snapshot, + let CombinedSnapshot { projection_cache_snapshot, + type_snapshot, int_snapshot, float_snapshot, region_vars_snapshot } = self.start_snapshot(); @@ -816,6 +834,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { // Roll back any non-region bindings - they should be resolved // inside `f`, with, e.g. `resolve_type_vars_if_possible`. + self.projection_cache + .borrow_mut() + .rollback_to(projection_cache_snapshot); self.type_variables .borrow_mut() .rollback_to(type_snapshot); diff --git a/src/librustc/middle/traits/fulfill.rs b/src/librustc/middle/traits/fulfill.rs index b237117564228..e09bd96eafd4c 100644 --- a/src/librustc/middle/traits/fulfill.rs +++ b/src/librustc/middle/traits/fulfill.rs @@ -507,6 +507,8 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>, // also includes references to its upvars as part // of its type, and those types are resolved at // the same time. + // + // FIXME(#32286) logic seems false if no upvars pending_obligation.stalled_on = trait_ref_type_vars(selcx, data.to_poly_trait_ref()); diff --git a/src/librustc/middle/traits/mod.rs b/src/librustc/middle/traits/mod.rs index 5f66e9e6344ad..85597a807cbe4 100644 --- a/src/librustc/middle/traits/mod.rs +++ b/src/librustc/middle/traits/mod.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Trait Resolution. See the Book for more. +//! Trait Resolution. See README.md for an overview of how this works. pub use self::SelectionError::*; pub use self::FulfillmentErrorCode::*; @@ -36,8 +36,9 @@ pub use self::coherence::orphan_check; pub use self::coherence::overlapping_impls; pub use self::coherence::OrphanCheckErr; pub use self::fulfill::{FulfillmentContext, GlobalFulfilledPredicates, RegionObligation}; -pub use self::project::{MismatchedProjectionTypes, ProjectionMode}; +pub use self::project::MismatchedProjectionTypes; pub use self::project::{normalize, Normalized}; +pub use self::project::{ProjectionCache, ProjectionCacheSnapshot, ProjectionMode}; pub use self::object_safety::is_object_safe; pub use self::object_safety::astconv_object_safety_violations; pub use self::object_safety::object_safety_violations; diff --git a/src/librustc/middle/traits/project.rs b/src/librustc/middle/traits/project.rs index e86f3ed01a49d..0d1f44d0f5334 100644 --- a/src/librustc/middle/traits/project.rs +++ b/src/librustc/middle/traits/project.rs @@ -28,6 +28,7 @@ use middle::infer::{self, TypeOrigin}; use middle::subst::Subst; use middle::ty::{self, ToPredicate, ToPolyTraitRef, Ty, TyCtxt}; use middle::ty::fold::{TypeFoldable, TypeFolder}; +use rustc_data_structures::snapshot_map::{Snapshot, SnapshotMap}; use syntax::parse::token; use syntax::ast; use util::common::FN_OUTPUT_NAME; @@ -152,14 +153,8 @@ enum ProjectionTyCandidate<'tcx> { // from the definition of `Trait` when you have something like <::B as Trait2>::C TraitDef(ty::PolyProjectionPredicate<'tcx>), - // defined in an impl - Impl(VtableImplData<'tcx, PredicateObligation<'tcx>>), - - // closure return type - Closure(VtableClosureData<'tcx, PredicateObligation<'tcx>>), - - // fn pointer return type - FnPointer(Ty<'tcx>), + // from a "impl" (or a "pseudo-impl" returned by select) + Select, } struct ProjectionTyCandidateSet<'tcx> { @@ -461,6 +456,73 @@ fn opt_normalize_projection_type<'a,'b,'tcx>( projection_ty, depth); + // FIXME(#20304) For now, I am caching here, which is good, but it + // means we don't capture the type variables that are created in + // the case of ambiguity. Which means we may create a large stream + // of such variables. OTOH, if we move the caching up a level, we + // would not benefit from caching when proving `T: Trait` + // bounds. It might be the case that we want two distinct caches, + // or else another kind of cache entry. + + let infcx = selcx.infcx(); + + let projection_ty = infcx.resolve_type_vars_if_possible(&projection_ty); + + match infcx.projection_cache.borrow_mut().try_start(projection_ty) { + Ok(()) => { } + Err(ProjectionCacheEntry::Ambiguous) => { + // If we found ambiguity the last time, that generally + // means we will continue to do so until some type in the + // key changes (and we know it hasn't, because we just + // fully resolved it). One exception though is closure + // types, which can transition from having a fixed kind to + // no kind with no visible change in the key. + // + // FIXME(#32286) refactor this so that closure type + // changes + if !projection_ty.has_closure_types() { + return None; + } + } + Err(ProjectionCacheEntry::InProgress) => { + // If while normalized A::B, we are asked to normalize + // A::B, just return A::B itself. This is a conservative + // answer, in the sense that A::B *is* clearly equivalent + // to A::B, though there may be a better value we can + // find. + + // Under lazy normalization, this can arise when + // bootstrapping. That is, imagine an environment with a + // where-clause like `A::B == u32`. Now, if we are asked + // to normalize `A::B`, we will want to check the + // where-clauses in scope. So we will try to unify `A::B` + // with `A::B`, which can trigger a recursive + // normalization. In that case, I think we will want this code: + // + // ``` + // let ty = selcx.tcx().mk_projection(projection_ty.trait_ref, + // projection_ty.item_name); + // return Some(NormalizedTy { value: v, obligations: vec![] }); + // ``` + + // But for now, let's classify this as an overflow: + let recursion_limit = selcx.tcx().sess.recursion_limit.get(); + let obligation = Obligation::with_depth(cause.clone(), + recursion_limit, + projection_ty); + report_overflow_error(selcx.infcx(), &obligation, false); + } + Err(ProjectionCacheEntry::NormalizedTy(ty)) => { + // If we find the value in the cache, then the obligations + // have already been returned from the previous entry (and + // should therefore have been honored). + return Some(NormalizedTy { value: ty, obligations: vec![] }); + } + Err(ProjectionCacheEntry::Error) => { + return Some(normalize_to_error(selcx, projection_ty, cause, depth)); + } + } + let obligation = Obligation::with_depth(cause.clone(), depth, projection_ty.clone()); match project_type(selcx, &obligation) { Ok(ProjectedTy::Progress(projected_ty, mut obligations)) => { @@ -483,11 +545,15 @@ fn opt_normalize_projection_type<'a,'b,'tcx>( depth); obligations.extend(normalizer.obligations); + infcx.projection_cache.borrow_mut() + .complete(projection_ty, normalized_ty); Some(Normalized { value: normalized_ty, obligations: obligations, }) } else { + infcx.projection_cache.borrow_mut() + .complete(projection_ty, projected_ty); Some(Normalized { value: projected_ty, obligations: obligations, @@ -497,6 +563,8 @@ fn opt_normalize_projection_type<'a,'b,'tcx>( Ok(ProjectedTy::NoProgress(projected_ty)) => { debug!("normalize_projection_type: projected_ty={:?} no progress", projected_ty); + infcx.projection_cache.borrow_mut() + .complete(projection_ty, projected_ty); Some(Normalized { value: projected_ty, obligations: vec!() @@ -504,6 +572,8 @@ fn opt_normalize_projection_type<'a,'b,'tcx>( } Err(ProjectionTyError::TooManyCandidates) => { debug!("normalize_projection_type: too many candidates"); + infcx.projection_cache.borrow_mut() + .ambiguous(projection_ty); None } Err(ProjectionTyError::TraitSelectionError(_)) => { @@ -513,6 +583,8 @@ fn opt_normalize_projection_type<'a,'b,'tcx>( // Trait`, which when processed will cause the error to be // reported later + infcx.projection_cache.borrow_mut() + .error(projection_ty); Some(normalize_to_error(selcx, projection_ty, cause, depth)) } } @@ -560,6 +632,9 @@ enum ProjectedTy<'tcx> { } /// Compute the result of a projection type (if we can). +/// +/// IMPORTANT: +/// - `obligation` must be fully normalized fn project_type<'cx,'tcx>( selcx: &mut SelectionContext<'cx,'tcx>, obligation: &ProjectionTyObligation<'tcx>) @@ -574,8 +649,7 @@ fn project_type<'cx,'tcx>( report_overflow_error(selcx.infcx(), &obligation, true); } - let obligation_trait_ref = - selcx.infcx().resolve_type_vars_if_possible(&obligation.predicate.trait_ref); + let obligation_trait_ref = &obligation.predicate.trait_ref; debug!("project: obligation_trait_ref={:?}", obligation_trait_ref); @@ -645,10 +719,8 @@ fn project_type<'cx,'tcx>( debug!("retaining param-env candidates only from {:?}", candidates.vec); candidates.vec.retain(|c| match *c { ProjectionTyCandidate::ParamEnv(..) => true, - ProjectionTyCandidate::Impl(..) | - ProjectionTyCandidate::Closure(..) | ProjectionTyCandidate::TraitDef(..) | - ProjectionTyCandidate::FnPointer(..) => false, + ProjectionTyCandidate::Select => false, }); debug!("resulting candidate set: {:?}", candidates.vec); if candidates.vec.len() != 1 { @@ -658,78 +730,12 @@ fn project_type<'cx,'tcx>( assert!(candidates.vec.len() <= 1); - let possible_candidate = candidates.vec.pop().and_then(|candidate| { - // In Any (i.e. trans) mode, all projections succeed; - // otherwise, we need to be sensitive to `default` and - // specialization. - if !selcx.projection_mode().is_any() { - if let ProjectionTyCandidate::Impl(ref impl_data) = candidate { - if let Some(node_item) = assoc_ty_def(selcx, - impl_data.impl_def_id, - obligation.predicate.item_name) { - if node_item.node.is_from_trait() { - if node_item.item.ty.is_some() { - // If the associated type has a default from the - // trait, that should be considered `default` and - // hence not projected. - // - // Note, however, that we allow a projection from - // the trait specifically in the case that the trait - // does *not* give a default. This is purely to - // avoid spurious errors: the situation can only - // arise when *no* impl in the specialization chain - // has provided a definition for the type. When we - // confirm the candidate, we'll turn the projection - // into a TyError, since the actual error will be - // reported in `check_impl_items_against_trait`. - return None; - } - } else if node_item.item.defaultness.is_default() { - return None; - } - } else { - // Normally this situation could only arise througha - // compiler bug, but at coherence-checking time we only look - // at the topmost impl (we don't even consider the trait - // itself) for the definition -- so we can fail to find a - // definition of the type even if it exists. - - // For now, we just unconditionally ICE, because otherwise, - // examples like the following will succeed: - // - // ``` - // trait Assoc { - // type Output; - // } - // - // impl Assoc for T { - // default type Output = bool; - // } - // - // impl Assoc for u8 {} - // impl Assoc for u16 {} - // - // trait Foo {} - // impl Foo for ::Output {} - // impl Foo for ::Output {} - // return None; - // } - // ``` - // - // The essential problem here is that the projection fails, - // leaving two unnormalized types, which appear not to unify - // -- so the overlap check succeeds, when it should fail. - selcx.tcx().sess.bug("Tried to project an inherited associated type during \ - coherence checking, which is currently not supported."); - } - } - } - Some(candidate) - }); - - match possible_candidate { + match candidates.vec.pop() { Some(candidate) => { - let (ty, obligations) = confirm_candidate(selcx, obligation, candidate); + let (ty, obligations) = confirm_candidate(selcx, + obligation, + &obligation_trait_ref, + candidate); Ok(ProjectedTy::Progress(ty, obligations)) } None => { @@ -845,38 +851,6 @@ fn assemble_candidates_from_predicates<'cx,'tcx,I>( } } -fn assemble_candidates_from_object_type<'cx,'tcx>( - selcx: &mut SelectionContext<'cx,'tcx>, - obligation: &ProjectionTyObligation<'tcx>, - obligation_trait_ref: &ty::TraitRef<'tcx>, - candidate_set: &mut ProjectionTyCandidateSet<'tcx>) -{ - let self_ty = obligation_trait_ref.self_ty(); - let object_ty = selcx.infcx().shallow_resolve(self_ty); - debug!("assemble_candidates_from_object_type(object_ty={:?})", - object_ty); - let data = match object_ty.sty { - ty::TyTrait(ref data) => data, - _ => { - selcx.tcx().sess.span_bug( - obligation.cause.span, - &format!("assemble_candidates_from_object_type called with non-object: {:?}", - object_ty)); - } - }; - let projection_bounds = data.projection_bounds_with_self_ty(selcx.tcx(), object_ty); - let env_predicates = projection_bounds.iter() - .map(|p| p.to_predicate()) - .collect(); - let env_predicates = elaborate_predicates(selcx.tcx(), env_predicates); - assemble_candidates_from_predicates(selcx, - obligation, - obligation_trait_ref, - candidate_set, - ProjectionTyCandidate::ParamEnv, - env_predicates) -} - fn assemble_candidates_from_impls<'cx,'tcx>( selcx: &mut SelectionContext<'cx,'tcx>, obligation: &ProjectionTyObligation<'tcx>, @@ -888,82 +862,183 @@ fn assemble_candidates_from_impls<'cx,'tcx>( // start out by selecting the predicate `T as TraitRef<...>`: let poly_trait_ref = obligation_trait_ref.to_poly_trait_ref(); let trait_obligation = obligation.with(poly_trait_ref.to_poly_trait_predicate()); - let vtable = match selcx.select(&trait_obligation) { - Ok(Some(vtable)) => vtable, - Ok(None) => { - candidate_set.ambiguous = true; - return Ok(()); - } - Err(e) => { - debug!("assemble_candidates_from_impls: selection error {:?}", - e); - return Err(e); - } - }; + selcx.infcx().probe(|_| { + let vtable = match selcx.select(&trait_obligation) { + Ok(Some(vtable)) => vtable, + Ok(None) => { + candidate_set.ambiguous = true; + return Ok(()); + } + Err(e) => { + debug!("assemble_candidates_from_impls: selection error {:?}", + e); + return Err(e); + } + }; - match vtable { - super::VtableImpl(data) => { - debug!("assemble_candidates_from_impls: impl candidate {:?}", - data); + match vtable { + super::VtableClosure(_) | + super::VtableFnPointer(_) | + super::VtableObject(_) => { + debug!("assemble_candidates_from_impls: vtable={:?}", + vtable); - candidate_set.vec.push( - ProjectionTyCandidate::Impl(data)); - } - super::VtableObject(_) => { - assemble_candidates_from_object_type( - selcx, obligation, obligation_trait_ref, candidate_set); - } - super::VtableClosure(data) => { - candidate_set.vec.push( - ProjectionTyCandidate::Closure(data)); - } - super::VtableFnPointer(fn_type) => { - candidate_set.vec.push( - ProjectionTyCandidate::FnPointer(fn_type)); - } - super::VtableParam(..) => { - // This case tell us nothing about the value of an - // associated type. Consider: - // - // ``` - // trait SomeTrait { type Foo; } - // fn foo(...) { } - // ``` - // - // If the user writes `::Foo`, then the `T - // : SomeTrait` binding does not help us decide what the - // type `Foo` is (at least, not more specifically than - // what we already knew). - // - // But wait, you say! What about an example like this: - // - // ``` - // fn bar>(...) { ... } - // ``` - // - // Doesn't the `T : Sometrait` predicate help - // resolve `T::Foo`? And of course it does, but in fact - // that single predicate is desugared into two predicates - // in the compiler: a trait predicate (`T : SomeTrait`) and a - // projection. And the projection where clause is handled - // in `assemble_candidates_from_param_env`. - } - super::VtableDefaultImpl(..) | - super::VtableBuiltin(..) => { - // These traits have no associated types. - selcx.tcx().sess.span_bug( - obligation.cause.span, - &format!("Cannot project an associated type from `{:?}`", - vtable)); + candidate_set.vec.push(ProjectionTyCandidate::Select); + } + super::VtableImpl(ref impl_data) if !selcx.projection_mode().is_any() => { + // We have to be careful when projecting out of an + // impl because of specialization. If we are not in + // trans (i.e., projection mode is not "any"), and the + // impl's type is declared as default, then we disable + // projection (even if the trait ref is fully + // monomorphic). In the case where trait ref is not + // fully monomorphic (i.e., includes type parameters), + // this is because those type parameters may + // ultimately be bound to types from other crates that + // may have specialized impls we can't see. In the + // case where the trait ref IS fully monomorphic, this + // is a policy decision that we made in the RFC in + // order to preserve flexibility for the crate that + // defined the specializable impl to specialize later + // for existing types. + // + // In either case, we handle this by not adding a + // candidate for an impl if it contains a `default` + // type. + let opt_node_item = assoc_ty_def(selcx, + impl_data.impl_def_id, + obligation.predicate.item_name); + let new_candidate = if let Some(node_item) = opt_node_item { + if node_item.node.is_from_trait() { + if node_item.item.ty.is_some() { + // The impl inherited a `type Foo = + // Bar` given in the trait, which is + // implicitly default. No candidate. + None + } else { + // The impl did not specify `type` and neither + // did the trait: + // + // ```rust + // trait Foo { type T; } + // impl Foo for Bar { } + // ``` + // + // This is an error, but it will be + // reported in `check_impl_items_against_trait`. + // We accept it here but will flag it as + // an error when we confirm the candidate + // (which will ultimately lead to `normalize_to_error` + // being invoked). + Some(ProjectionTyCandidate::Select) + } + } else if node_item.item.defaultness.is_default() { + // The impl specified `default type Foo = + // Bar`. No candidate. + None + } else { + // The impl specified `type Foo = Bar` + // with no default. Add a candidate. + Some(ProjectionTyCandidate::Select) + } + } else { + // This is saying that neither the trait nor + // the impl contain a definition for this + // associated type. Normally this situation + // could only arise through a compiler bug -- + // if the user wrote a bad item name, it + // should have failed in astconv. **However**, + // at coherence-checking time, we only look at + // the topmost impl (we don't even consider + // the trait itself) for the definition -- and + // so in that case it may be that the trait + // *DOES* have a declaration, but we don't see + // it, and we end up in this branch. + // + // This is kind of tricky to handle actually. + // For now, we just unconditionally ICE, + // because otherwise, examples like the + // following will succeed: + // + // ``` + // trait Assoc { + // type Output; + // } + // + // impl Assoc for T { + // default type Output = bool; + // } + // + // impl Assoc for u8 {} + // impl Assoc for u16 {} + // + // trait Foo {} + // impl Foo for ::Output {} + // impl Foo for ::Output {} + // return None; + // } + // ``` + // + // The essential problem here is that the + // projection fails, leaving two unnormalized + // types, which appear not to unify -- so the + // overlap check succeeds, when it should + // fail. + selcx.tcx().sess.bug("Tried to project an inherited associated type during \ + coherence checking, which is currently not supported."); + }; + candidate_set.vec.extend(new_candidate); + } + super::VtableImpl(_) => { + // In trans mode, we can just project out of impls, no prob. + assert!(selcx.projection_mode().is_any()); + candidate_set.vec.push(ProjectionTyCandidate::Select); + } + super::VtableParam(..) => { + // This case tell us nothing about the value of an + // associated type. Consider: + // + // ``` + // trait SomeTrait { type Foo; } + // fn foo(...) { } + // ``` + // + // If the user writes `::Foo`, then the `T + // : SomeTrait` binding does not help us decide what the + // type `Foo` is (at least, not more specifically than + // what we already knew). + // + // But wait, you say! What about an example like this: + // + // ``` + // fn bar>(...) { ... } + // ``` + // + // Doesn't the `T : Sometrait` predicate help + // resolve `T::Foo`? And of course it does, but in fact + // that single predicate is desugared into two predicates + // in the compiler: a trait predicate (`T : SomeTrait`) and a + // projection. And the projection where clause is handled + // in `assemble_candidates_from_param_env`. + } + super::VtableDefaultImpl(..) | + super::VtableBuiltin(..) => { + // These traits have no associated types. + selcx.tcx().sess.span_bug( + obligation.cause.span, + &format!("Cannot project an associated type from `{:?}`", + vtable)); + } } - } - Ok(()) + Ok(()) + }) } fn confirm_candidate<'cx,'tcx>( selcx: &mut SelectionContext<'cx,'tcx>, obligation: &ProjectionTyObligation<'tcx>, + obligation_trait_ref: &ty::TraitRef<'tcx>, candidate: ProjectionTyCandidate<'tcx>) -> (Ty<'tcx>, Vec>) { @@ -977,20 +1052,117 @@ fn confirm_candidate<'cx,'tcx>( confirm_param_env_candidate(selcx, obligation, poly_projection) } - ProjectionTyCandidate::Impl(impl_vtable) => { - confirm_impl_candidate(selcx, obligation, impl_vtable) + ProjectionTyCandidate::Select => { + confirm_select_candidate(selcx, obligation, obligation_trait_ref) } + } +} - ProjectionTyCandidate::Closure(closure_vtable) => { - confirm_closure_candidate(selcx, obligation, closure_vtable) +fn confirm_select_candidate<'cx,'tcx>( + selcx: &mut SelectionContext<'cx,'tcx>, + obligation: &ProjectionTyObligation<'tcx>, + obligation_trait_ref: &ty::TraitRef<'tcx>) + -> (Ty<'tcx>, Vec>) +{ + let poly_trait_ref = obligation_trait_ref.to_poly_trait_ref(); + let trait_obligation = obligation.with(poly_trait_ref.to_poly_trait_predicate()); + let vtable = match selcx.select(&trait_obligation) { + Ok(Some(vtable)) => vtable, + _ => { + selcx.tcx().sess.span_bug( + obligation.cause.span, + &format!("Failed to select `{:?}`", trait_obligation)); } + }; - ProjectionTyCandidate::FnPointer(fn_type) => { - confirm_fn_pointer_candidate(selcx, obligation, fn_type) - } + match vtable { + super::VtableImpl(data) => + confirm_impl_candidate(selcx, obligation, data), + super::VtableClosure(data) => + confirm_closure_candidate(selcx, obligation, data), + super::VtableFnPointer(data) => + confirm_fn_pointer_candidate(selcx, obligation, data), + super::VtableObject(_) => + confirm_object_candidate(selcx, obligation, obligation_trait_ref), + super::VtableDefaultImpl(..) | + super::VtableParam(..) | + super::VtableBuiltin(..) => + // we don't create Select candidates with this kind of resolution + selcx.tcx().sess.span_bug( + obligation.cause.span, + &format!("Cannot project an associated type from `{:?}`", + vtable)), } } +fn confirm_object_candidate<'cx,'tcx>( + selcx: &mut SelectionContext<'cx,'tcx>, + obligation: &ProjectionTyObligation<'tcx>, + obligation_trait_ref: &ty::TraitRef<'tcx>) + -> (Ty<'tcx>, Vec>) +{ + let self_ty = obligation_trait_ref.self_ty(); + let object_ty = selcx.infcx().shallow_resolve(self_ty); + debug!("assemble_candidates_from_object_type(object_ty={:?})", + object_ty); + let data = match object_ty.sty { + ty::TyTrait(ref data) => data, + _ => { + selcx.tcx().sess.span_bug( + obligation.cause.span, + &format!("assemble_candidates_from_object_type called with non-object: {:?}", + object_ty)); + } + }; + let projection_bounds = data.projection_bounds_with_self_ty(selcx.tcx(), object_ty); + let env_predicates = projection_bounds.iter() + .map(|p| p.to_predicate()) + .collect(); + let env_predicate = { + let env_predicates = elaborate_predicates(selcx.tcx(), env_predicates); + + // select only those projections that are actually projecting an + // item with the correct name + let env_predicates = env_predicates.filter_map(|p| match p { + ty::Predicate::Projection(data) => + if data.item_name() == obligation.predicate.item_name { + Some(data) + } else { + None + }, + _ => None + }); + + // select those with a relevant trait-ref + let mut env_predicates = env_predicates.filter(|data| { + let origin = TypeOrigin::RelateOutputImplTypes(obligation.cause.span); + let data_poly_trait_ref = data.to_poly_trait_ref(); + let obligation_poly_trait_ref = obligation_trait_ref.to_poly_trait_ref(); + selcx.infcx().probe(|_| { + selcx.infcx().sub_poly_trait_refs(false, + origin, + data_poly_trait_ref, + obligation_poly_trait_ref).is_ok() + }) + }); + + // select the first matching one; there really ought to be one or + // else the object type is not WF, since an object type should + // include all of its projections explicitly + match env_predicates.next() { + Some(env_predicate) => env_predicate, + None => { + debug!("confirm_object_candidate: no env-predicate \ + found in object type `{:?}`; ill-formed", + object_ty); + return (selcx.tcx().types.err, vec!()); + } + } + }; + + confirm_param_env_candidate(selcx, obligation, env_predicate) +} + fn confirm_fn_pointer_candidate<'cx,'tcx>( selcx: &mut SelectionContext<'cx,'tcx>, obligation: &ProjectionTyObligation<'tcx>, @@ -1162,3 +1334,77 @@ fn assoc_ty_def<'cx, 'tcx>(selcx: &SelectionContext<'cx, 'tcx>, .next() } } + +// # Cache + +pub struct ProjectionCache<'tcx> { + map: SnapshotMap, ProjectionCacheEntry<'tcx>>, +} + +#[derive(Copy, Clone, Debug)] +enum ProjectionCacheEntry<'tcx> { + InProgress, + Ambiguous, + Error, + NormalizedTy(Ty<'tcx>), +} + +// NB: intentionally not Clone +pub struct ProjectionCacheSnapshot { + snapshot: Snapshot +} + +impl<'tcx> ProjectionCache<'tcx> { + pub fn new() -> Self { + ProjectionCache { + map: SnapshotMap::new() + } + } + + pub fn snapshot(&mut self) -> ProjectionCacheSnapshot { + ProjectionCacheSnapshot { snapshot: self.map.snapshot() } + } + + pub fn rollback_to(&mut self, snapshot: ProjectionCacheSnapshot) { + self.map.rollback_to(snapshot.snapshot); + } + + pub fn commit(&mut self, snapshot: ProjectionCacheSnapshot) { + self.map.commit(snapshot.snapshot); + } + + /// Try to start normalize `key`; returns an error if + /// normalization already occured (this error corresponds to a + /// cache hit, so it's actually a good thing). + fn try_start(&mut self, key: ty::ProjectionTy<'tcx>) + -> Result<(), ProjectionCacheEntry<'tcx>> { + if let Some(entry) = self.map.get(&key) { + return Err(*entry); + } + + self.map.insert(key, ProjectionCacheEntry::InProgress); + Ok(()) + } + + /// Indicates that `key` was normalized to `value`. + fn complete(&mut self, key: ty::ProjectionTy<'tcx>, value: Ty<'tcx>) { + let fresh = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value)); + assert!(!fresh, "never started projecting `{:?}`", key); + } + + /// 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). + fn ambiguous(&mut self, key: ty::ProjectionTy<'tcx>) { + let fresh = self.map.insert(key, ProjectionCacheEntry::Ambiguous); + assert!(!fresh, "never started projecting `{:?}`", key); + } + + /// Indicates that trying to normalize `key` resulted in + /// error. + fn error(&mut self, key: ty::ProjectionTy<'tcx>) { + let fresh = self.map.insert(key, ProjectionCacheEntry::Error); + assert!(!fresh, "never started projecting `{:?}`", key); + } +} diff --git a/src/librustc/middle/ty/flags.rs b/src/librustc/middle/ty/flags.rs index c491bd6ca5e99..f7f06c2d72d7f 100644 --- a/src/librustc/middle/ty/flags.rs +++ b/src/librustc/middle/ty/flags.rs @@ -170,8 +170,13 @@ impl FlagComputation { fn add_region(&mut self, r: ty::Region) { match r { - ty::ReVar(..) | - ty::ReSkolemized(..) => { self.add_flags(TypeFlags::HAS_RE_INFER); } + ty::ReVar(..) => { + self.add_flags(TypeFlags::HAS_RE_INFER); + } + ty::ReSkolemized(..) => { + self.add_flags(TypeFlags::HAS_RE_INFER); + self.add_flags(TypeFlags::HAS_RE_SKOL); + } ty::ReLateBound(debruijn, _) => { self.add_depth(debruijn.depth); } ty::ReEarlyBound(..) => { self.add_flags(TypeFlags::HAS_RE_EARLY_BOUND); } ty::ReStatic => {} diff --git a/src/librustc/middle/ty/mod.rs b/src/librustc/middle/ty/mod.rs index 081196835936c..450677a71f92b 100644 --- a/src/librustc/middle/ty/mod.rs +++ b/src/librustc/middle/ty/mod.rs @@ -473,15 +473,16 @@ bitflags! { const HAS_SELF = 1 << 1, const HAS_TY_INFER = 1 << 2, const HAS_RE_INFER = 1 << 3, - const HAS_RE_EARLY_BOUND = 1 << 4, - const HAS_FREE_REGIONS = 1 << 5, - const HAS_TY_ERR = 1 << 6, - const HAS_PROJECTION = 1 << 7, - const HAS_TY_CLOSURE = 1 << 8, + const HAS_RE_SKOL = 1 << 4, + const HAS_RE_EARLY_BOUND = 1 << 5, + const HAS_FREE_REGIONS = 1 << 6, + const HAS_TY_ERR = 1 << 7, + const HAS_PROJECTION = 1 << 8, + const HAS_TY_CLOSURE = 1 << 9, // true if there are "names" of types and regions and so forth // that are local to a particular fn - const HAS_LOCAL_NAMES = 1 << 9, + const HAS_LOCAL_NAMES = 1 << 10, const NEEDS_SUBST = TypeFlags::HAS_PARAMS.bits | TypeFlags::HAS_SELF.bits | diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index 2234325aa013b..3c57d332d245f 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -40,6 +40,7 @@ pub mod bitvec; pub mod graph; pub mod ivar; pub mod obligation_forest; +pub mod snapshot_map; pub mod snapshot_vec; pub mod transitive_relation; pub mod unify; diff --git a/src/librustc_data_structures/snapshot_map/mod.rs b/src/librustc_data_structures/snapshot_map/mod.rs new file mode 100644 index 0000000000000..e6ec4771a6efd --- /dev/null +++ b/src/librustc_data_structures/snapshot_map/mod.rs @@ -0,0 +1,124 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use fnv::FnvHashMap; +use std::hash::Hash; +use std::ops; + +#[cfg(test)] +mod test; + +pub struct SnapshotMap + where K: Hash + Clone + Eq +{ + map: FnvHashMap, + undo_log: Vec>, +} + +pub struct Snapshot { + len: usize +} + +enum UndoLog { + OpenSnapshot, + CommittedSnapshot, + Inserted(K), + Overwrite(K, V), +} + +impl SnapshotMap + where K: Hash + Clone + Eq +{ + pub fn new() -> Self { + SnapshotMap { + map: FnvHashMap(), + undo_log: vec![] + } + } + + pub fn insert(&mut self, key: K, value: V) -> bool { + match self.map.insert(key.clone(), value) { + None => { + if !self.undo_log.is_empty() { + self.undo_log.push(UndoLog::Inserted(key)); + } + true + } + Some(old_value) => { + if !self.undo_log.is_empty() { + self.undo_log.push(UndoLog::Overwrite(key, old_value)); + } + false + } + } + } + + pub fn get(&self, key: &K) -> Option<&V> { + self.map.get(key) + } + + pub fn snapshot(&mut self) -> Snapshot { + self.undo_log.push(UndoLog::OpenSnapshot); + let len = self.undo_log.len() - 1; + Snapshot { len: len } + } + + fn assert_open_snapshot(&self, snapshot: &Snapshot) { + assert!(snapshot.len < self.undo_log.len()); + assert!(match self.undo_log[snapshot.len] { + UndoLog::OpenSnapshot => true, + _ => false + }); + } + + pub fn commit(&mut self, snapshot: Snapshot) { + self.assert_open_snapshot(&snapshot); + if snapshot.len == 0 { + // The root snapshot. + self.undo_log.truncate(0); + } else { + self.undo_log[snapshot.len] = UndoLog::CommittedSnapshot; + } + } + + pub fn rollback_to(&mut self, snapshot: Snapshot) { + self.assert_open_snapshot(&snapshot); + while self.undo_log.len() > snapshot.len + 1 { + match self.undo_log.pop().unwrap() { + UndoLog::OpenSnapshot => { + panic!("cannot rollback an uncommitted snapshot"); + } + + UndoLog::CommittedSnapshot => { } + + UndoLog::Inserted(key) => { + self.map.remove(&key); + } + + UndoLog::Overwrite(key, old_value) => { + self.map.insert(key, old_value); + } + } + } + + let v = self.undo_log.pop().unwrap(); + assert!(match v { UndoLog::OpenSnapshot => true, _ => false }); + assert!(self.undo_log.len() == snapshot.len); + } +} + +impl<'k, K, V> ops::Index<&'k K> for SnapshotMap + where K: Hash + Clone + Eq +{ + type Output = V; + fn index(&self, key: &'k K) -> &V { + &self.map[key] + } +} diff --git a/src/librustc_data_structures/snapshot_map/test.rs b/src/librustc_data_structures/snapshot_map/test.rs new file mode 100644 index 0000000000000..4114082839b0b --- /dev/null +++ b/src/librustc_data_structures/snapshot_map/test.rs @@ -0,0 +1,50 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::SnapshotMap; + +#[test] +fn basic() { + let mut map = SnapshotMap::new(); + map.insert(22, "twenty-two"); + let snapshot = map.snapshot(); + map.insert(22, "thirty-three"); + assert_eq!(map[&22], "thirty-three"); + map.insert(44, "fourty-four"); + assert_eq!(map[&44], "fourty-four"); + assert_eq!(map.get(&33), None); + map.rollback_to(snapshot); + assert_eq!(map[&22], "twenty-two"); + assert_eq!(map.get(&33), None); + assert_eq!(map.get(&44), None); +} + +#[test] +#[should_panic] +fn out_of_order() { + let mut map = SnapshotMap::new(); + map.insert(22, "twenty-two"); + let snapshot1 = map.snapshot(); + let _snapshot2 = map.snapshot(); + map.rollback_to(snapshot1); +} + +#[test] +fn nested_commit_then_rollback() { + let mut map = SnapshotMap::new(); + map.insert(22, "twenty-two"); + let snapshot1 = map.snapshot(); + let snapshot2 = map.snapshot(); + map.insert(22, "thirty-three"); + map.commit(snapshot2); + assert_eq!(map[&22], "thirty-three"); + map.rollback_to(snapshot1); + assert_eq!(map[&22], "twenty-two"); +} diff --git a/src/test/compile-fail/issue-20831-debruijn.rs b/src/test/compile-fail/issue-20831-debruijn.rs index 3f96a9c342283..dac1625159748 100644 --- a/src/test/compile-fail/issue-20831-debruijn.rs +++ b/src/test/compile-fail/issue-20831-debruijn.rs @@ -39,7 +39,6 @@ impl<'a> Publisher<'a> for MyStruct<'a> { // Not obvious, but there is an implicit lifetime here -------^ //~^^ ERROR cannot infer //~| ERROR cannot infer - //~| ERROR cannot infer // // The fact that `Publisher` is using an implicit lifetime is // what was causing the debruijn accounting to be off, so diff --git a/src/test/run-pass/project-cache-issue-31849.rs b/src/test/run-pass/project-cache-issue-31849.rs new file mode 100644 index 0000000000000..d03424b2b2b7a --- /dev/null +++ b/src/test/run-pass/project-cache-issue-31849.rs @@ -0,0 +1,75 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Regression test for #31849: the problem here was actually a performance +// cliff, but I'm adding the test for reference. + +pub trait Upcast { + fn upcast(self) -> T; +} + +impl Upcast<(T1, T2)> for (S1,S2) + where S1: Upcast, + S2: Upcast, +{ + fn upcast(self) -> (T1, T2) { (self.0.upcast(), self.1.upcast()) } +} + +impl Upcast<()> for () +{ + fn upcast(self) -> () { () } +} + +pub trait ToStatic { + type Static: 'static; + fn to_static(self) -> Self::Static where Self: Sized; +} + +impl ToStatic for (T, U) + where T: ToStatic, + U: ToStatic +{ + type Static = (T::Static, U::Static); + fn to_static(self) -> Self::Static { (self.0.to_static(), self.1.to_static()) } +} + +impl ToStatic for () +{ + type Static = (); + fn to_static(self) -> () { () } +} + + +trait Factory { + type Output; + fn build(&self) -> Self::Output; +} + +impl Factory for (S, T) + where S: Factory, + T: Factory, + S::Output: ToStatic, + ::Static: Upcast, +{ + type Output = (S::Output, T::Output); + fn build(&self) -> Self::Output { (self.0.build().to_static().upcast(), self.1.build()) } +} + +impl Factory for () { + type Output = (); + fn build(&self) -> Self::Output { () } +} + +fn main() { + // More parens, more time. + let it = ((((((((((),()),()),()),()),()),()),()),()),()); + it.build(); +} +