From b800ce160899b2c2350c45c25844640c46dd49cf Mon Sep 17 00:00:00 2001
From: Huon Wilson <dbau.pp+github@gmail.com>
Date: Fri, 5 Dec 2014 19:01:07 -0800
Subject: [PATCH] Implement lifetime elision for Foo(...) -> ... type sugar.

This means that `Fn(&A) -> (&B, &C)` is equivalent to `for<'a> Fn(&'a A)
-> (&'a B, &'a C)` similar to the lifetime elision of lower-case `fn` in
types and declarations.

Closes #18992.
---
 src/librustc_typeck/astconv.rs                | 121 ++++++++++++------
 .../unboxed-closure-sugar-equiv.rs            |   6 +-
 .../unboxed-closure-sugar-lifetime-elision.rs |  34 +++++
 3 files changed, 117 insertions(+), 44 deletions(-)
 create mode 100644 src/test/compile-fail/unboxed-closure-sugar-lifetime-elision.rs

diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs
index d95ad9a11c87f..7f1aad8ca77c5 100644
--- a/src/librustc_typeck/astconv.rs
+++ b/src/librustc_typeck/astconv.rs
@@ -386,20 +386,81 @@ fn convert_angle_bracketed_parameters<'tcx, AC, RS>(this: &AC,
     (regions, types)
 }
 
+/// Returns the appropriate lifetime to use for any output lifetimes
+/// (if one exists) and a vector of the (pattern, number of lifetimes)
+/// corresponding to each input type/pattern.
+fn find_implied_output_region(input_tys: &[Ty], input_pats: Vec<String>)
+                              -> (Option<ty::Region>, Vec<(String, uint)>)
+{
+    let mut lifetimes_for_params: Vec<(String, uint)> = Vec::new();
+    let mut possible_implied_output_region = None;
+
+    for (input_type, input_pat) in input_tys.iter().zip(input_pats.into_iter()) {
+        let mut accumulator = Vec::new();
+        ty::accumulate_lifetimes_in_type(&mut accumulator, *input_type);
+
+        if accumulator.len() == 1 {
+            // there's a chance that the unique lifetime of this
+            // iteration will be the appropriate lifetime for output
+            // parameters, so lets store it.
+            possible_implied_output_region = Some(accumulator[0])
+        }
+
+        lifetimes_for_params.push((input_pat, accumulator.len()));
+    }
+
+    let implied_output_region = if lifetimes_for_params.iter().map(|&(_, n)| n).sum() == 1 {
+        assert!(possible_implied_output_region.is_some());
+        possible_implied_output_region
+    } else {
+        None
+    };
+    (implied_output_region, lifetimes_for_params)
+}
+
+fn convert_ty_with_lifetime_elision<'tcx,AC>(this: &AC,
+                                             implied_output_region: Option<ty::Region>,
+                                             param_lifetimes: Vec<(String, uint)>,
+                                             ty: &ast::Ty)
+                                             -> Ty<'tcx>
+    where AC: AstConv<'tcx>
+{
+    match implied_output_region {
+        Some(implied_output_region) => {
+            let rb = SpecificRscope::new(implied_output_region);
+            ast_ty_to_ty(this, &rb, ty)
+        }
+        None => {
+            // All regions must be explicitly specified in the output
+            // if the lifetime elision rules do not apply. This saves
+            // the user from potentially-confusing errors.
+            let rb = UnelidableRscope::new(param_lifetimes);
+            ast_ty_to_ty(this, &rb, ty)
+        }
+    }
+}
+
 fn convert_parenthesized_parameters<'tcx,AC>(this: &AC,
                                              data: &ast::ParenthesizedParameterData)
                                              -> Vec<Ty<'tcx>>
     where AC: AstConv<'tcx>
 {
     let binding_rscope = BindingRscope::new();
-
     let inputs = data.inputs.iter()
                             .map(|a_t| ast_ty_to_ty(this, &binding_rscope, &**a_t))
-                            .collect();
+                            .collect::<Vec<Ty<'tcx>>>();
+
+    let input_params = Vec::from_elem(inputs.len(), String::new());
+    let (implied_output_region,
+         params_lifetimes) = find_implied_output_region(&*inputs, input_params);
+
     let input_ty = ty::mk_tup(this.tcx(), inputs);
 
     let output = match data.output {
-        Some(ref output_ty) => ast_ty_to_ty(this, &binding_rscope, &**output_ty),
+        Some(ref output_ty) => convert_ty_with_lifetime_elision(this,
+                                                                implied_output_region,
+                                                                params_lifetimes,
+                                                                &**output_ty),
         None => ty::mk_nil(this.tcx()),
     };
 
@@ -1059,55 +1120,33 @@ fn ty_of_method_or_bare_fn<'a, 'tcx, AC: AstConv<'tcx>>(
     let self_and_input_tys: Vec<Ty> =
         self_ty.into_iter().chain(input_tys).collect();
 
-    let mut lifetimes_for_params: Vec<(String, Vec<ty::Region>)> = Vec::new();
 
     // Second, if there was exactly one lifetime (either a substitution or a
     // reference) in the arguments, then any anonymous regions in the output
     // have that lifetime.
-    if implied_output_region.is_none() {
-        let mut self_and_input_tys_iter = self_and_input_tys.iter();
-        if self_ty.is_some() {
+    let lifetimes_for_params = if implied_output_region.is_none() {
+        let input_tys = if self_ty.is_some() {
             // Skip the first argument if `self` is present.
-            drop(self_and_input_tys_iter.next())
-        }
-
-        for (input_type, input_pat) in self_and_input_tys_iter.zip(input_pats.into_iter()) {
-            let mut accumulator = Vec::new();
-            ty::accumulate_lifetimes_in_type(&mut accumulator, *input_type);
-            lifetimes_for_params.push((input_pat, accumulator));
-        }
-
-        if lifetimes_for_params.iter().map(|&(_, ref x)| x.len()).sum() == 1 {
-            implied_output_region =
-                Some(lifetimes_for_params.iter()
-                                         .filter_map(|&(_, ref x)|
-                                            if x.len() == 1 { Some(x[0]) } else { None })
-                                         .next().unwrap());
-        }
-    }
+            self_and_input_tys.slice_from(1)
+        } else {
+            self_and_input_tys.slice_from(0)
+        };
 
-    let param_lifetimes: Vec<(String, uint)> = lifetimes_for_params.into_iter()
-                                                                   .map(|(n, v)| (n, v.len()))
-                                                                   .filter(|&(_, l)| l != 0)
-                                                                   .collect();
+        let (ior, lfp) = find_implied_output_region(input_tys, input_pats);
+        implied_output_region = ior;
+        lfp
+    } else {
+        vec![]
+    };
 
     let output_ty = match decl.output {
         ast::Return(ref output) if output.node == ast::TyInfer =>
             ty::FnConverging(this.ty_infer(output.span)),
         ast::Return(ref output) =>
-            ty::FnConverging(match implied_output_region {
-                Some(implied_output_region) => {
-                    let rb = SpecificRscope::new(implied_output_region);
-                    ast_ty_to_ty(this, &rb, &**output)
-                }
-                None => {
-                    // All regions must be explicitly specified in the output
-                    // if the lifetime elision rules do not apply. This saves
-                    // the user from potentially-confusing errors.
-                    let rb = UnelidableRscope::new(param_lifetimes);
-                    ast_ty_to_ty(this, &rb, &**output)
-                }
-            }),
+            ty::FnConverging(convert_ty_with_lifetime_elision(this,
+                                                              implied_output_region,
+                                                              lifetimes_for_params,
+                                                              &**output)),
         ast::NoReturn(_) => ty::FnDiverging
     };
 
diff --git a/src/test/compile-fail/unboxed-closure-sugar-equiv.rs b/src/test/compile-fail/unboxed-closure-sugar-equiv.rs
index 6f875efdef7ea..308b33f9b4db1 100644
--- a/src/test/compile-fail/unboxed-closure-sugar-equiv.rs
+++ b/src/test/compile-fail/unboxed-closure-sugar-equiv.rs
@@ -44,9 +44,9 @@ fn test<'a,'b>() {
     eq::< for<'a,'b> Foo<(&'a int,&'b uint),uint>,
           Foo(&int,&uint) -> uint                             >();
 
-    // FIXME(#18992) Test lifetime elision in `()` form:
-    // eq::< for<'a,'b> Foo<(&'a int,), &'a int>,
-    //      Foo(&int) -> &int                                   >();
+    // lifetime elision
+    eq::< for<'a,'b> Foo<(&'a int,), &'a int>,
+          Foo(&int) -> &int                                   >();
 
     // Errors expected:
     eq::< Foo<(),()>,                   Foo(char)                     >();
diff --git a/src/test/compile-fail/unboxed-closure-sugar-lifetime-elision.rs b/src/test/compile-fail/unboxed-closure-sugar-lifetime-elision.rs
new file mode 100644
index 0000000000000..e08d84944c02a
--- /dev/null
+++ b/src/test/compile-fail/unboxed-closure-sugar-lifetime-elision.rs
@@ -0,0 +1,34 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Test that the unboxed closure sugar can be used with an arbitrary
+// struct type and that it is equivalent to the same syntax using
+// angle brackets. This test covers only simple types and in
+// particular doesn't test bound regions.
+
+#![feature(unboxed_closures)]
+#![allow(dead_code)]
+
+trait Foo<T,U> {
+    fn dummy(&self, t: T, u: U);
+}
+
+trait Eq<Sized? X> for Sized? { }
+impl<Sized? X> Eq<X> for X { }
+fn eq<Sized? A,Sized? B:Eq<A>>() { }
+
+fn main() {
+    eq::< for<'a> Foo<(&'a int,), &'a int>,
+          Foo(&int) -> &int                                   >();
+    eq::< for<'a> Foo<(&'a int,), (&'a int, &'a int)>,
+          Foo(&int) -> (&int, &int)                           >();
+
+    let _: Foo(&int, &uint) -> &uint; //~ ERROR missing lifetime specifier
+}