]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/precedence.rs
Avoid "whitelist"
[rust.git] / clippy_lints / src / precedence.rs
1 use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
2 use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
3 use rustc_errors::Applicability;
4 use rustc_lint::{EarlyContext, EarlyLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6 use rustc_span::source_map::Spanned;
7
8 const ALLOWED_ODD_FUNCTIONS: [&str; 14] = [
9     "asin",
10     "asinh",
11     "atan",
12     "atanh",
13     "cbrt",
14     "fract",
15     "round",
16     "signum",
17     "sin",
18     "sinh",
19     "tan",
20     "tanh",
21     "to_degrees",
22     "to_radians",
23 ];
24
25 declare_clippy_lint! {
26     /// **What it does:** Checks for operations where precedence may be unclear
27     /// and suggests to add parentheses. Currently it catches the following:
28     /// * mixed usage of arithmetic and bit shifting/combining operators without
29     /// parentheses
30     /// * a "negative" numeric literal (which is really a unary `-` followed by a
31     /// numeric literal)
32     ///   followed by a method call
33     ///
34     /// **Why is this bad?** Not everyone knows the precedence of those operators by
35     /// heart, so expressions like these may trip others trying to reason about the
36     /// code.
37     ///
38     /// **Known problems:** None.
39     ///
40     /// **Example:**
41     /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
42     /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1
43     pub PRECEDENCE,
44     complexity,
45     "operations where precedence may be unclear"
46 }
47
48 declare_lint_pass!(Precedence => [PRECEDENCE]);
49
50 impl EarlyLintPass for Precedence {
51     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
52         if expr.span.from_expansion() {
53             return;
54         }
55
56         if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind {
57             let span_sugg = |expr: &Expr, sugg, appl| {
58                 span_lint_and_sugg(
59                     cx,
60                     PRECEDENCE,
61                     expr.span,
62                     "operator precedence can trip the unwary",
63                     "consider parenthesizing your expression",
64                     sugg,
65                     appl,
66                 );
67             };
68
69             if !is_bit_op(op) {
70                 return;
71             }
72             let mut applicability = Applicability::MachineApplicable;
73             match (is_arith_expr(left), is_arith_expr(right)) {
74                 (true, true) => {
75                     let sugg = format!(
76                         "({}) {} ({})",
77                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
78                         op.to_string(),
79                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
80                     );
81                     span_sugg(expr, sugg, applicability);
82                 },
83                 (true, false) => {
84                     let sugg = format!(
85                         "({}) {} {}",
86                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
87                         op.to_string(),
88                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
89                     );
90                     span_sugg(expr, sugg, applicability);
91                 },
92                 (false, true) => {
93                     let sugg = format!(
94                         "{} {} ({})",
95                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
96                         op.to_string(),
97                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
98                     );
99                     span_sugg(expr, sugg, applicability);
100                 },
101                 (false, false) => (),
102             }
103         }
104
105         if let ExprKind::Unary(UnOp::Neg, ref rhs) = expr.kind {
106             if let ExprKind::MethodCall(ref path_segment, ref args, _) = rhs.kind {
107                 let path_segment_str = path_segment.ident.name.as_str();
108                 if let Some(slf) = args.first() {
109                     if let ExprKind::Lit(ref lit) = slf.kind {
110                         match lit.kind {
111                             LitKind::Int(..) | LitKind::Float(..) => {
112                                 if ALLOWED_ODD_FUNCTIONS
113                                     .iter()
114                                     .any(|odd_function| **odd_function == *path_segment_str)
115                                 {
116                                     return;
117                                 }
118                                 let mut applicability = Applicability::MachineApplicable;
119                                 span_lint_and_sugg(
120                                     cx,
121                                     PRECEDENCE,
122                                     expr.span,
123                                     "unary minus has lower precedence than method call",
124                                     "consider adding parentheses to clarify your intent",
125                                     format!(
126                                         "-({})",
127                                         snippet_with_applicability(cx, rhs.span, "..", &mut applicability)
128                                     ),
129                                     applicability,
130                                 );
131                             },
132                             _ => (),
133                         }
134                     }
135                 }
136             }
137         }
138     }
139 }
140
141 fn is_arith_expr(expr: &Expr) -> bool {
142     match expr.kind {
143         ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
144         _ => false,
145     }
146 }
147
148 #[must_use]
149 fn is_bit_op(op: BinOpKind) -> bool {
150     use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
151     match op {
152         BitXor | BitAnd | BitOr | Shl | Shr => true,
153         _ => false,
154     }
155 }
156
157 #[must_use]
158 fn is_arith_op(op: BinOpKind) -> bool {
159     use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub};
160     match op {
161         Add | Sub | Mul | Div | Rem => true,
162         _ => false,
163     }
164 }