Skip to content

Commit 56e8db4

Browse files
committed
new lint: inherent methods that should be trait impls (fixes rust-lang#218)
1 parent 802d56c commit 56e8db4

File tree

4 files changed

+140
-5
lines changed

4 files changed

+140
-5
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
A collection of lints that give helpful tips to newbies and catch oversights.
55

66
##Lints
7-
There are 45 lints included in this crate:
7+
There are 46 lints included in this crate:
88

99
name | default | meaning
1010
-------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -44,6 +44,7 @@ ptr_arg | allow | fn arguments of the type `&Vec<...>` or `&S
4444
range_step_by_zero | warn | using Range::step_by(0), which produces an infinite iterator
4545
redundant_closure | warn | using redundant closures, i.e. `|a| foo(a)` (which can be written as just `foo`)
4646
result_unwrap_used | allow | using `Result.unwrap()`, which might be better handled
47+
should_implement_trait | warn | defining a method that should be implementing a std trait
4748
single_match | warn | a match statement with a single nontrivial arm (i.e, where the other arm is `_ => {}`) is used; recommends `if let` instead
4849
str_to_string | warn | using `to_string()` on a str, which should be `to_owned()`
4950
string_add | allow | using `x + ..` where x is a `String`; suggests using `push_str()` instead

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
9292
matches::SINGLE_MATCH,
9393
methods::OPTION_UNWRAP_USED,
9494
methods::RESULT_UNWRAP_USED,
95+
methods::SHOULD_IMPLEMENT_TRAIT,
9596
methods::STR_TO_STRING,
9697
methods::STRING_TO_STRING,
9798
misc::CMP_NAN,

src/methods.rs

+116-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ use syntax::ast::*;
22
use rustc::lint::*;
33
use rustc::middle::ty;
44

5-
use utils::{span_lint, match_type, walk_ptrs_ty};
5+
use utils::{span_lint, match_path, match_type, walk_ptrs_ty};
66
use utils::{OPTION_PATH, RESULT_PATH, STRING_PATH};
77

8+
use self::SelfKind::*;
9+
use self::OutType::*;
10+
811
#[derive(Copy,Clone)]
912
pub struct MethodsPass;
1013

@@ -16,10 +19,13 @@ declare_lint!(pub STR_TO_STRING, Warn,
1619
"using `to_string()` on a str, which should be `to_owned()`");
1720
declare_lint!(pub STRING_TO_STRING, Warn,
1821
"calling `String.to_string()` which is a no-op");
22+
declare_lint!(pub SHOULD_IMPLEMENT_TRAIT, Warn,
23+
"defining a method that should be implementing a std trait");
1924

2025
impl LintPass for MethodsPass {
2126
fn get_lints(&self) -> LintArray {
22-
lint_array!(OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, STR_TO_STRING, STRING_TO_STRING)
27+
lint_array!(OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, STR_TO_STRING, STRING_TO_STRING,
28+
SHOULD_IMPLEMENT_TRAIT)
2329
}
2430

2531
fn check_expr(&mut self, cx: &Context, expr: &Expr) {
@@ -46,4 +52,112 @@ impl LintPass for MethodsPass {
4652
}
4753
}
4854
}
55+
56+
fn check_item(&mut self, cx: &Context, item: &Item) {
57+
if let ItemImpl(_, _, _, None, _, ref items) = item.node {
58+
for item in items {
59+
let name = item.ident.name;
60+
for &(method_name, n_args, self_kind, out_type, trait_name) in &TRAIT_METHODS {
61+
if_let_chain! {
62+
[
63+
name == method_name,
64+
let MethodImplItem(ref sig, _) = item.node,
65+
sig.decl.inputs.len() == n_args,
66+
out_type.matches(&sig.decl.output),
67+
self_kind.matches(&sig.explicit_self.node)
68+
], {
69+
span_lint(cx, SHOULD_IMPLEMENT_TRAIT, item.span, &format!(
70+
"defining a method called `{}` on this type; consider implementing \
71+
the `{}` trait or choosing a less ambiguous name", name, trait_name));
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
const TRAIT_METHODS: [(&'static str, usize, SelfKind, OutType, &'static str); 30] = [
81+
("add", 2, ValueSelf, AnyType, "std::ops::Add`"),
82+
("sub", 2, ValueSelf, AnyType, "std::ops::Sub"),
83+
("mul", 2, ValueSelf, AnyType, "std::ops::Mul"),
84+
("div", 2, ValueSelf, AnyType, "std::ops::Div"),
85+
("rem", 2, ValueSelf, AnyType, "std::ops::Rem"),
86+
("shl", 2, ValueSelf, AnyType, "std::ops::Shl"),
87+
("shr", 2, ValueSelf, AnyType, "std::ops::Shr"),
88+
("bitand", 2, ValueSelf, AnyType, "std::ops::BitAnd"),
89+
("bitor", 2, ValueSelf, AnyType, "std::ops::BitOr"),
90+
("bitxor", 2, ValueSelf, AnyType, "std::ops::BitXor"),
91+
("neg", 1, ValueSelf, AnyType, "std::ops::Neg"),
92+
("not", 1, ValueSelf, AnyType, "std::ops::Not"),
93+
("drop", 1, RefMutSelf, UnitType, "std::ops::Drop"),
94+
("index", 2, RefSelf, RefType, "std::ops::Index"),
95+
("index_mut", 2, RefMutSelf, RefType, "std::ops::IndexMut"),
96+
("deref", 1, RefSelf, RefType, "std::ops::Deref"),
97+
("deref_mut", 1, RefMutSelf, RefType, "std::ops::DerefMut"),
98+
("clone", 1, RefSelf, AnyType, "std::clone::Clone"),
99+
("borrow", 1, RefSelf, RefType, "std::borrow::Borrow"),
100+
("borrow_mut", 1, RefMutSelf, RefType, "std::borrow::BorrowMut"),
101+
("as_ref", 1, RefSelf, RefType, "std::convert::AsRef"),
102+
("as_mut", 1, RefMutSelf, RefType, "std::convert::AsMut"),
103+
("eq", 2, RefSelf, BoolType, "std::cmp::PartialEq"),
104+
("cmp", 2, RefSelf, AnyType, "std::cmp::Ord"),
105+
("default", 0, NoSelf, AnyType, "std::default::Default"),
106+
("hash", 2, RefSelf, UnitType, "std::hash::Hash"),
107+
("next", 1, RefMutSelf, AnyType, "std::iter::Iterator"),
108+
("into_iter", 1, ValueSelf, AnyType, "std::iter::IntoIterator"),
109+
("from_iter", 1, NoSelf, AnyType, "std::iter::FromIterator"),
110+
("from_str", 1, NoSelf, AnyType, "std::str::FromStr"),
111+
];
112+
113+
#[derive(Clone, Copy)]
114+
enum SelfKind {
115+
ValueSelf,
116+
RefSelf,
117+
RefMutSelf,
118+
NoSelf
119+
}
120+
121+
impl SelfKind {
122+
fn matches(&self, slf: &ExplicitSelf_) -> bool {
123+
match (self, slf) {
124+
(&ValueSelf, &SelfValue(_)) => true,
125+
(&RefSelf, &SelfRegion(_, Mutability::MutImmutable, _)) => true,
126+
(&RefMutSelf, &SelfRegion(_, Mutability::MutMutable, _)) => true,
127+
(&NoSelf, &SelfStatic) => true,
128+
_ => false
129+
}
130+
}
131+
}
132+
133+
#[derive(Clone, Copy)]
134+
enum OutType {
135+
UnitType,
136+
BoolType,
137+
AnyType,
138+
RefType,
139+
}
140+
141+
impl OutType {
142+
fn matches(&self, ty: &FunctionRetTy) -> bool {
143+
match (self, ty) {
144+
(&UnitType, &DefaultReturn(_)) => true,
145+
(&UnitType, &Return(ref ty)) if ty.node == TyTup(vec![]) => true,
146+
(&BoolType, &Return(ref ty)) if is_bool(ty) => true,
147+
(&AnyType, &Return(ref ty)) if ty.node != TyTup(vec![]) => true,
148+
(&RefType, &Return(ref ty)) => {
149+
if let TyRptr(_, _) = ty.node { true } else { false }
150+
}
151+
_ => false
152+
}
153+
}
154+
}
155+
156+
fn is_bool(ty: &Ty) -> bool {
157+
if let TyPath(None, ref p) = ty.node {
158+
if match_path(p, &["bool"]) {
159+
return true;
160+
}
161+
}
162+
false
49163
}

tests/compile-fail/methods.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
#![feature(plugin)]
22
#![plugin(clippy)]
33

4-
#[deny(option_unwrap_used, result_unwrap_used)]
5-
#[deny(str_to_string, string_to_string)]
4+
#![allow(unused)]
5+
#![deny(clippy)]
6+
7+
use std::ops::Mul;
8+
9+
struct T;
10+
11+
impl T {
12+
fn add(self, other: T) -> T { self } //~ERROR defining a method called `add`
13+
fn drop(&mut self) { } //~ERROR defining a method called `drop`
14+
15+
fn sub(&self, other: T) -> &T { self } // no error, self is a ref
16+
fn div(self) -> T { self } // no error, different #arguments
17+
fn rem(self, other: T) { } // no error, wrong return type
18+
}
19+
20+
impl Mul<T> for T {
21+
type Output = T;
22+
fn mul(self, other: T) -> T { self } // no error, obviously
23+
}
24+
625
fn main() {
726
let opt = Some(0);
827
let _ = opt.unwrap(); //~ERROR used unwrap() on an Option

0 commit comments

Comments
 (0)