]> git.lizzy.rs Git - rust.git/commitdiff
make NLL handle `IfEq` bounds by using SCC normalization
authorNiko Matsakis <niko@alum.mit.edu>
Fri, 21 Sep 2018 23:26:24 +0000 (19:26 -0400)
committerNiko Matsakis <niko@alum.mit.edu>
Wed, 26 Sep 2018 13:38:26 +0000 (09:38 -0400)
src/librustc_data_structures/indexed_vec.rs
src/librustc_mir/borrow_check/nll/region_infer/mod.rs
src/test/ui/nll/ty-outlives/issue-53789-1.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/issue-53789-2.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-body.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-bound.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-lifetime.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-where-clause-env.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-where-clause-none.rs [new file with mode: 0644]
src/test/ui/nll/ty-outlives/projection-where-clause-trait.rs [new file with mode: 0644]

index 2f11fea46d69a6c6db09c10ea0a5c0d32e4a6546..a59bf9d530c4d0919001c3c81f23a33c9c0bbb40 100644 (file)
@@ -535,6 +535,13 @@ pub fn len(&self) -> usize {
         self.raw.len()
     }
 
+    /// Gives the next index that will be assigned when `push` is
+    /// called.
+    #[inline]
+    pub fn next_index(&self) -> I {
+        I::new(self.len())
+    }
+
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.raw.is_empty()
index a0bc734d5d75af2ae8160a135859768623231e86..2dbb5cd9deb144437a38fb9d9cbd9e236489f217 100644 (file)
@@ -69,6 +69,15 @@ pub struct RegionInferenceContext<'tcx> {
     /// visible from this index.
     scc_universes: IndexVec<ConstraintSccIndex, ty::UniverseIndex>,
 
+    /// Contains a "representative" from each SCC. This will be the
+    /// minimal RegionVid belonging to that universe. It is used as a
+    /// kind of hacky way to manage checking outlives relationships,
+    /// since we can 'canonicalize' each region to the representative
+    /// of its SCC and be sure that -- if they have the same repr --
+    /// they *must* be equal (though not having the same repr does not
+    /// mean they are unequal).
+    scc_representatives: IndexVec<ConstraintSccIndex, ty::RegionVid>,
+
     /// The final inferred values of the region variables; we compute
     /// one value per SCC. To get the value for any given *region*,
     /// you first find which scc it is a part of.
@@ -208,6 +217,8 @@ pub(crate) fn new(
 
         let scc_universes = Self::compute_scc_universes(&constraint_sccs, &definitions);
 
+        let scc_representatives = Self::compute_scc_representatives(&constraint_sccs, &definitions);
+
         let mut result = Self {
             definitions,
             liveness_constraints,
@@ -215,6 +226,7 @@ pub(crate) fn new(
             constraint_graph,
             constraint_sccs,
             scc_universes,
+            scc_representatives,
             scc_values,
             type_tests,
             universal_regions,
@@ -251,6 +263,27 @@ fn compute_scc_universes(
         scc_universes
     }
 
+    /// For each SCC, we compute a unique `RegionVid` (in fact, the
+    /// minimal one that belongs to the SCC). See
+    /// `scc_representatives` field of `RegionInferenceContext` for
+    /// more details.
+    fn compute_scc_representatives(
+        constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
+        definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
+    ) -> IndexVec<ConstraintSccIndex, ty::RegionVid> {
+        let num_sccs = constraints_scc.num_sccs();
+        let next_region_vid = definitions.next_index();
+        let mut scc_representatives = IndexVec::from_elem_n(next_region_vid, num_sccs);
+
+        for region_vid in definitions.indices() {
+            let scc = constraints_scc.scc(region_vid);
+            let prev_min = scc_representatives[scc];
+            scc_representatives[scc] = region_vid.min(prev_min);
+        }
+
+        scc_representatives
+    }
+
     /// Initializes the region variables for each universally
     /// quantified region (lifetime parameter). The first N variables
     /// always correspond to the regions appearing in the function
@@ -545,7 +578,14 @@ fn check_type_tests<'gcx>(
         for type_test in &self.type_tests {
             debug!("check_type_test: {:?}", type_test);
 
-            if self.eval_verify_bound(mir, type_test.lower_bound, &type_test.verify_bound) {
+            let generic_ty = type_test.generic_kind.to_ty(tcx);
+            if self.eval_verify_bound(
+                tcx,
+                mir,
+                generic_ty,
+                type_test.lower_bound,
+                &type_test.verify_bound,
+            ) {
                 continue;
             }
 
@@ -679,7 +719,7 @@ fn try_promote_type_test<'gcx>(
             // where `ur` is a local bound -- we are sometimes in a
             // position to prove things that our caller cannot.  See
             // #53570 for an example.
-            if self.eval_verify_bound(mir, ur, &type_test.verify_bound) {
+            if self.eval_verify_bound(tcx, mir, generic_ty, ur, &type_test.verify_bound) {
                 continue;
             }
 
@@ -853,7 +893,9 @@ fn universal_upper_bound(&self, r: RegionVid) -> RegionVid {
     /// `point`, and returns true or false.
     fn eval_verify_bound(
         &self,
+        tcx: TyCtxt<'_, '_, 'tcx>,
         mir: &Mir<'tcx>,
+        generic_ty: Ty<'tcx>,
         lower_bound: RegionVid,
         verify_bound: &VerifyBound<'tcx>,
     ) -> bool {
@@ -863,23 +905,85 @@ fn eval_verify_bound(
         );
 
         match verify_bound {
-            VerifyBound::IfEq(..) => false, // FIXME
+            VerifyBound::IfEq(test_ty, verify_bound1) => {
+                self.eval_if_eq(tcx, mir, generic_ty, lower_bound, test_ty, verify_bound1)
+            }
 
             VerifyBound::OutlivedBy(r) => {
                 let r_vid = self.to_region_vid(r);
                 self.eval_outlives(mir, r_vid, lower_bound)
             }
 
-            VerifyBound::AnyBound(verify_bounds) => verify_bounds
-                .iter()
-                .any(|verify_bound| self.eval_verify_bound(mir, lower_bound, verify_bound)),
+            VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| {
+                self.eval_verify_bound(tcx, mir, generic_ty, lower_bound, verify_bound)
+            }),
 
-            VerifyBound::AllBounds(verify_bounds) => verify_bounds
-                .iter()
-                .all(|verify_bound| self.eval_verify_bound(mir, lower_bound, verify_bound)),
+            VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| {
+                self.eval_verify_bound(tcx, mir, generic_ty, lower_bound, verify_bound)
+            }),
         }
     }
 
+    fn eval_if_eq(
+        &self,
+        tcx: TyCtxt<'_, '_, 'tcx>,
+        mir: &Mir<'tcx>,
+        generic_ty: Ty<'tcx>,
+        lower_bound: RegionVid,
+        test_ty: Ty<'tcx>,
+        verify_bound: &VerifyBound<'tcx>,
+    ) -> bool {
+        let generic_ty_normalized = self.normalize_to_scc_representatives(tcx, generic_ty);
+        let test_ty_normalized = self.normalize_to_scc_representatives(tcx, test_ty);
+        if generic_ty_normalized == test_ty_normalized {
+            self.eval_verify_bound(tcx, mir, generic_ty, lower_bound, verify_bound)
+        } else {
+            false
+        }
+    }
+
+    /// This is a conservative normalization procedure. It takes every
+    /// free region in `value` and replaces it with the
+    /// "representative" of its SCC (see `scc_representatives` field).
+    /// We are guaranteed that if two values normalize to the same
+    /// thing, then they are equal; this is a conservative check in
+    /// that they could still be equal even if they normalize to
+    /// different results. (For example, there might be two regions
+    /// with the same value that are not in the same SCC).
+    ///
+    /// NB. This is not an ideal approach and I would like to revisit
+    /// it. However, it works pretty well in practice. In particular,
+    /// this is needed to deal with projection outlives bounds like
+    ///
+    ///     <T as Foo<'0>>::Item: '1
+    ///
+    /// In particular, this routine winds up being important when
+    /// there are bounds like `where <T as Foo<'a>>::Item: 'b` in the
+    /// environment.  In this case, if we can show that `'0 == 'a`,
+    /// and that `'b: '1`, then we know that the clause is
+    /// satisfied. In such cases, particularly due to limitations of
+    /// the trait solver =), we usually wind up with a where-clause like
+    /// `T: Foo<'a>` in scope, which thus forces `'0 == 'a` to be added as
+    /// a constraint, and thus ensures that they are in the same SCC.
+    ///
+    /// So why can't we do a more correct routine? Well, we could
+    /// *almost* use the `relate_tys` code, but the way it is
+    /// currently setup it creates inference variables to deal with
+    /// higher-ranked things and so forth, and right now the inference
+    /// context is not permitted to make more inference variables. So
+    /// we use this kind of hacky solution.
+    fn normalize_to_scc_representatives<T>(&self, tcx: TyCtxt<'_, '_, 'tcx>, value: T) -> T
+    where
+        T: TypeFoldable<'tcx>,
+    {
+        tcx.fold_regions(&value, &mut false, |r, _db| {
+            let vid = self.to_region_vid(r);
+            let scc = self.constraint_sccs.scc(vid);
+            let repr = self.scc_representatives[scc];
+            tcx.mk_region(ty::ReVar(repr))
+        })
+    }
+
     // Evaluate whether `sup_region: sub_region @ point`.
     fn eval_outlives(
         &self,
diff --git a/src/test/ui/nll/ty-outlives/issue-53789-1.rs b/src/test/ui/nll/ty-outlives/issue-53789-1.rs
new file mode 100644 (file)
index 0000000..593cdfd
--- /dev/null
@@ -0,0 +1,91 @@
+// Regression test for #53789.
+//
+// compile-pass
+
+#![feature(nll)]
+#![allow(unused_variables)]
+
+use std::collections::BTreeMap;
+
+trait ValueTree {
+    type Value;
+}
+
+trait Strategy {
+    type Value: ValueTree;
+}
+
+type StrategyFor<A> = StrategyType<'static, A>;
+type StrategyType<'a, A> = <A as Arbitrary<'a>>::Strategy;
+
+impl<K: ValueTree, V: ValueTree> Strategy for (K, V) {
+    type Value = TupleValueTree<(K, V)>;
+}
+
+impl<K: ValueTree, V: ValueTree> ValueTree for TupleValueTree<(K, V)> {
+    type Value = BTreeMapValueTree<K, V>;
+}
+
+struct TupleValueTree<T> {
+    tree: T,
+}
+
+struct BTreeMapStrategy<K, V>(std::marker::PhantomData<(K, V)>)
+where
+    K: Strategy,
+    V: Strategy;
+
+struct BTreeMapValueTree<K, V>(std::marker::PhantomData<(K, V)>)
+where
+    K: ValueTree,
+    V: ValueTree;
+
+impl<K, V> Strategy for BTreeMapStrategy<K, V>
+where
+    K: Strategy,
+    V: Strategy,
+{
+    type Value = BTreeMapValueTree<K::Value, V::Value>;
+}
+
+impl<K, V> ValueTree for BTreeMapValueTree<K, V>
+where
+    K: ValueTree,
+    V: ValueTree,
+{
+    type Value = BTreeMap<K::Value, V::Value>;
+}
+
+trait Arbitrary<'a>: Sized {
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy;
+    type Parameters;
+    type Strategy: Strategy<Value = Self::ValueTree>;
+    type ValueTree: ValueTree<Value = Self>;
+}
+
+impl<'a, A, B> Arbitrary<'a> for BTreeMap<A, B>
+where
+    A: Arbitrary<'static>,
+    B: Arbitrary<'static>,
+    StrategyFor<A>: 'static,
+    StrategyFor<B>: 'static,
+{
+    type ValueTree = <Self::Strategy as Strategy>::Value;
+    type Parameters = (A::Parameters, B::Parameters);
+    type Strategy = BTreeMapStrategy<A::Strategy, B::Strategy>;
+    fn arbitrary_with(args: Self::Parameters) -> BTreeMapStrategy<A::Strategy, B::Strategy> {
+        let (a, b) = args;
+        btree_map(any_with::<A>(a), any_with::<B>(b))
+    }
+}
+
+fn btree_map<K: Strategy + 'static, V: Strategy>(key: K, value: V) -> BTreeMapStrategy<K, V> {
+    unimplemented!()
+}
+
+fn any_with<'a, A: Arbitrary<'a>>(args: A::Parameters) -> StrategyType<'a, A> {
+    unimplemented!()
+}
+
+fn main() { }
+
diff --git a/src/test/ui/nll/ty-outlives/issue-53789-2.rs b/src/test/ui/nll/ty-outlives/issue-53789-2.rs
new file mode 100644 (file)
index 0000000..62e2833
--- /dev/null
@@ -0,0 +1,251 @@
+// Regression test for #53789.
+//
+// compile-pass
+
+#![feature(nll)]
+#![allow(unused_variables)]
+
+use std::collections::BTreeMap;
+use std::ops::Range;
+use std::cmp::Ord;
+
+macro_rules! valuetree {
+    () => {
+        type ValueTree =
+            <Self::Strategy as $crate::Strategy>::Value;
+    };
+}
+
+macro_rules! product_unpack {
+    ($factor: pat) => {
+        ($factor,)
+    };
+    ($($factor: pat),*) => {
+        ( $( $factor ),* )
+    };
+    ($($factor: pat),*,) => {
+        ( $( $factor ),* )
+    };
+}
+
+macro_rules! product_type {
+    ($factor: ty) => {
+        ($factor,)
+    };
+    ($($factor: ty),*) => {
+        ( $( $factor, )* )
+    };
+    ($($factor: ty),*,) => {
+        ( $( $factor, )* )
+    };
+}
+
+macro_rules! default {
+    ($type: ty, $val: expr) => {
+        impl Default for $type {
+            fn default() -> Self { $val.into() }
+        }
+    };
+}
+
+// Pervasive internal sugar
+macro_rules! mapfn {
+    ($(#[$meta:meta])* [$($vis:tt)*]
+     fn $name:ident[$($gen:tt)*]($parm:ident: $input:ty) -> $output:ty {
+         $($body:tt)*
+     }) => {
+        $(#[$meta])*
+            #[derive(Clone, Copy)]
+        $($vis)* struct $name;
+        impl $($gen)* statics::MapFn<$input> for $name {
+            type Output = $output;
+        }
+    }
+}
+
+macro_rules! opaque_strategy_wrapper {
+    ($(#[$smeta:meta])* pub struct $stratname:ident
+     [$($sgen:tt)*][$($swhere:tt)*]
+     ($innerstrat:ty) -> $stratvtty:ty;
+
+     $(#[$vmeta:meta])* pub struct $vtname:ident
+     [$($vgen:tt)*][$($vwhere:tt)*]
+     ($innervt:ty) -> $actualty:ty;
+    ) => {
+        $(#[$smeta])* struct $stratname $($sgen)* (std::marker::PhantomData<(K, V)>)
+            $($swhere)*;
+
+        $(#[$vmeta])* struct $vtname $($vgen)* ($innervt) $($vwhere)*;
+
+        impl $($sgen)* Strategy for $stratname $($sgen)* $($swhere)* {
+            type Value = $stratvtty;
+        }
+
+        impl $($vgen)* ValueTree for $vtname $($vgen)* $($vwhere)* {
+            type Value = $actualty;
+        }
+    }
+}
+
+trait ValueTree {
+    type Value;
+}
+
+trait Strategy {
+    type Value : ValueTree;
+}
+
+#[derive(Clone)]
+struct VecStrategy<T : Strategy> {
+    element: T,
+    size: Range<usize>,
+}
+
+fn vec<T : Strategy>(element: T, size: Range<usize>)
+                     -> VecStrategy<T> {
+    VecStrategy {
+        element: element,
+        size: size,
+    }
+}
+
+type ValueFor<S> = <<S as Strategy>::Value as ValueTree>::Value;
+
+trait Arbitrary<'a>: Sized {
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy;
+
+    type Parameters: Default;
+    type Strategy: Strategy<Value = Self::ValueTree>;
+    type ValueTree: ValueTree<Value = Self>;
+}
+
+type StrategyFor<A> = StrategyType<'static, A>;
+type StrategyType<'a, A> = <A as Arbitrary<'a>>::Strategy;
+
+//#[derive(Clone, PartialEq, Eq, Hash, Debug, From, Into)]
+struct SizeBounds(Range<usize>);
+default!(SizeBounds, 0..100);
+
+
+impl From<Range<usize>> for SizeBounds {
+    fn from(high: Range<usize>) -> Self {
+        unimplemented!()
+    }
+}
+
+impl From<SizeBounds> for Range<usize> {
+    fn from(high: SizeBounds) -> Self {
+        unimplemented!()
+    }
+}
+
+
+fn any_with<'a, A: Arbitrary<'a>>(args: A::Parameters)
+                                  -> StrategyType<'a, A> {
+    unimplemented!()
+}
+
+impl<K: ValueTree, V: ValueTree> Strategy for (K, V) where
+    <K as ValueTree>::Value: Ord {
+    type Value = TupleValueTree<(K, V)>;
+}
+
+impl<K: ValueTree, V: ValueTree> ValueTree for TupleValueTree<(K, V)> where
+    <K as ValueTree>::Value: Ord {
+    type Value = BTreeMapValueTree<K, V>;
+}
+
+#[derive(Clone)]
+struct VecValueTree<T : ValueTree> {
+    elements: Vec<T>,
+}
+
+#[derive(Clone, Copy)]
+struct TupleValueTree<T> {
+    tree: T,
+}
+
+opaque_strategy_wrapper! {
+    #[derive(Clone)]
+    pub struct BTreeMapStrategy[<K, V>]
+        [where K : Strategy, V : Strategy, ValueFor<K> : Ord](
+            statics::Filter<statics::Map<VecStrategy<(K,V)>,
+            VecToBTreeMap>, MinSize>)
+        -> BTreeMapValueTree<K::Value, V::Value>;
+
+    #[derive(Clone)]
+    pub struct BTreeMapValueTree[<K, V>]
+        [where K : ValueTree, V : ValueTree, K::Value : Ord](
+            statics::Filter<statics::Map<VecValueTree<TupleValueTree<(K, V)>>,
+            VecToBTreeMap>, MinSize>)
+        -> BTreeMap<K::Value, V::Value>;
+}
+
+type RangedParams2<A, B> = product_type![SizeBounds, A, B];
+
+impl<'a, A, B> Arbitrary<'a> for BTreeMap<A, B>
+where
+    A: Arbitrary<'static> + Ord,
+    B: Arbitrary<'static>,
+StrategyFor<A>: 'static,
+StrategyFor<B>: 'static,
+{
+    valuetree!();
+    type Parameters = RangedParams2<A::Parameters, B::Parameters>;
+    type Strategy = BTreeMapStrategy<A::Strategy, B::Strategy>;
+    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+        let product_unpack![range, a, b] = args;
+        btree_map(any_with::<A>(a), any_with::<B>(b), range.into())
+    }
+}
+
+#[derive(Clone, Copy)]
+struct MinSize(usize);
+
+mapfn! {
+    [] fn VecToBTreeMap[<K : Ord, V>]
+        (vec: Vec<(K, V)>) -> BTreeMap<K, V>
+    {
+        vec.into_iter().collect()
+    }
+}
+
+fn btree_map<K : Strategy + 'static, V : Strategy + 'static>
+    (key: K, value: V, size: Range<usize>)
+     -> BTreeMapStrategy<K, V>
+where ValueFor<K> : Ord {
+    unimplemented!()
+}
+
+mod statics {
+    pub(super) trait MapFn<T> {
+        type Output;
+    }
+
+    #[derive(Clone)]
+    pub struct Filter<S, F> {
+        source: S,
+        fun: F,
+    }
+
+    impl<S, F> Filter<S, F> {
+        pub fn new(source: S, whence: String, filter: F) -> Self {
+            unimplemented!()
+        }
+    }
+
+    #[derive(Clone)]
+    pub struct Map<S, F> {
+        source: S,
+        fun: F,
+    }
+
+    impl<S, F> Map<S, F> {
+        pub fn new(source: S, fun: F) -> Self {
+            unimplemented!()
+        }
+    }
+}
+
+fn main() { }
+
diff --git a/src/test/ui/nll/ty-outlives/projection-body.rs b/src/test/ui/nll/ty-outlives/projection-body.rs
new file mode 100644 (file)
index 0000000..680e26d
--- /dev/null
@@ -0,0 +1,25 @@
+// Test that when we infer the lifetime to a subset of the fn body, it
+// works out.
+
+trait MyTrait<'a> {
+    type Output;
+}
+
+fn foo1<T>()
+where
+    for<'x> T: MyTrait<'x>,
+{
+    // Here the region `'c` in `<T as MyTrait<'c>>::Output` will be
+    // inferred to a subset of the fn body.
+    let x = bar::<T::Output>();
+    drop(x);
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-bound.rs b/src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-bound.rs
new file mode 100644 (file)
index 0000000..9c2cbfd
--- /dev/null
@@ -0,0 +1,36 @@
+#![feature(nll)]
+
+// Test that we are able to establish that `<T as
+// MyTrait<'a>>::Output` outlives `'b` here. We need to prove however
+// that `<T as MyTrait<'a>>::Output` outlives `'a`, so we also have to
+// prove that `'b: 'a`.
+
+trait MyTrait<'a> {
+    type Output;
+}
+
+fn foo1<'a, 'b, T>() -> &'a ()
+where
+    T: MyTrait<'a>,
+    <T as MyTrait<'a>>::Output: 'b,
+{
+    bar::<T::Output>() //~ ERROR may not live long enough
+}
+
+fn foo2<'a, 'b, T>() -> &'a ()
+where
+    T: MyTrait<'a>,
+    <T as MyTrait<'a>>::Output: 'b,
+    'b: 'a,
+{
+    bar::<T::Output>() // OK
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-lifetime.rs b/src/test/ui/nll/ty-outlives/projection-where-clause-env-wrong-lifetime.rs
new file mode 100644 (file)
index 0000000..07cc37a
--- /dev/null
@@ -0,0 +1,24 @@
+// Test that if we need to prove that `<T as MyTrait<'a>>::Output:
+// 'a`, but we only know that `<T as MyTrait<'b>>::Output: 'a`, that
+// doesn't suffice.
+
+trait MyTrait<'a> {
+    type Output;
+}
+
+fn foo1<'a, 'b, T>() -> &'a ()
+where
+    for<'x> T: MyTrait<'x>,
+    <T as MyTrait<'b>>::Output: 'a,
+{
+    bar::<<T as MyTrait<'a>>::Output>() //~ ERROR the associated type `<T as MyTrait<'a>>::Output` may not live long enough
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/ty-outlives/projection-where-clause-env.rs b/src/test/ui/nll/ty-outlives/projection-where-clause-env.rs
new file mode 100644 (file)
index 0000000..c6935ba
--- /dev/null
@@ -0,0 +1,30 @@
+#![feature(nll)]
+
+// Test that when we have a `<T as MyTrait<'a>>::Output: 'a`
+// relationship in the environment we take advantage of it.  In this
+// case, that means we **don't** have to prove that `T: 'a`.
+//
+// Regression test for #53121.
+//
+// compile-pass
+
+trait MyTrait<'a> {
+    type Output;
+}
+
+fn foo<'a, T>() -> &'a ()
+where
+    T: MyTrait<'a>,
+    <T as MyTrait<'a>>::Output: 'a,
+{
+    bar::<T::Output>()
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/ty-outlives/projection-where-clause-none.rs b/src/test/ui/nll/ty-outlives/projection-where-clause-none.rs
new file mode 100644 (file)
index 0000000..f0f72f5
--- /dev/null
@@ -0,0 +1,26 @@
+#![feature(nll)]
+
+// Test that we are NOT able to establish that `<T as
+// MyTrait<'a>>::Output: 'a` outlives `'a` here -- we have only one
+// recourse, which is to prove that `T: 'a` and `'a: 'a`, but we don't
+// know that `T: 'a`.
+
+trait MyTrait<'a> {
+    type Output;
+}
+
+fn foo<'a, T>() -> &'a ()
+where
+    T: MyTrait<'a>,
+{
+    bar::<T::Output>() //~ ERROR the parameter type `T` may not live long enough
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/ty-outlives/projection-where-clause-trait.rs b/src/test/ui/nll/ty-outlives/projection-where-clause-trait.rs
new file mode 100644 (file)
index 0000000..7c7d64a
--- /dev/null
@@ -0,0 +1,27 @@
+#![feature(nll)]
+
+// Test that we are able to establish that `<T as
+// MyTrait<'a>>::Output: 'a` outlives `'a` (because the trait says
+// so).
+//
+// compile-pass
+
+trait MyTrait<'a> {
+    type Output: 'a;
+}
+
+fn foo<'a, T>() -> &'a ()
+where
+    T: MyTrait<'a>,
+{
+    bar::<T::Output>()
+}
+
+fn bar<'a, T>() -> &'a ()
+where
+    T: 'a,
+{
+    &()
+}
+
+fn main() {}