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