]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/precedence.rs
Auto merge of #91565 - dtolnay:printhelpers, r=jackh726
[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     pub PRECEDENCE,
46     complexity,
47     "operations where precedence may be unclear"
48 }
49
50 declare_lint_pass!(Precedence => [PRECEDENCE]);
51
52 impl EarlyLintPass for Precedence {
53     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
54         if expr.span.from_expansion() {
55             return;
56         }
57
58         if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind {
59             let span_sugg = |expr: &Expr, sugg, appl| {
60                 span_lint_and_sugg(
61                     cx,
62                     PRECEDENCE,
63                     expr.span,
64                     "operator precedence can trip the unwary",
65                     "consider parenthesizing your expression",
66                     sugg,
67                     appl,
68                 );
69             };
70
71             if !is_bit_op(op) {
72                 return;
73             }
74             let mut applicability = Applicability::MachineApplicable;
75             match (is_arith_expr(left), is_arith_expr(right)) {
76                 (true, true) => {
77                     let sugg = format!(
78                         "({}) {} ({})",
79                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
80                         op.to_string(),
81                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
82                     );
83                     span_sugg(expr, sugg, applicability);
84                 },
85                 (true, false) => {
86                     let sugg = format!(
87                         "({}) {} {}",
88                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
89                         op.to_string(),
90                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
91                     );
92                     span_sugg(expr, sugg, applicability);
93                 },
94                 (false, true) => {
95                     let sugg = format!(
96                         "{} {} ({})",
97                         snippet_with_applicability(cx, left.span, "..", &mut applicability),
98                         op.to_string(),
99                         snippet_with_applicability(cx, right.span, "..", &mut applicability)
100                     );
101                     span_sugg(expr, sugg, applicability);
102                 },
103                 (false, false) => (),
104             }
105         }
106
107         if let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind {
108             let mut arg = operand;
109
110             let mut all_odd = true;
111             while let ExprKind::MethodCall(path_segment, args, _) = &arg.kind {
112                 let path_segment_str = path_segment.ident.name.as_str();
113                 all_odd &= ALLOWED_ODD_FUNCTIONS
114                     .iter()
115                     .any(|odd_function| **odd_function == *path_segment_str);
116                 arg = args.first().expect("A method always has a receiver.");
117             }
118
119             if_chain! {
120                 if !all_odd;
121                 if let ExprKind::Lit(lit) = &arg.kind;
122                 if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind;
123                 then {
124                     let mut applicability = Applicability::MachineApplicable;
125                     span_lint_and_sugg(
126                         cx,
127                         PRECEDENCE,
128                         expr.span,
129                         "unary minus has lower precedence than method call",
130                         "consider adding parentheses to clarify your intent",
131                         format!(
132                             "-({})",
133                             snippet_with_applicability(cx, operand.span, "..", &mut applicability)
134                         ),
135                         applicability,
136                     );
137                 }
138             }
139         }
140     }
141 }
142
143 fn is_arith_expr(expr: &Expr) -> bool {
144     match expr.kind {
145         ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
146         _ => false,
147     }
148 }
149
150 #[must_use]
151 fn is_bit_op(op: BinOpKind) -> bool {
152     use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
153     matches!(op, BitXor | BitAnd | BitOr | Shl | Shr)
154 }
155
156 #[must_use]
157 fn is_arith_op(op: BinOpKind) -> bool {
158     use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub};
159     matches!(op, Add | Sub | Mul | Div | Rem)
160 }