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