]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/no_effect.rs
Improve docs
[rust.git] / clippy_lints / src / no_effect.rs
1 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
2 use rustc::hir::def::{Def, PathResolution};
3 use rustc::hir::{Expr, Expr_, Stmt, StmtSemi, BlockCheckMode, UnsafeSource};
4 use utils::{in_macro, span_lint, snippet_opt, span_lint_and_then};
5 use std::ops::Deref;
6
7 /// **What it does:** This lint checks for statements which have no effect.
8 ///
9 /// **Why is this bad?** Similar to dead code, these statements are actually executed. However, as
10 /// they have no effect, all they do is make the code less readable.
11 ///
12 /// **Known problems:** None.
13 ///
14 /// **Example:**
15 /// ```rust
16 /// 0;
17 /// ```
18 declare_lint! {
19     pub NO_EFFECT,
20     Warn,
21     "statements with no effect"
22 }
23
24 /// **What it does:** This lint checks for expression statements that can be reduced to a sub-expression
25 ///
26 /// **Why is this bad?** Expressions by themselves often have no side-effects. Having such
27 /// expressions reduces readability.
28 ///
29 /// **Known problems:** None.
30 ///
31 /// **Example:**
32 /// ```rust
33 /// compute_array()[0];
34 /// ```
35 declare_lint! {
36     pub UNNECESSARY_OPERATION,
37     Warn,
38     "outer expressions with no effect"
39 }
40
41 fn has_no_effect(cx: &LateContext, expr: &Expr) -> bool {
42     if in_macro(cx, expr.span) {
43         return false;
44     }
45     match expr.node {
46         Expr_::ExprLit(..) |
47         Expr_::ExprClosure(..) |
48         Expr_::ExprPath(..) => true,
49         Expr_::ExprIndex(ref a, ref b) |
50         Expr_::ExprBinary(_, ref a, ref b) => has_no_effect(cx, a) && has_no_effect(cx, b),
51         Expr_::ExprVec(ref v) |
52         Expr_::ExprTup(ref v) => v.iter().all(|val| has_no_effect(cx, val)),
53         Expr_::ExprRepeat(ref inner, _) |
54         Expr_::ExprCast(ref inner, _) |
55         Expr_::ExprType(ref inner, _) |
56         Expr_::ExprUnary(_, ref inner) |
57         Expr_::ExprField(ref inner, _) |
58         Expr_::ExprTupField(ref inner, _) |
59         Expr_::ExprAddrOf(_, ref inner) |
60         Expr_::ExprBox(ref inner) => has_no_effect(cx, inner),
61         Expr_::ExprStruct(_, ref fields, ref base) => {
62             fields.iter().all(|field| has_no_effect(cx, &field.expr)) &&
63             match *base {
64                 Some(ref base) => has_no_effect(cx, base),
65                 None => true,
66             }
67         }
68         Expr_::ExprCall(ref callee, ref args) => {
69             let def = cx.tcx.def_map.borrow().get(&callee.id).map(|d| d.full_def());
70             match def {
71                 Some(Def::Struct(..)) |
72                 Some(Def::Variant(..)) => args.iter().all(|arg| has_no_effect(cx, arg)),
73                 _ => false,
74             }
75         }
76         Expr_::ExprBlock(ref block) => {
77             block.stmts.is_empty() &&
78             if let Some(ref expr) = block.expr {
79                 has_no_effect(cx, expr)
80             } else {
81                 false
82             }
83         }
84         _ => false,
85     }
86 }
87
88 #[derive(Copy, Clone)]
89 pub struct Pass;
90
91 impl LintPass for Pass {
92     fn get_lints(&self) -> LintArray {
93         lint_array!(NO_EFFECT, UNNECESSARY_OPERATION)
94     }
95 }
96
97 impl LateLintPass for Pass {
98     fn check_stmt(&mut self, cx: &LateContext, stmt: &Stmt) {
99         if let StmtSemi(ref expr, _) = stmt.node {
100             if has_no_effect(cx, expr) {
101                 span_lint(cx, NO_EFFECT, stmt.span, "statement with no effect");
102             } else if let Some(reduced) = reduce_expression(cx, expr) {
103                 let mut snippet = String::new();
104                 for e in reduced {
105                     if in_macro(cx, e.span) {
106                         return;
107                     }
108                     if let Some(snip) = snippet_opt(cx, e.span) {
109                         snippet.push_str(&snip);
110                         snippet.push(';');
111                     } else {
112                         return;
113                     }
114                 }
115                 span_lint_and_then(cx, UNNECESSARY_OPERATION, stmt.span, "statement can be reduced", |db| {
116                     db.span_suggestion(stmt.span, "replace it with", snippet);
117                 });
118             }
119         }
120     }
121 }
122
123
124 fn reduce_expression<'a>(cx: &LateContext, expr: &'a Expr) -> Option<Vec<&'a Expr>> {
125     if in_macro(cx, expr.span) {
126         return None;
127     }
128     match expr.node {
129         Expr_::ExprIndex(ref a, ref b) |
130         Expr_::ExprBinary(_, ref a, ref b) => Some(vec![&**a, &**b]),
131         Expr_::ExprVec(ref v) |
132         Expr_::ExprTup(ref v) => Some(v.iter().map(Deref::deref).collect()),
133         Expr_::ExprRepeat(ref inner, _) |
134         Expr_::ExprCast(ref inner, _) |
135         Expr_::ExprType(ref inner, _) |
136         Expr_::ExprUnary(_, ref inner) |
137         Expr_::ExprField(ref inner, _) |
138         Expr_::ExprTupField(ref inner, _) |
139         Expr_::ExprAddrOf(_, ref inner) |
140         Expr_::ExprBox(ref inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
141         Expr_::ExprStruct(_, ref fields, ref base) => {
142             Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
143         }
144         Expr_::ExprCall(ref callee, ref args) => {
145             match cx.tcx.def_map.borrow().get(&callee.id).map(PathResolution::full_def) {
146                 Some(Def::Struct(..)) |
147                 Some(Def::Variant(..)) => Some(args.iter().map(Deref::deref).collect()),
148                 _ => None,
149             }
150         }
151         Expr_::ExprBlock(ref block) => {
152             if block.stmts.is_empty() {
153                 block.expr.as_ref().and_then(|e| {
154                     match block.rules {
155                         BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
156                         BlockCheckMode::DefaultBlock => Some(vec![&**e]),
157                         // in case of compiler-inserted signaling blocks
158                         _ => reduce_expression(cx, e),
159                     }
160                 })
161             } else {
162                 None
163             }
164         }
165         _ => None,
166     }
167 }