]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/is_unit_expr.rs
Fix lines that exceed max width manually
[rust.git] / clippy_lints / src / is_unit_expr.rs
1 use rustc::lint::*;
2 use syntax::ast::*;
3 use syntax::ext::quote::rt::Span;
4 use utils::span_note_and_lint;
5
6 /// **What it does:** Checks for
7 ///  - () being assigned to a variable
8 ///  - () being passed to a function
9 ///
10 /// **Why is this bad?** It is extremely unlikely that a user intended to
11 /// assign '()' to valiable. Instead,
12 /// Unit is what a block evaluates to when it returns nothing. This is
13 /// typically caused by a trailing
14 ///   unintended semicolon.
15 ///
16 /// **Known problems:** None.
17 ///
18 /// **Example:**
19 /// * `let x = {"foo" ;}` when the user almost certainly intended `let x
20 /// ={"foo"}`
21 declare_lint! {
22     pub UNIT_EXPR,
23     Warn,
24     "unintended assignment or use of a unit typed value"
25 }
26
27 #[derive(Copy, Clone)]
28 pub struct UnitExpr;
29
30 impl LintPass for UnitExpr {
31     fn get_lints(&self) -> LintArray {
32         lint_array!(UNIT_EXPR)
33     }
34 }
35
36 impl EarlyLintPass for UnitExpr {
37     fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
38         if let ExprKind::Assign(ref _left, ref right) = expr.node {
39             if let Some(span) = is_unit_expr(right) {
40                 span_note_and_lint(
41                     cx,
42                     UNIT_EXPR,
43                     expr.span,
44                     "This expression evaluates to the Unit type ()",
45                     span,
46                     "Consider removing the trailing semicolon",
47                 );
48             }
49         }
50         if let ExprKind::MethodCall(ref _left, ref args) = expr.node {
51             for arg in args {
52                 if let Some(span) = is_unit_expr(arg) {
53                     span_note_and_lint(
54                         cx,
55                         UNIT_EXPR,
56                         expr.span,
57                         "This expression evaluates to the Unit type ()",
58                         span,
59                         "Consider removing the trailing semicolon",
60                     );
61                 }
62             }
63         }
64         if let ExprKind::Call(_, ref args) = expr.node {
65             for arg in args {
66                 if let Some(span) = is_unit_expr(arg) {
67                     span_note_and_lint(
68                         cx,
69                         UNIT_EXPR,
70                         expr.span,
71                         "This expression evaluates to the Unit type ()",
72                         span,
73                         "Consider removing the trailing semicolon",
74                     );
75                 }
76             }
77         }
78     }
79
80     fn check_stmt(&mut self, cx: &EarlyContext, stmt: &Stmt) {
81         if let StmtKind::Local(ref local) = stmt.node {
82             if local.pat.node == PatKind::Wild {
83                 return;
84             }
85             if let Some(ref expr) = local.init {
86                 if let Some(span) = is_unit_expr(expr) {
87                     span_note_and_lint(
88                         cx,
89                         UNIT_EXPR,
90                         expr.span,
91                         "This expression evaluates to the Unit type ()",
92                         span,
93                         "Consider removing the trailing semicolon",
94                     );
95                 }
96             }
97         }
98     }
99 }
100
101 fn is_unit_expr(expr: &Expr) -> Option<Span> {
102     match expr.node {
103         ExprKind::Block(ref block) => if check_last_stmt_in_block(block) {
104             Some(block.stmts[block.stmts.len() - 1].span)
105         } else {
106             None
107         },
108         ExprKind::If(_, ref then, ref else_) => {
109             let check_then = check_last_stmt_in_block(then);
110             if let Some(ref else_) = *else_ {
111                 let check_else = is_unit_expr(else_);
112                 if let Some(ref expr_else) = check_else {
113                     return Some(*expr_else);
114                 }
115             }
116             if check_then {
117                 Some(expr.span)
118             } else {
119                 None
120             }
121         },
122         ExprKind::Match(ref _pattern, ref arms) => {
123             for arm in arms {
124                 if let Some(expr) = is_unit_expr(&arm.body) {
125                     return Some(expr);
126                 }
127             }
128             None
129         },
130         _ => None,
131     }
132 }
133
134 fn check_last_stmt_in_block(block: &Block) -> bool {
135     let final_stmt = &block.stmts[block.stmts.len() - 1];
136
137
138     // Made a choice here to risk false positives on divergent macro invocations
139     // like `panic!()`
140     match final_stmt.node {
141         StmtKind::Expr(_) => false,
142         StmtKind::Semi(ref expr) => match expr.node {
143             ExprKind::Break(_, _) | ExprKind::Continue(_) | ExprKind::Ret(_) => false,
144             _ => true,
145         },
146         _ => true,
147     }
148 }