From b800ce160899b2c2350c45c25844640c46dd49cf Mon Sep 17 00:00:00 2001 From: Huon Wilson 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) + -> (Option, 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, + 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> 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::>>(); + + 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 = self_ty.into_iter().chain(input_tys).collect(); - let mut lifetimes_for_params: Vec<(String, Vec)> = 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 or the MIT license +// , 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 { + fn dummy(&self, t: T, u: U); +} + +trait Eq for Sized? { } +impl Eq for X { } +fn eq>() { } + +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 +}