Skip to content
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

needless-lifetime - nested elision sites #5978

Merged
merged 7 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
161 changes: 71 additions & 90 deletions clippy_lints/src/lifetimes.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use crate::utils::paths;
use crate::utils::{get_trait_def_id, in_macro, span_lint, trait_ref_of_method};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{
walk_fn_decl, walk_generic_param, walk_generics, walk_param_bound, walk_ty, NestedVisitorMap, Visitor,
walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty,
NestedVisitorMap, Visitor,
};
use rustc_hir::FnRetTy::Return;
use rustc_hir::{
BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item,
ItemKind, Lifetime, LifetimeName, ParamName, QPath, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty,
TyKind, WhereClause, WherePredicate,
BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem,
ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, TraitFn,
TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, Symbol};

use crate::utils::{in_macro, last_path_segment, span_lint, trait_ref_of_method};
use std::iter::FromIterator;

declare_clippy_lint! {
/// **What it does:** Checks for lifetime annotations which can be removed by
Expand Down Expand Up @@ -108,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
}

/// The lifetime of a &-reference.
#[derive(PartialEq, Eq, Hash, Debug)]
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
enum RefLt {
Unnamed,
Static,
Expand All @@ -127,7 +128,6 @@ fn check_fn_inner<'tcx>(
return;
}

let mut bounds_lts = Vec::new();
let types = generics
.params
.iter()
Expand Down Expand Up @@ -156,13 +156,12 @@ fn check_fn_inner<'tcx>(
if bound.name != LifetimeName::Static && !bound.is_elided() {
return;
}
bounds_lts.push(bound);
}
}
}
}
}
if could_use_elision(cx, decl, body, &generics.params, bounds_lts) {
if could_use_elision(cx, decl, body, &generics.params) {
span_lint(
cx,
NEEDLESS_LIFETIMES,
Expand All @@ -181,7 +180,6 @@ fn could_use_elision<'tcx>(
func: &'tcx FnDecl<'_>,
body: Option<BodyId>,
named_generics: &'tcx [GenericParam<'_>],
bounds_lts: Vec<&'tcx Lifetime>,
) -> bool {
// There are two scenarios where elision works:
// * no output references, all input references have different LT
Expand All @@ -204,15 +202,31 @@ fn could_use_elision<'tcx>(
if let Return(ref ty) = func.output {
output_visitor.visit_ty(ty);
}
for lt in named_generics {
input_visitor.visit_generic_param(lt)
}

if input_visitor.abort() || output_visitor.abort() {
return false;
}

let input_lts = match input_visitor.into_vec() {
Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
None => return false,
};
let output_lts = match output_visitor.into_vec() {
Some(val) => val,
None => return false,
};
if allowed_lts
.intersection(&FxHashSet::from_iter(
input_visitor
.nested_elision_site_lts
.iter()
.chain(output_visitor.nested_elision_site_lts.iter())
.cloned()
.filter(|v| matches!(v, RefLt::Named(_))),
))
.next()
.is_some()
{
return false;
}

let input_lts = input_visitor.lts;
let output_lts = output_visitor.lts;

if let Some(body_id) = body {
let mut checker = BodyLifetimeChecker {
Expand Down Expand Up @@ -277,35 +291,29 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> {
allowed_lts
}

fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
for lt in bounds_lts {
if lt.name != LifetimeName::Static {
vec.push(RefLt::Named(lt.name.ident().name));
}
}

vec
}

/// Number of unique lifetimes in the given vector.
#[must_use]
fn unique_lifetimes(lts: &[RefLt]) -> usize {
lts.iter().collect::<FxHashSet<_>>().len()
}

const CLOSURE_TRAIT_BOUNDS: [&[&str]; 3] = [&paths::FN, &paths::FN_MUT, &paths::FN_ONCE];

/// A visitor usable for `rustc_front::visit::walk_ty()`.
struct RefVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
lts: Vec<RefLt>,
abort: bool,
nested_elision_site_lts: Vec<RefLt>,
unelided_trait_object_lifetime: bool,
}

impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
cx,
lts: Vec::new(),
abort: false,
nested_elision_site_lts: Vec::new(),
unelided_trait_object_lifetime: false,
}
}

Expand All @@ -325,40 +333,16 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
}
}

fn into_vec(self) -> Option<Vec<RefLt>> {
if self.abort {
None
} else {
Some(self.lts)
}
fn all_lts(&self) -> Vec<RefLt> {
self.lts
.iter()
.chain(self.nested_elision_site_lts.iter())
.cloned()
.collect::<Vec<_>>()
}

fn collect_anonymous_lifetimes(&mut self, qpath: &QPath<'_>, ty: &Ty<'_>) {
if let Some(ref last_path_segment) = last_path_segment(qpath).args {
if !last_path_segment.parenthesized
&& !last_path_segment
.args
.iter()
.any(|arg| matches!(arg, GenericArg::Lifetime(_)))
{
let hir_id = ty.hir_id;
match self.cx.qpath_res(qpath, hir_id) {
Res::Def(DefKind::TyAlias | DefKind::Struct, def_id) => {
let generics = self.cx.tcx.generics_of(def_id);
for _ in generics.params.as_slice() {
self.record(&None);
}
},
Res::Def(DefKind::Trait, def_id) => {
let trait_def = self.cx.tcx.trait_def(def_id);
for _ in &self.cx.tcx.generics_of(trait_def.def_id).params {
self.record(&None);
}
},
_ => (),
}
}
}
fn abort(&self) -> bool {
self.unelided_trait_object_lifetime
}
}

Expand All @@ -370,30 +354,36 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
self.record(&Some(*lifetime));
}

fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) {
let trait_ref = &poly_tref.trait_ref;
if CLOSURE_TRAIT_BOUNDS
.iter()
.any(|trait_path| trait_ref.trait_def_id() == get_trait_def_id(self.cx, trait_path))
{
let mut sub_visitor = RefVisitor::new(self.cx);
sub_visitor.visit_trait_ref(trait_ref);
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
} else {
walk_poly_trait_ref(self, poly_tref, tbm);
}
}

fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
match ty.kind {
TyKind::Rptr(ref lt, _) if lt.is_elided() => {
self.record(&None);
},
TyKind::Path(ref path) => {
self.collect_anonymous_lifetimes(path, ty);
},
TyKind::OpaqueDef(item, _) => {
let map = self.cx.tcx.hir();
if let ItemKind::OpaqueTy(ref exist_ty) = map.expect_item(item.id).kind {
for bound in exist_ty.bounds {
if let GenericBound::Outlives(_) = *bound {
self.record(&None);
}
}
} else {
unreachable!()
}
let item = map.expect_item(item.id);
walk_item(self, item);
walk_ty(self, ty);
},
TyKind::BareFn(&BareFnTy { decl, .. }) => {
let mut sub_visitor = RefVisitor::new(self.cx);
sub_visitor.visit_fn_decl(decl);
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
},
TyKind::TraitObject(bounds, ref lt) => {
if !lt.is_elided() {
self.abort = true;
self.unelided_trait_object_lifetime = true;
}
for bound in bounds {
self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
Expand Down Expand Up @@ -430,16 +420,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl
walk_param_bound(&mut visitor, bound);
}
// and check that all lifetimes are allowed
match visitor.into_vec() {
None => return false,
Some(lts) => {
for lt in lts {
if !allowed_lts.contains(&lt) {
return true;
}
}
},
}
return visitor.all_lts().iter().any(|it| !allowed_lts.contains(it));
},
WherePredicate::EqPredicate(ref pred) => {
let mut visitor = RefVisitor::new(cx);
Expand Down
3 changes: 3 additions & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FN: [&str; 3] = ["core", "ops", "Fn"];
pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
ebroto marked this conversation as resolved.
Show resolved Hide resolved
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
Expand Down
10 changes: 10 additions & 0 deletions tests/ui/crashes/ice-2774.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
--> $DIR/ice-2774.rs:17:1
|
LL | pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::needless-lifetimes` implied by `-D warnings`

error: aborting due to previous error

14 changes: 14 additions & 0 deletions tests/ui/crashes/needless_lifetimes_impl_trait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration)
--> $DIR/needless_lifetimes_impl_trait.rs:17:5
|
LL | fn baz<'a>(&'a self) -> impl Foo + 'a {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/needless_lifetimes_impl_trait.rs:3:9
|
LL | #![deny(clippy::needless_lifetimes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

82 changes: 82 additions & 0 deletions tests/ui/needless_lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,86 @@ mod issue4291 {
}
}

mod issue2944 {
trait Foo {}
struct Bar {}
struct Baz<'a> {
bar: &'a Bar,
}

impl<'a> Foo for Baz<'a> {}
impl Bar {
fn baz<'a>(&'a self) -> impl Foo + 'a {
Baz { bar: self }
}
}
}

mod nested_elision_sites {
ebroto marked this conversation as resolved.
Show resolved Hide resolved
// issue #issue2944

// closure trait bounds subject to nested elision
// don't lint because they refer to outer lifetimes
fn trait_fn<'a>(i: &'a i32) -> impl Fn() -> &'a i32 {
move || i
}
fn trait_fn_mut<'a>(i: &'a i32) -> impl FnMut() -> &'a i32 {
move || i
}
fn trait_fn_once<'a>(i: &'a i32) -> impl FnOnce() -> &'a i32 {
move || i
}

// don't lint
fn impl_trait_in_input_position<'a>(f: impl Fn() -> &'a i32) -> &'a i32 {
f()
}
fn impl_trait_in_output_position<'a>(i: &'a i32) -> impl Fn() -> &'a i32 {
move || i
}
// lint
fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 {
f(i)
}
fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 {
f(i)
}

// don't lint
fn generics_not_elidable<'a, T: Fn() -> &'a i32>(f: T) -> &'a i32 {
f()
}
// lint
fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 {
f(i)
}

// don't lint
fn where_clause_not_elidable<'a, T>(f: T) -> &'a i32
where
T: Fn() -> &'a i32,
{
f()
}
// lint
fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32
where
T: Fn(&i32) -> &i32,
{
f(i)
}

// don't lint
fn pointer_fn_in_input_position<'a>(f: fn(&'a i32) -> &'a i32, i: &'a i32) -> &'a i32 {
f(i)
}
fn pointer_fn_in_output_position<'a>(_: &'a i32) -> fn(&'a i32) -> &'a i32 {
|i| i
}
// lint
fn pointer_fn_elidable<'a>(f: fn(&i32) -> &i32, i: &'a i32) -> &'a i32 {
ebroto marked this conversation as resolved.
Show resolved Hide resolved
f(i)
}
}

fn main() {}
Loading