-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
What is this lint about
In functions not all lifetime parameters are created equal.
For example, if you have a function like
fn f<'a, 'b>(arg: &'a u8) -> &'b u8 { .... }
both 'a
and 'b
are listed in the same parameter list, but when stripped from the surface syntax the function looks more like
for<'a> fn f<'b>(arg: &'a u8) -> &'b u8 { .... }
where 'b
is a "true" ("early-bound") parameter of the function, and 'a
is an "existential" ("late-bound") parameter. This means the function is not parameterized by 'a
.
To give some more intuition, let's write a type for function pointer to f
:
// PtrF is not parameterized by 'a,
type PtrF<'b> = for<'a> fn(&'a u8) -> &'b u8;
// but it has to be parameterized by 'b
type PtrF = for<'a, 'b> fn(&'a u8) -> &'b u8; // ERROR
See more about this distinction in http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes
When lifetime arguments are provided to a function explicitly, e.g.
f::<'my_a, 'my_b>
the first argument doesn't make much sense because the function is not parameterized by 'a
.
Providing arguments for "late-bound" lifetime parameters in general doesn't make sense, while arguments for "early-bound" lifetime parameters can be provided.
It's not clear how to provide arguments for early-bound lifetime parameters if they are intermixed with late-bound parameters in the same list. For now providing any explicit arguments is prohibited if late-bound parameters are present, so in the future we can adopt any solution without hitting backward compatibility issues.
Note that late-bound lifetime parameters can be introduced implicitly through lifetime elision:
fn f(&u8) {}
// Desugars into
fn f<'a>(&'a u8) {} // 'a is late-bound
The precise rules discerning between early- and late-bound lifetimes can be found here:
rust/src/librustc/middle/resolve_lifetime.rs
Lines 1541 to 1700 in 91aff57
/// Detects late-bound lifetimes and inserts them into | |
/// `map.late_bound`. | |
/// | |
/// A region declared on a fn is **late-bound** if: | |
/// - it is constrained by an argument type; | |
/// - it does not appear in a where-clause. | |
/// | |
/// "Constrained" basically means that it appears in any type but | |
/// not amongst the inputs to a projection. In other words, `<&'a | |
/// T as Trait<''b>>::Foo` does not constrain `'a` or `'b`. | |
fn insert_late_bound_lifetimes(map: &mut NamedRegionMap, | |
fn_def_id: DefId, | |
decl: &hir::FnDecl, | |
generics: &hir::Generics) { | |
debug!("insert_late_bound_lifetimes(decl={:?}, generics={:?})", decl, generics); | |
let mut constrained_by_input = ConstrainedCollector { regions: FxHashSet() }; | |
for arg_ty in &decl.inputs { | |
constrained_by_input.visit_ty(arg_ty); | |
} | |
let mut appears_in_output = AllCollector { | |
regions: FxHashSet(), | |
impl_trait: false | |
}; | |
intravisit::walk_fn_ret_ty(&mut appears_in_output, &decl |
|
debug!("insert_late_bound_lifetimes: constrained_by_input={:?}", | |
constrained_by_input.regions); | |
// Walk the lifetimes that appear in where clauses. | |
// | |
// Subtle point: because we disallow nested bindings, we can just | |
// ignore binders here and scrape up all names we see. | |
let mut appears_in_where_clause = AllCollector { | |
regions: FxHashSet(), | |
impl_trait: false | |
}; | |
for ty_param in generics.ty_params.iter() { | |
walk_list!(&mut appears_in_where_clause, | |
visit_ty_param_bound, | |
&ty_param.bounds); | |
} | |
walk_list!(&mut appears_in_where_clause, | |
visit_where_predicate, | |
&generics.where_clause.predicates); | |
for lifetime_def in &generics.lifetimes { | |
if !lifetime_def.bounds.is_empty() { | |
// `'a: 'b` means both `'a` and `'b` are referenced | |
appears_in_where_clause.visit_lifetime_def(lifetime_def); | |
} | |
} | |
debug!("insert_late_bound_lifetimes: appears_in_where_clause={:?}", | |
appears_in_where_clause.regions); | |
// Late bound regions are those that: | |
// - appear in the inputs | |
// - do not appear in the where-clauses | |
// - are not implicitly captured by `impl Trait` | |
for lifetime in &generics.lifetimes { | |
let name = lifetime.lifetime.name; | |
// appears in the where clauses? early-bound. | |
if appears_in_where_clause.regions.contains(&name) { continue; } | |
// any `impl Trait` in the return type? early-bound. | |
if appears_in_output.impl_trait { continue; } | |
// does not appear in the inputs, but appears in the return | |
// type? eventually this will be early-bound, but for now we | |
// just mark it so we can issue warnings. | |
let constrained_by_input = constrained_by_input.regions.contains(&name); | |
let appears_in_output = appears_in_output.regions.contains(&name); | |
if !constrained_by_input && appears_in_output { | |
debug!("inserting issue_32330 entry for {:?}, {:?} on {:?}", | |
lifetime.lifetime.id, | |
name, | |
fn_def_id); | |
map.issue_32330.insert( | |
lifetime.lifetime.id, | |
ty::Issue32330 { | |
fn_def_id, | |
region_name: name, | |
}); | |
continue; | |
} | |
debug!("insert_late_bound_lifetimes: \ | |
lifetime {:?} with id {:?} is late-bound", | |
lifetime.lifetime.name, lifetime.lifetime.id); | |
let inserted = map.late_bound.insert(lifetime.lifetime.id); | |
assert!(inserted, "visited lifetime {:?} twice", lifetime.lifetime.id); | |
} | |
return; | |
struct ConstrainedCollector { | |
regions: FxHashSet<ast::Name>, | |
} | |
impl<'v> Visitor<'v> for ConstrainedCollector { | |
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> { | |
NestedVisitorMap::None | |
} | |
fn visit_ty(&mut self, ty: &'v hir::Ty) { | |
match ty.node { | |
hir::TyPath(hir::QPath::Resolved(Some(_), _)) | | |
hir::TyPath(hir::QPath::TypeRelative(..)) => { | |
// ignore lifetimes appearing in associated type | |
// projections, as they are not *constrained* | |
// (defined above) | |
} | |
hir::TyPath(hir::QPath::Resolved(None, ref path)) => { | |
// consider only the lifetimes on the final | |
// segment; I am not sure it's even currently | |
// valid to have them elsewhere, but even if it | |
// is, those would be potentially inputs to | |
// projections | |
if let Some(last_segment) = path.segments.last() { | |
self.visit_path_segment(path.span, last_segment); | |
} | |
} | |
_ => { | |
intravisit::walk_ty(self, ty); | |
} | |
} | |
} | |
fn visit_lifetime(&mut self, lifetime_ref: &'v hir::Lifetime) { | |
self.regions.insert(lifetime_ref.name); | |
} | |
} | |
struct AllCollector { | |
regions: FxHashSet<ast::Name>, | |
impl_trait: bool | |
} | |
impl<'v> Visitor<'v> for AllCollector { | |
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> { | |
NestedVisitorMap::None | |
} | |
fn visit_lifetime(&mut self, lifetime_ref: &'v hir::Lifetime) { | |
self.regions.insert(lifetime_ref.name); | |
} | |
fn visit_ty(&mut self, ty: &hir::Ty) { | |
if let hir::TyImplTrait(_) = ty.node { | |
self.impl_trait = true; | |
} | |
intravisit::walk_ty(self, ty); | |
} | |
} | |
} |
How to fix this warning/error
Just removing the lifetime arguments pointed to by the lint should be enough in most cases.
Current status
- Support generic lifetime arguments in method calls #42492 introduces the
late_bound_lifetime_arguments
lint as warn-by-default - PR ? makes the
late_bound_lifetime_arguments
lint deny-by-default - PR ? makes the
late_bound_lifetime_arguments
lint a hard error
Metadata
Metadata
Assignees
Labels
Type
Projects
Status