Change:
HRTB doesn't correctly implement lifetime elision for notation. See the test .
Nominating.
notes that this can be fixed backwards compatibly, and agrees. P-high, not 1.0.
Well, sort of -- I mean lifetime elision broke code, but almost only bad code. The problem is that people can use to satisfy the signature and then clients get to assume any lifetime they want.

Discussion:
(regions, types) } <ins> /// 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) } } } </ins> 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)) <del> .collect(); </del> <ins> .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); </ins> let input_ty = ty::mk_tup(this.tcx(), inputs); let output = match data.output { <del> Some(ref output_ty) => ast_ty_to_ty(this, &binding_rscope, &**output_ty), </del> <ins> Some(ref output_ty) => convert_ty_with_lifetime_elision(this, implied_output_region, params_lifetimes, &**output_ty), </ins> None => ty::mk_nil(this.tcx()), };

let self_and_input_tys: Vec<Ty> = self_ty.into_iter().chain(input_tys).collect(); <del> let mut lifetimes_for_params: Vec<(String, Vec<ty::Region>)> = Vec::new(); </del> // 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. <del> if implied_output_region.is_none() { let mut self_and_input_tys_iter = self_and_input_tys.iter(); if self_ty.is_some() { </del> <ins> let lifetimes_for_params = if implied_output_region.is_none() { let input_tys = if self_ty.is_some() { </ins> // Skip the first argument if `self` is present. <del> 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()); } } </del> <ins> self_and_input_tys.slice_from(1) } else { self_and_input_tys.slice_from(0) }; </ins> <del> let param_lifetimes: Vec<(String, uint)> = lifetimes_for_params.into_iter() .map(|(n, v)| (n, v.len())) .filter(|&(_, l)| l != 0) .collect(); </del> <ins> let (ior, lfp) = find_implied_output_region(input_tys, input_pats); implied_output_region = ior; lfp } else { vec![] }; </ins> 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) => <del> 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) } }), </del> <ins> ty::FnConverging(convert_ty_with_lifetime_elision(this, implied_output_region, lifetimes_for_params, &**output)), </ins> ast::NoReturn(_) => ty::FnDiverging };

eq::< for<'a,'b> Foo<(&'a int,&'b uint),uint>, Foo(&int,&uint) -> uint                             >(); <del> // FIXME(#18992) Test lifetime elision in `()` form: // eq::< for<'a,'b> Foo<(&'a int,), &'a int>, //      Foo(&int) -> &int                                   >(); </del> <ins> // lifetime elision eq::< for<'a,'b> Foo<(&'a int,), &'a int>, Foo(&int) -> &int                                   >(); </ins> // Errors expected: eq::< Foo<(),()>,                   Foo(char)                     >();

<ins> // 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 } </ins>