Skip to content

Implement lifetime elision for Foo(...) -> ... type sugar. #19589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 80 additions & 41 deletions src/librustc_typeck/astconv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
};

Expand Down Expand Up @@ -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
};

Expand Down
6 changes: 3 additions & 3 deletions src/test/compile-fail/unboxed-closure-sugar-equiv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) >();
Expand Down
34 changes: 34 additions & 0 deletions src/test/compile-fail/unboxed-closure-sugar-lifetime-elision.rs
Original file line number Diff line number Diff line change
@@ -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
}