]> git.lizzy.rs Git - rust.git/blob - src/methods.rs
rustup to 1.5.0-nightly (7bf4c885f 2015-09-26)
[rust.git] / src / methods.rs
1 use rustc_front::hir::*;
2 use rustc::lint::*;
3 use rustc::middle::ty;
4 use rustc::middle::subst::Subst;
5 use std::iter;
6 use std::borrow::Cow;
7
8 use utils::{snippet, span_lint, match_path, match_type, walk_ptrs_ty_depth};
9 use utils::{OPTION_PATH, RESULT_PATH, STRING_PATH};
10
11 use self::SelfKind::*;
12 use self::OutType::*;
13
14 #[derive(Copy,Clone)]
15 pub struct MethodsPass;
16
17 declare_lint!(pub OPTION_UNWRAP_USED, Allow,
18               "using `Option.unwrap()`, which should at least get a better message using `expect()`");
19 declare_lint!(pub RESULT_UNWRAP_USED, Allow,
20               "using `Result.unwrap()`, which might be better handled");
21 declare_lint!(pub STR_TO_STRING, Warn,
22               "using `to_string()` on a str, which should be `to_owned()`");
23 declare_lint!(pub STRING_TO_STRING, Warn,
24               "calling `String.to_string()` which is a no-op");
25 declare_lint!(pub SHOULD_IMPLEMENT_TRAIT, Warn,
26               "defining a method that should be implementing a std trait");
27 declare_lint!(pub WRONG_SELF_CONVENTION, Warn,
28               "defining a method named with an established prefix (like \"into_\") that takes \
29                `self` with the wrong convention");
30 declare_lint!(pub WRONG_PUB_SELF_CONVENTION, Allow,
31               "defining a public method named with an established prefix (like \"into_\") that takes \
32                `self` with the wrong convention");
33
34 impl LintPass for MethodsPass {
35     fn get_lints(&self) -> LintArray {
36         lint_array!(OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, STR_TO_STRING, STRING_TO_STRING,
37                     SHOULD_IMPLEMENT_TRAIT, WRONG_SELF_CONVENTION)
38     }
39 }
40
41 impl LateLintPass for MethodsPass {
42     fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
43         if let ExprMethodCall(ref name, _, ref args) = expr.node {
44             let (obj_ty, ptr_depth) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&args[0]));
45             if name.node.as_str() == "unwrap" {
46                 if match_type(cx, obj_ty, &OPTION_PATH) {
47                     span_lint(cx, OPTION_UNWRAP_USED, expr.span,
48                               "used unwrap() on an Option value. If you don't want \
49                                to handle the None case gracefully, consider using \
50                                expect() to provide a better panic message");
51                 } else if match_type(cx, obj_ty, &RESULT_PATH) {
52                     span_lint(cx, RESULT_UNWRAP_USED, expr.span,
53                               "used unwrap() on a Result value. Graceful handling \
54                                of Err values is preferred");
55                 }
56             }
57             else if name.node.as_str() == "to_string" {
58                 if obj_ty.sty == ty::TyStr {
59                     let mut arg_str = snippet(cx, args[0].span, "_");
60                     if ptr_depth > 1 {
61                         arg_str = Cow::Owned(format!(
62                             "({}{})",
63                             iter::repeat('*').take(ptr_depth - 1).collect::<String>(),
64                             arg_str));
65                     }
66                     span_lint(cx, STR_TO_STRING, expr.span, &format!(
67                         "`{}.to_owned()` is faster", arg_str));
68                 } else if match_type(cx, obj_ty, &STRING_PATH) {
69                     span_lint(cx, STRING_TO_STRING, expr.span, "`String.to_string()` is a no-op; use \
70                                                                 `clone()` to make a copy");
71                 }
72             }
73         }
74     }
75
76     fn check_item(&mut self, cx: &LateContext, item: &Item) {
77         if let ItemImpl(_, _, _, None, ref ty, ref items) = item.node {
78             for implitem in items {
79                 let name = implitem.name;
80                 if let MethodImplItem(ref sig, _) = implitem.node {
81                     // check missing trait implementations
82                     for &(method_name, n_args, self_kind, out_type, trait_name) in &TRAIT_METHODS {
83                         if_let_chain! {
84                             [
85                                 name.as_str() == method_name,
86                                 sig.decl.inputs.len() == n_args,
87                                 out_type.matches(&sig.decl.output),
88                                 self_kind.matches(&sig.explicit_self.node, false)
89                             ], {
90                                 span_lint(cx, SHOULD_IMPLEMENT_TRAIT, implitem.span, &format!(
91                                     "defining a method called `{}` on this type; consider implementing \
92                                      the `{}` trait or choosing a less ambiguous name", name, trait_name));
93                             }
94                         }
95                     }
96                     // check conventions w.r.t. conversion method names and predicates
97                     let is_copy = is_copy(cx, &ty, &item);
98                     for &(prefix, self_kinds) in &CONVENTIONS {
99                         if name.as_str().starts_with(prefix) &&
100                                 !self_kinds.iter().any(|k| k.matches(&sig.explicit_self.node, is_copy)) {
101                             let lint = if item.vis == Visibility::Public {
102                                 WRONG_PUB_SELF_CONVENTION
103                             } else {
104                                 WRONG_SELF_CONVENTION
105                             };
106                             span_lint(cx, lint, sig.explicit_self.span, &format!(
107                                 "methods called `{}*` usually take {}; consider choosing a less \
108                                  ambiguous name", prefix,
109                                 &self_kinds.iter().map(|k| k.description()).collect::<Vec<_>>().join(" or ")));
110                         }
111                     }
112                 }
113             }
114         }
115     }
116 }
117
118 const CONVENTIONS: [(&'static str, &'static [SelfKind]); 5] = [
119     ("into_", &[ValueSelf]),
120     ("to_",   &[RefSelf]),
121     ("as_",   &[RefSelf, RefMutSelf]),
122     ("is_",   &[RefSelf, NoSelf]),
123     ("from_", &[NoSelf]),
124 ];
125
126 const TRAIT_METHODS: [(&'static str, usize, SelfKind, OutType, &'static str); 30] = [
127     ("add",        2, ValueSelf,  AnyType,  "std::ops::Add"),
128     ("sub",        2, ValueSelf,  AnyType,  "std::ops::Sub"),
129     ("mul",        2, ValueSelf,  AnyType,  "std::ops::Mul"),
130     ("div",        2, ValueSelf,  AnyType,  "std::ops::Div"),
131     ("rem",        2, ValueSelf,  AnyType,  "std::ops::Rem"),
132     ("shl",        2, ValueSelf,  AnyType,  "std::ops::Shl"),
133     ("shr",        2, ValueSelf,  AnyType,  "std::ops::Shr"),
134     ("bitand",     2, ValueSelf,  AnyType,  "std::ops::BitAnd"),
135     ("bitor",      2, ValueSelf,  AnyType,  "std::ops::BitOr"),
136     ("bitxor",     2, ValueSelf,  AnyType,  "std::ops::BitXor"),
137     ("neg",        1, ValueSelf,  AnyType,  "std::ops::Neg"),
138     ("not",        1, ValueSelf,  AnyType,  "std::ops::Not"),
139     ("drop",       1, RefMutSelf, UnitType, "std::ops::Drop"),
140     ("index",      2, RefSelf,    RefType,  "std::ops::Index"),
141     ("index_mut",  2, RefMutSelf, RefType,  "std::ops::IndexMut"),
142     ("deref",      1, RefSelf,    RefType,  "std::ops::Deref"),
143     ("deref_mut",  1, RefMutSelf, RefType,  "std::ops::DerefMut"),
144     ("clone",      1, RefSelf,    AnyType,  "std::clone::Clone"),
145     ("borrow",     1, RefSelf,    RefType,  "std::borrow::Borrow"),
146     ("borrow_mut", 1, RefMutSelf, RefType,  "std::borrow::BorrowMut"),
147     ("as_ref",     1, RefSelf,    RefType,  "std::convert::AsRef"),
148     ("as_mut",     1, RefMutSelf, RefType,  "std::convert::AsMut"),
149     ("eq",         2, RefSelf,    BoolType, "std::cmp::PartialEq"),
150     ("cmp",        2, RefSelf,    AnyType,  "std::cmp::Ord"),
151     ("default",    0, NoSelf,     AnyType,  "std::default::Default"),
152     ("hash",       2, RefSelf,    UnitType, "std::hash::Hash"),
153     ("next",       1, RefMutSelf, AnyType,  "std::iter::Iterator"),
154     ("into_iter",  1, ValueSelf,  AnyType,  "std::iter::IntoIterator"),
155     ("from_iter",  1, NoSelf,     AnyType,  "std::iter::FromIterator"),
156     ("from_str",   1, NoSelf,     AnyType,  "std::str::FromStr"),
157 ];
158
159 #[derive(Clone, Copy)]
160 enum SelfKind {
161     ValueSelf,
162     RefSelf,
163     RefMutSelf,
164     NoSelf,
165 }
166
167 impl SelfKind {
168     fn matches(&self, slf: &ExplicitSelf_, allow_value_for_ref: bool) -> bool {
169         match (self, slf) {
170             (&ValueSelf, &SelfValue(_)) => true,
171             (&RefSelf, &SelfRegion(_, Mutability::MutImmutable, _)) => true,
172             (&RefMutSelf, &SelfRegion(_, Mutability::MutMutable, _)) => true,
173             (&RefSelf, &SelfValue(_)) => allow_value_for_ref,
174             (&RefMutSelf, &SelfValue(_)) => allow_value_for_ref,
175             (&NoSelf, &SelfStatic) => true,
176             (_, &SelfExplicit(ref ty, _)) => self.matches_explicit_type(ty, allow_value_for_ref),
177             _ => false
178         }
179     }
180
181     fn matches_explicit_type(&self, ty: &Ty, allow_value_for_ref: bool) -> bool {
182         match (self, &ty.node) {
183             (&ValueSelf, &TyPath(..)) => true,
184             (&RefSelf, &TyRptr(_, MutTy { mutbl: Mutability::MutImmutable, .. })) => true,
185             (&RefMutSelf, &TyRptr(_, MutTy { mutbl: Mutability::MutMutable, .. })) => true,
186             (&RefSelf, &TyPath(..)) => allow_value_for_ref,
187             (&RefMutSelf, &TyPath(..)) => allow_value_for_ref,
188             _ => false
189         }
190     }
191
192     fn description(&self) -> &'static str {
193         match *self {
194             ValueSelf => "self by value",
195             RefSelf => "self by reference",
196             RefMutSelf => "self by mutable reference",
197             NoSelf => "no self",
198         }
199     }
200 }
201
202 #[derive(Clone, Copy)]
203 enum OutType {
204     UnitType,
205     BoolType,
206     AnyType,
207     RefType,
208 }
209
210 impl OutType {
211     fn matches(&self, ty: &FunctionRetTy) -> bool {
212         match (self, ty) {
213             (&UnitType, &DefaultReturn(_)) => true,
214             (&UnitType, &Return(ref ty)) if ty.node == TyTup(vec![]) => true,
215             (&BoolType, &Return(ref ty)) if is_bool(ty) => true,
216             (&AnyType, &Return(ref ty)) if ty.node != TyTup(vec![])  => true,
217             (&RefType, &Return(ref ty)) => {
218                 if let TyRptr(_, _) = ty.node { true } else { false }
219             }
220             _ => false
221         }
222     }
223 }
224
225 fn is_bool(ty: &Ty) -> bool {
226     if let TyPath(None, ref p) = ty.node {
227         if match_path(p, &["bool"]) {
228             return true;
229         }
230     }
231     false
232 }
233
234 fn is_copy(cx: &LateContext, ast_ty: &Ty, item: &Item) -> bool {
235     match cx.tcx.ast_ty_to_ty_cache.borrow().get(&ast_ty.id) {
236         None => false,
237         Some(ty) => {
238             let env = ty::ParameterEnvironment::for_item(cx.tcx, item.id);
239             !ty.subst(cx.tcx, &env.free_substs).moves_by_default(&env, ast_ty.span)
240         }
241     }
242 }