-
Couldn't load subscription status.
- Fork 13.9k
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; // ERRORSee 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-boundThe 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.output); | |
| 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_argumentslint as warn-by-default - PR ? makes the
late_bound_lifetime_argumentslint deny-by-default - PR ? makes the
late_bound_lifetime_argumentslint a hard error
Metadata
Metadata
Assignees
Labels
Type
Projects
Status