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