]> git.lizzy.rs Git - rust.git/blob - src/misc.rs
Merge pull request #160 from Manishearth/dogfood
[rust.git] / src / misc.rs
1 use syntax::ptr::P;
2 use syntax::ast;
3 use syntax::ast::*;
4 use syntax::ast_util::{is_comparison_binop, binop_to_string};
5 use syntax::visit::{FnKind};
6 use rustc::lint::{Context, LintPass, LintArray, Lint, Level};
7 use rustc::middle::ty;
8 use syntax::codemap::{Span, Spanned};
9
10 use utils::{match_path, snippet, span_lint, span_help_and_lint, walk_ptrs_ty};
11
12 /// Handles uncategorized lints
13 /// Currently handles linting of if-let-able matches
14 #[allow(missing_copy_implementations)]
15 pub struct MiscPass;
16
17
18 declare_lint!(pub SINGLE_MATCH, Warn,
19               "a match statement with a single nontrivial arm (i.e, where the other arm \
20                is `_ => {}`) is used; recommends `if let` instead");
21
22 impl LintPass for MiscPass {
23     fn get_lints(&self) -> LintArray {
24         lint_array!(SINGLE_MATCH)
25     }
26
27     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
28         if let ExprMatch(ref ex, ref arms, ast::MatchSource::Normal) = expr.node {
29             if arms.len() == 2 {
30                 if arms[0].guard.is_none() && arms[1].pats.len() == 1 {
31                     match arms[1].body.node {
32                         ExprTup(ref v) if v.is_empty() && arms[1].guard.is_none() => (),
33                         ExprBlock(ref b) if b.stmts.is_empty() && arms[1].guard.is_none() => (),
34                          _ => return
35                     }
36                     // In some cases, an exhaustive match is preferred to catch situations when
37                     // an enum is extended. So we only consider cases where a `_` wildcard is used
38                     if arms[1].pats[0].node == PatWild(PatWildSingle) &&
39                             arms[0].pats.len() == 1 {
40                         let body_code = snippet(cx, arms[0].body.span, "..");
41                         let suggestion = if let ExprBlock(_) = arms[0].body.node {
42                             body_code.into_owned()
43                         } else {
44                             format!("{{ {} }}", body_code)
45                         };
46                         span_help_and_lint(cx, SINGLE_MATCH, expr.span,
47                               "you seem to be trying to use match for \
48                               destructuring a single pattern. Did you mean to \
49                               use `if let`?",
50                               &*format!("try\nif let {} = {} {}",
51                                         snippet(cx, arms[0].pats[0].span, ".."),
52                                         snippet(cx, ex.span, ".."),
53                                         suggestion)
54                         );
55                     }
56                 }
57             }
58         }
59     }
60 }
61
62
63 declare_lint!(pub TOPLEVEL_REF_ARG, Warn,
64               "a function argument is declared `ref` (i.e. `fn foo(ref x: u8)`, but not \
65                `fn foo((ref x, ref y): (u8, u8))`)");
66
67 #[allow(missing_copy_implementations)]
68 pub struct TopLevelRefPass;
69
70 impl LintPass for TopLevelRefPass {
71     fn get_lints(&self) -> LintArray {
72         lint_array!(TOPLEVEL_REF_ARG)
73     }
74
75     fn check_fn(&mut self, cx: &Context, _: FnKind, decl: &FnDecl, _: &Block, _: Span, _: NodeId) {
76         for ref arg in &decl.inputs {
77             if let PatIdent(BindByRef(_), _, _) = arg.pat.node {
78                 span_lint(cx,
79                     TOPLEVEL_REF_ARG,
80                     arg.pat.span,
81                     "`ref` directly on a function argument is ignored. Consider using a reference type instead."
82                 );
83             }
84         }
85     }
86 }
87
88 declare_lint!(pub CMP_NAN, Deny,
89               "comparisons to NAN (which will always return false, which is probably not intended)");
90
91 #[derive(Copy,Clone)]
92 pub struct CmpNan;
93
94 impl LintPass for CmpNan {
95     fn get_lints(&self) -> LintArray {
96         lint_array!(CMP_NAN)
97     }
98
99     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
100         if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
101             if is_comparison_binop(cmp.node) {
102                 if let &ExprPath(_, ref path) = &left.node {
103                     check_nan(cx, path, expr.span);
104                 }
105                 if let &ExprPath(_, ref path) = &right.node {
106                     check_nan(cx, path, expr.span);
107                 }
108             }
109         }
110     }
111 }
112
113 fn check_nan(cx: &Context, path: &Path, span: Span) {
114     path.segments.last().map(|seg| if seg.identifier.name == "NAN" {
115         span_lint(cx, CMP_NAN, span,
116                   "doomed comparison with NAN, use `std::{f32,f64}::is_nan()` instead");
117     });
118 }
119
120 declare_lint!(pub FLOAT_CMP, Warn,
121               "using `==` or `!=` on float values (as floating-point operations \
122                usually involve rounding errors, it is always better to check for approximate \
123                equality within small bounds)");
124
125 #[derive(Copy,Clone)]
126 pub struct FloatCmp;
127
128 impl LintPass for FloatCmp {
129     fn get_lints(&self) -> LintArray {
130         lint_array!(FLOAT_CMP)
131     }
132
133     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
134         if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
135             let op = cmp.node;
136             if (op == BiEq || op == BiNe) && (is_float(cx, left) || is_float(cx, right)) {
137                 span_lint(cx, FLOAT_CMP, expr.span, &format!(
138                     "{}-comparison of f32 or f64 detected. Consider changing this to \
139                      `abs({} - {}) < epsilon` for some suitable value of epsilon",
140                     binop_to_string(op), snippet(cx, left.span, ".."),
141                     snippet(cx, right.span, "..")));
142             }
143         }
144     }
145 }
146
147 fn is_float(cx: &Context, expr: &Expr) -> bool {
148     if let ty::TyFloat(_) = walk_ptrs_ty(cx.tcx.expr_ty(expr)).sty {
149         true
150     } else {
151         false
152     }
153 }
154
155 declare_lint!(pub PRECEDENCE, Warn,
156               "expressions where precedence may trip up the unwary reader of the source; \
157                suggests adding parentheses, e.g. `x << 2 + y` will be parsed as `x << (2 + y)`");
158
159 #[derive(Copy,Clone)]
160 pub struct Precedence;
161
162 impl LintPass for Precedence {
163     fn get_lints(&self) -> LintArray {
164         lint_array!(PRECEDENCE)
165     }
166
167     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
168         if let ExprBinary(Spanned { node: op, ..}, ref left, ref right) = expr.node {
169             if is_bit_op(op) && (is_arith_expr(left) || is_arith_expr(right)) {
170                 span_lint(cx, PRECEDENCE, expr.span,
171                     "operator precedence can trip the unwary. Consider adding parentheses \
172                      to the subexpression");
173             }
174         }
175     }
176 }
177
178 fn is_arith_expr(expr : &Expr) -> bool {
179     match expr.node {
180         ExprBinary(Spanned { node: op, ..}, _, _) => is_arith_op(op),
181         _ => false
182     }
183 }
184
185 fn is_bit_op(op : BinOp_) -> bool {
186     match op {
187         BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => true,
188         _ => false
189     }
190 }
191
192 fn is_arith_op(op : BinOp_) -> bool {
193     match op {
194         BiAdd | BiSub | BiMul | BiDiv | BiRem => true,
195         _ => false
196     }
197 }
198
199 declare_lint!(pub CMP_OWNED, Warn,
200               "creating owned instances for comparing with others, e.g. `x == \"foo\".to_string()`");
201
202 #[derive(Copy,Clone)]
203 pub struct CmpOwned;
204
205 impl LintPass for CmpOwned {
206     fn get_lints(&self) -> LintArray {
207         lint_array!(CMP_OWNED)
208     }
209
210     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
211         if let ExprBinary(ref cmp, ref left, ref right) = expr.node {
212             if is_comparison_binop(cmp.node) {
213                 check_to_owned(cx, left, right.span);
214                 check_to_owned(cx, right, left.span)
215             }
216         }
217     }
218 }
219
220 fn check_to_owned(cx: &Context, expr: &Expr, other_span: Span) {
221     match &expr.node {
222         &ExprMethodCall(Spanned{node: ref ident, ..}, _, ref args) => {
223             let name = ident.name;
224             if name == "to_string" ||
225                 name == "to_owned" && is_str_arg(cx, args) {
226                     span_lint(cx, CMP_OWNED, expr.span, &format!(
227                         "this creates an owned instance just for comparison. \
228                          Consider using `{}.as_slice()` to compare without allocation",
229                         snippet(cx, other_span, "..")))
230                 }
231         },
232         &ExprCall(ref path, _) => {
233             if let &ExprPath(None, ref path) = &path.node {
234                 if match_path(path, &["String", "from_str"]) ||
235                     match_path(path, &["String", "from"]) {
236                         span_lint(cx, CMP_OWNED, expr.span, &format!(
237                             "this creates an owned instance just for comparison. \
238                              Consider using `{}.as_slice()` to compare without allocation",
239                             snippet(cx, other_span, "..")))
240                     }
241             }
242         },
243         _ => ()
244     }
245 }
246
247 fn is_str_arg(cx: &Context, args: &[P<Expr>]) -> bool {
248     args.len() == 1 && if let ty::TyStr =
249         walk_ptrs_ty(cx.tcx.expr_ty(&*args[0])).sty { true } else { false }
250 }
251
252 declare_lint!(pub MODULO_ONE, Warn, "taking a number modulo 1, which always returns 0");
253
254 #[derive(Copy,Clone)]
255 pub struct ModuloOne;
256
257 impl LintPass for ModuloOne {
258     fn get_lints(&self) -> LintArray {
259         lint_array!(MODULO_ONE)
260     }
261
262     fn check_expr(&mut self, cx: &Context, expr: &Expr) {
263         if let ExprBinary(ref cmp, _, ref right) = expr.node {
264             if let &Spanned {node: BinOp_::BiRem, ..} = cmp {
265                 if is_lit_one(right) {
266                     cx.span_lint(MODULO_ONE, expr.span, "any number modulo 1 will be 0");
267                 }
268             }
269         }
270     }
271 }
272
273 fn is_lit_one(expr: &Expr) -> bool {
274     if let ExprLit(ref spanned) = expr.node {
275         if let LitInt(1, _) = spanned.node {
276             return true;
277         }
278     }
279     false
280 }