]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/precedence.rs
Merge pull request #3265 from mikerite/fix-export
[rust.git] / clippy_lints / src / precedence.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10
11 use crate::rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass};
12 use crate::rustc::{declare_tool_lint, lint_array};
13 use crate::syntax::ast::*;
14 use crate::syntax::source_map::Spanned;
15 use crate::utils::{in_macro, snippet, span_lint_and_sugg};
16
17 /// **What it does:** Checks for operations where precedence may be unclear
18 /// and suggests to add parentheses. Currently it catches the following:
19 /// * mixed usage of arithmetic and bit shifting/combining operators without
20 /// parentheses
21 /// * a "negative" numeric literal (which is really a unary `-` followed by a
22 /// numeric literal)
23 ///   followed by a method call
24 ///
25 /// **Why is this bad?** Not everyone knows the precedence of those operators by
26 /// heart, so expressions like these may trip others trying to reason about the
27 /// code.
28 ///
29 /// **Known problems:** None.
30 ///
31 /// **Example:**
32 /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
33 /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1
34 declare_clippy_lint! {
35     pub PRECEDENCE,
36     complexity,
37     "operations where precedence may be unclear"
38 }
39
40 #[derive(Copy, Clone)]
41 pub struct Precedence;
42
43 impl LintPass for Precedence {
44     fn get_lints(&self) -> LintArray {
45         lint_array!(PRECEDENCE)
46     }
47 }
48
49 impl EarlyLintPass for Precedence {
50     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
51         if in_macro(expr.span) {
52             return;
53         }
54
55         if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.node {
56             let span_sugg = |expr: &Expr, sugg| {
57                 span_lint_and_sugg(
58                     cx,
59                     PRECEDENCE,
60                     expr.span,
61                     "operator precedence can trip the unwary",
62                     "consider parenthesizing your expression",
63                     sugg,
64                 );
65             };
66
67             if !is_bit_op(op) {
68                 return;
69             }
70             match (is_arith_expr(left), is_arith_expr(right)) {
71                 (true, true) => {
72                     let sugg = format!(
73                         "({}) {} ({})",
74                         snippet(cx, left.span, ".."),
75                         op.to_string(),
76                         snippet(cx, right.span, "..")
77                     );
78                     span_sugg(expr, sugg);
79                 },
80                 (true, false) => {
81                     let sugg = format!(
82                         "({}) {} {}",
83                         snippet(cx, left.span, ".."),
84                         op.to_string(),
85                         snippet(cx, right.span, "..")
86                     );
87                     span_sugg(expr, sugg);
88                 },
89                 (false, true) => {
90                     let sugg = format!(
91                         "{} {} ({})",
92                         snippet(cx, left.span, ".."),
93                         op.to_string(),
94                         snippet(cx, right.span, "..")
95                     );
96                     span_sugg(expr, sugg);
97                 },
98                 (false, false) => (),
99             }
100         }
101
102         if let ExprKind::Unary(UnOp::Neg, ref rhs) = expr.node {
103             if let ExprKind::MethodCall(_, ref args) = rhs.node {
104                 if let Some(slf) = args.first() {
105                     if let ExprKind::Lit(ref lit) = slf.node {
106                         match lit.node {
107                             LitKind::Int(..) | LitKind::Float(..) | LitKind::FloatUnsuffixed(..) => {
108                                 span_lint_and_sugg(
109                                     cx,
110                                     PRECEDENCE,
111                                     expr.span,
112                                     "unary minus has lower precedence than method call",
113                                     "consider adding parentheses to clarify your intent",
114                                     format!("-({})", snippet(cx, rhs.span, "..")),
115                                 );
116                             },
117                             _ => (),
118                         }
119                     }
120                 }
121             }
122         }
123     }
124 }
125
126 fn is_arith_expr(expr: &Expr) -> bool {
127     match expr.node {
128         ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
129         _ => false,
130     }
131 }
132
133 fn is_bit_op(op: BinOpKind) -> bool {
134     use crate::syntax::ast::BinOpKind::*;
135     match op {
136         BitXor | BitAnd | BitOr | Shl | Shr => true,
137         _ => false,
138     }
139 }
140
141 fn is_arith_op(op: BinOpKind) -> bool {
142     use crate::syntax::ast::BinOpKind::*;
143     match op {
144         Add | Sub | Mul | Div | Rem => true,
145         _ => false,
146     }
147 }