Skip to content

Commit

Permalink
Auto merge of #5725 - montrivo:should-impl-trait, r=flip1995,yaahc
Browse files Browse the repository at this point in the history
should_impl_trait - ignore methods with lifetime params

Fixes: #5617

changelog: don't lint should_implement_trait when an `Iterator::next` case has explicit parameters
  • Loading branch information
bors committed Aug 4, 2020
2 parents bbbc973 + 108032e commit 9df7e19
Show file tree
Hide file tree
Showing 8 changed files with 673 additions and 129 deletions.
144 changes: 103 additions & 41 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,16 +1496,31 @@ impl<'tcx> LateLintPass<'tcx> for Methods {

then {
if cx.access_levels.is_exported(impl_item.hir_id) {
// check missing trait implementations
for &(method_name, n_args, fn_header, self_kind, out_type, trait_name) in &TRAIT_METHODS {
if name == method_name &&
sig.decl.inputs.len() == n_args &&
out_type.matches(cx, &sig.decl.output) &&
self_kind.matches(cx, self_ty, first_arg_ty) &&
fn_header_equals(*fn_header, sig.header) {
span_lint(cx, SHOULD_IMPLEMENT_TRAIT, impl_item.span, &format!(
"defining a method called `{}` on this type; consider implementing \
the `{}` trait or choosing a less ambiguous name", name, trait_name));
// check missing trait implementations
for method_config in &TRAIT_METHODS {
if name == method_config.method_name &&
sig.decl.inputs.len() == method_config.param_count &&
method_config.output_type.matches(cx, &sig.decl.output) &&
method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
fn_header_equals(*method_config.fn_header, sig.header) &&
method_config.lifetime_param_cond(&impl_item)
{
span_lint_and_help(
cx,
SHOULD_IMPLEMENT_TRAIT,
impl_item.span,
&format!(
"method `{}` can be confused for the standard trait method `{}::{}`",
method_config.method_name,
method_config.trait_name,
method_config.method_name
),
None,
&format!(
"consider implementing the trait `{}` or choosing a less ambiguous method name",
method_config.trait_name
)
);
}
}
}
Expand Down Expand Up @@ -3390,38 +3405,85 @@ const FN_HEADER: hir::FnHeader = hir::FnHeader {
abi: rustc_target::spec::abi::Abi::Rust,
};

struct ShouldImplTraitCase {
trait_name: &'static str,
method_name: &'static str,
param_count: usize,
fn_header: &'static hir::FnHeader,
// implicit self kind expected (none, self, &self, ...)
self_kind: SelfKind,
// checks against the output type
output_type: OutType,
// certain methods with explicit lifetimes can't implement the equivalent trait method
lint_explicit_lifetime: bool,
}
impl ShouldImplTraitCase {
const fn new(
trait_name: &'static str,
method_name: &'static str,
param_count: usize,
fn_header: &'static hir::FnHeader,
self_kind: SelfKind,
output_type: OutType,
lint_explicit_lifetime: bool,
) -> ShouldImplTraitCase {
ShouldImplTraitCase {
trait_name,
method_name,
param_count,
fn_header,
self_kind,
output_type,
lint_explicit_lifetime,
}
}

fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
self.lint_explicit_lifetime
|| !impl_item.generics.params.iter().any(|p| {
matches!(
p.kind,
hir::GenericParamKind::Lifetime {
kind: hir::LifetimeParamKind::Explicit
}
)
})
}
}

#[rustfmt::skip]
const TRAIT_METHODS: [(&str, usize, &hir::FnHeader, SelfKind, OutType, &str); 30] = [
("add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Add"),
("as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::convert::AsMut"),
("as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::convert::AsRef"),
("bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitAnd"),
("bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitOr"),
("bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitXor"),
("borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::borrow::Borrow"),
("borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::borrow::BorrowMut"),
("clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::clone::Clone"),
("cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::cmp::Ord"),
("default", 0, &FN_HEADER, SelfKind::No, OutType::Any, "std::default::Default"),
("deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Deref"),
("deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::DerefMut"),
("div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Div"),
("drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, "std::ops::Drop"),
("eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, "std::cmp::PartialEq"),
("from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::iter::FromIterator"),
("from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::str::FromStr"),
("hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, "std::hash::Hash"),
("index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Index"),
("index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::IndexMut"),
("into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::iter::IntoIterator"),
("mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Mul"),
("neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Neg"),
("next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, "std::iter::Iterator"),
("not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Not"),
("rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Rem"),
("shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shl"),
("shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shr"),
("sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Sub"),
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", "add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, true),
ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, true),
// FIXME: default doesn't work
ShouldImplTraitCase::new("std::default::Default", "default", 0, &FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::Div", "div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, true),
ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, true),
ShouldImplTraitCase::new("std::ops::Index", "index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, false),
ShouldImplTraitCase::new("std::ops::Not", "not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, true),
];

#[rustfmt::skip]
Expand Down
68 changes: 3 additions & 65 deletions tests/ui/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
clippy::non_ascii_literal,
clippy::new_without_default,
clippy::needless_pass_by_value,
clippy::needless_lifetimes,
clippy::print_stdout,
clippy::must_use_candidate,
clippy::use_self,
Expand All @@ -33,71 +34,6 @@ use std::sync::{self, Arc};

use option_helpers::IteratorFalsePositives;

pub struct T;

impl T {
pub fn add(self, other: T) -> T {
self
}

// no error, not public interface
pub(crate) fn drop(&mut self) {}

// no error, private function
fn neg(self) -> Self {
self
}

// no error, private function
fn eq(&self, other: T) -> bool {
true
}

// No error; self is a ref.
fn sub(&self, other: T) -> &T {
self
}

// No error; different number of arguments.
fn div(self) -> T {
self
}

// No error; wrong return type.
fn rem(self, other: T) {}

// Fine
fn into_u32(self) -> u32 {
0
}

fn into_u16(&self) -> u16 {
0
}

fn to_something(self) -> u32 {
0
}

fn new(self) -> Self {
unimplemented!();
}
}

pub struct T1;

impl T1 {
// Shouldn't trigger lint as it is unsafe.
pub unsafe fn add(self, rhs: T1) -> T1 {
self
}

// Should not trigger lint since this is an async function.
pub async fn next(&mut self) -> Option<T1> {
None
}
}

struct Lt<'a> {
foo: &'a u32,
}
Expand Down Expand Up @@ -171,6 +107,8 @@ impl BadNew {
}
}

struct T;

impl Mul<T> for T {
type Output = T;
// No error, obviously.
Expand Down
36 changes: 13 additions & 23 deletions tests/ui/methods.stderr
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
error: defining a method called `add` on this type; consider implementing the `std::ops::Add` trait or choosing a less ambiguous name
--> $DIR/methods.rs:39:5
|
LL | / pub fn add(self, other: T) -> T {
LL | | self
LL | | }
| |_____^
|
= note: `-D clippy::should-implement-trait` implied by `-D warnings`

error: methods called `new` usually return `Self`
--> $DIR/methods.rs:169:5
--> $DIR/methods.rs:105:5
|
LL | / fn new() -> i32 {
LL | | 0
Expand All @@ -19,7 +9,7 @@ LL | | }
= note: `-D clippy::new-ret-no-self` implied by `-D warnings`

error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead.
--> $DIR/methods.rs:188:13
--> $DIR/methods.rs:126:13
|
LL | let _ = v.iter().filter(|&x| *x < 0).next();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -28,7 +18,7 @@ LL | let _ = v.iter().filter(|&x| *x < 0).next();
= note: replace `filter(|&x| *x < 0).next()` with `find(|&x| *x < 0)`

error: called `filter(p).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(p)` instead.
--> $DIR/methods.rs:191:13
--> $DIR/methods.rs:129:13
|
LL | let _ = v.iter().filter(|&x| {
| _____________^
Expand All @@ -38,33 +28,33 @@ LL | | ).next();
| |___________________________^

error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:208:22
--> $DIR/methods.rs:146:22
|
LL | let _ = v.iter().find(|&x| *x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x < 0)`
|
= note: `-D clippy::search-is-some` implied by `-D warnings`

error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:209:20
--> $DIR/methods.rs:147:20
|
LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| **y == x)`

error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:210:20
--> $DIR/methods.rs:148:20
|
LL | let _ = (0..1).find(|x| *x == 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| x == 0)`

error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:211:22
--> $DIR/methods.rs:149:22
|
LL | let _ = v.iter().find(|x| **x == 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|x| *x == 0)`

error: called `is_some()` after searching an `Iterator` with find. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:214:13
--> $DIR/methods.rs:152:13
|
LL | let _ = v.iter().find(|&x| {
| _____________^
Expand All @@ -74,13 +64,13 @@ LL | | ).is_some();
| |______________________________^

error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:220:22
--> $DIR/methods.rs:158:22
|
LL | let _ = v.iter().position(|&x| x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`

error: called `is_some()` after searching an `Iterator` with position. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:223:13
--> $DIR/methods.rs:161:13
|
LL | let _ = v.iter().position(|&x| {
| _____________^
Expand All @@ -90,13 +80,13 @@ LL | | ).is_some();
| |______________________________^

error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:229:22
--> $DIR/methods.rs:167:22
|
LL | let _ = v.iter().rposition(|&x| x < 0).is_some();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `any(|&x| x < 0)`

error: called `is_some()` after searching an `Iterator` with rposition. This is more succinctly expressed by calling `any()`.
--> $DIR/methods.rs:232:13
--> $DIR/methods.rs:170:13
|
LL | let _ = v.iter().rposition(|&x| {
| _____________^
Expand All @@ -105,5 +95,5 @@ LL | | }
LL | | ).is_some();
| |______________________________^

error: aborting due to 13 previous errors
error: aborting due to 12 previous errors

Loading

0 comments on commit 9df7e19

Please sign in to comment.