|
1 |
| -use crate::utils::{span_lint_and_help, span_lint_and_sugg}; |
| 1 | +use crate::utils::{ |
| 2 | + last_path_segment, numeric_literal::NumericLiteral, qpath_res, snippet_opt, span_lint_and_help, span_lint_and_sugg, |
| 3 | +}; |
2 | 4 | use if_chain::if_chain;
|
3 |
| -use rustc_ast::{BinOpKind, Expr, ExprKind, LitKind}; |
| 5 | +use rustc_ast::{LitIntType, LitKind}; |
4 | 6 | use rustc_errors::Applicability;
|
5 |
| -use rustc_lint::{EarlyContext, EarlyLintPass}; |
| 7 | +use rustc_hir::{ |
| 8 | + def::{DefKind, Res}, |
| 9 | + BinOpKind, BindingAnnotation, Expr, ExprKind, ItemKind, Lit, Node, PatKind, QPath, |
| 10 | +}; |
| 11 | +use rustc_lint::{LateContext, LateLintPass, LintContext}; |
6 | 12 | use rustc_middle::lint::in_external_macro;
|
7 | 13 | use rustc_session::{declare_lint_pass, declare_tool_lint};
|
| 14 | +use rustc_span::Span; |
8 | 15 |
|
9 | 16 | declare_clippy_lint! {
|
10 |
| - /// **What it does:** Checks for use of `^` operator when exponentiation was intended. |
| 17 | + /// **What it does:** Checks for use of `^` operator when exponentiation was probably intended. |
| 18 | + /// A caret is commonly an ASCII-compatible/keyboard-accessible way to write down exponentiation in docs, |
| 19 | + /// readmes, and comments, and copying and pasting a formula can inadvertedly introduce this error. |
| 20 | + /// Moreover, `^` means exponentiation in other programming languages. |
11 | 21 | ///
|
12 |
| - /// **Why is this bad?** This is most probably a typo. |
| 22 | + /// **Why is this bad?** This is most probably a mistake. |
13 | 23 | ///
|
14 | 24 | /// **Known problems:** None.
|
15 | 25 | ///
|
16 | 26 | /// **Example:**
|
17 | 27 | ///
|
18 |
| - /// ```rust,ignore |
| 28 | + /// ```rust |
19 | 29 | /// // Bad
|
20 |
| - /// 2 ^ 16; |
| 30 | + /// let a = 2 ^ 16; |
| 31 | + /// let b = 10 ^ 4; |
21 | 32 | ///
|
22 | 33 | /// // Good
|
23 |
| - /// 1 << 16; |
24 |
| - /// 2i32.pow(16); |
| 34 | + /// let a = 1 << 16; |
| 35 | + /// let b = 10i32.pow(4); |
25 | 36 | /// ```
|
26 | 37 | pub XOR_USED_AS_POW,
|
27 | 38 | correctness,
|
28 |
| - "use of `^` operator when exponentiation was intended" |
| 39 | + "use of `^` operator when exponentiation was probably intended" |
29 | 40 | }
|
30 | 41 |
|
31 | 42 | declare_lint_pass!(XorUsedAsPow => [XOR_USED_AS_POW]);
|
32 | 43 |
|
33 |
| -impl EarlyLintPass for XorUsedAsPow { |
34 |
| - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { |
| 44 | +impl LateLintPass<'_> for XorUsedAsPow { |
| 45 | + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { |
| 46 | + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); |
| 47 | + if let Some(Node::Item(parent_item)) = cx.tcx.hir().find(parent_id) { |
| 48 | + if let ItemKind::Enum(_, _) = parent_item.kind { |
| 49 | + return; |
| 50 | + } |
| 51 | + } |
| 52 | + |
35 | 53 | if_chain! {
|
36 |
| - if !in_external_macro(cx.sess, expr.span); |
| 54 | + if !in_external_macro(cx.sess(), expr.span); |
37 | 55 | if let ExprKind::Binary(op, left, right) = &expr.kind;
|
38 | 56 | if BinOpKind::BitXor == op.node;
|
39 |
| - if let ExprKind::Lit(lit) = &left.kind; |
40 |
| - if let LitKind::Int(lhs, _) = lit.kind; |
41 |
| - if let ExprKind::Lit(lit) = &right.kind; |
42 |
| - if let LitKind::Int(rhs, _) = lit.kind; |
| 57 | + if let ExprKind::Lit(lhs) = &left.kind; |
| 58 | + if let Some((lhs_val, lhs_type)) = unwrap_dec_int_literal(cx, lhs); |
43 | 59 | then {
|
44 |
| - if lhs == 2 { |
45 |
| - if rhs == 8 || rhs == 16 || rhs == 32 || rhs == 64 || rhs == 128 { |
46 |
| - span_lint_and_sugg( |
47 |
| - cx, |
48 |
| - XOR_USED_AS_POW, |
49 |
| - expr.span, |
50 |
| - "it appears you are trying to get the maximum value of an integer, but `^` is not an exponentiation operator", |
51 |
| - "try", |
52 |
| - format!("std::u{}::MAX", rhs), |
53 |
| - Applicability::MaybeIncorrect, |
54 |
| - ) |
55 |
| - } else { |
56 |
| - span_lint_and_sugg( |
57 |
| - cx, |
58 |
| - XOR_USED_AS_POW, |
59 |
| - expr.span, |
60 |
| - "it appears you are trying to get a power of two, but `^` is not an exponentiation operator", |
61 |
| - "use a bitshift instead", |
62 |
| - format!("1 << {}", rhs), |
63 |
| - Applicability::MaybeIncorrect, |
64 |
| - ) |
| 60 | + match &right.kind { |
| 61 | + ExprKind::Lit(rhs) => { |
| 62 | + if let Some((rhs_val, _)) = unwrap_dec_int_literal(cx, rhs) { |
| 63 | + report_with_lit(cx, lhs_val, rhs_val, expr.span); |
| 64 | + } |
65 | 65 | }
|
66 |
| - } else { |
67 |
| - span_lint_and_help( |
68 |
| - cx, |
69 |
| - XOR_USED_AS_POW, |
70 |
| - expr.span, |
71 |
| - "`^` is not an exponentiation operator but appears to have been used as one", |
72 |
| - None, |
73 |
| - "did you mean to use .pow()?" |
74 |
| - ) |
| 66 | + ExprKind::Path(qpath) => { |
| 67 | + match qpath_res(cx, qpath, right.hir_id) { |
| 68 | + Res::Local(hir_id) => { |
| 69 | + if_chain! { |
| 70 | + let node = cx.tcx.hir().get(hir_id); |
| 71 | + if let Node::Binding(pat) = node; |
| 72 | + if let PatKind::Binding(bind_ann, ..) = pat.kind; |
| 73 | + if !matches!(bind_ann, BindingAnnotation::RefMut | |
| 74 | + BindingAnnotation::Mutable); |
| 75 | + let parent_node = cx.tcx.hir().get_parent_node(hir_id); |
| 76 | + if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); |
| 77 | + if let Some(init) = parent_let_expr.init; |
| 78 | + then { |
| 79 | + match init.kind { |
| 80 | + // immutable bindings that are initialized with literal |
| 81 | + ExprKind::Lit(..) => report_with_ident(cx, lhs_val, qpath, expr.span), |
| 82 | + // immutable bindings that are initialized with constant |
| 83 | + ExprKind::Path(ref path) => { |
| 84 | + let res = qpath_res(cx, path, init.hir_id); |
| 85 | + if let Res::Def(DefKind::Const, ..) = res { |
| 86 | + report_with_ident(cx, lhs_val, qpath, expr.span); |
| 87 | + } |
| 88 | + } |
| 89 | + _ => {}, |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + }, |
| 94 | + // constant |
| 95 | + Res::Def(DefKind::Const, ..) => report_with_ident(cx, lhs_val, qpath, expr.span), |
| 96 | + _ => {}, |
| 97 | + } |
| 98 | + } |
| 99 | + _ => {} |
75 | 100 | }
|
76 | 101 | }
|
77 | 102 | }
|
78 | 103 | }
|
79 | 104 | }
|
| 105 | + |
| 106 | +fn unwrap_dec_int_literal(cx: &LateContext<'_>, lit: &Lit) -> Option<(u128, LitIntType)> { |
| 107 | + if_chain! { |
| 108 | + if let LitKind::Int(val, val_type) = lit.node; |
| 109 | + if let Some(snippet) = snippet_opt(cx, lit.span); |
| 110 | + if let Some(decoded) = NumericLiteral::from_lit_kind(&snippet, &lit.node); |
| 111 | + if decoded.is_decimal(); |
| 112 | + then { |
| 113 | + return Some((val, val_type)); |
| 114 | + } |
| 115 | + else { |
| 116 | + return None; |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +fn report_with_ident(cx: &LateContext<'_>, lhs: u128, rhs: &QPath<'_>, span: Span) { |
| 122 | + match lhs { |
| 123 | + 2 => { |
| 124 | + let ident = last_path_segment(rhs).ident.name.to_ident_string(); |
| 125 | + report_pow_of_two(cx, format!("1 << {}", ident), span); |
| 126 | + }, |
| 127 | + 10 => report_pow_of_ten(cx, span), |
| 128 | + _ => {}, |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +fn report_with_lit(cx: &LateContext<'_>, lhs: u128, rhs: u128, span: Span) { |
| 133 | + if rhs > 127 { |
| 134 | + return; |
| 135 | + } |
| 136 | + match lhs { |
| 137 | + 2 => { |
| 138 | + if rhs == 0 { |
| 139 | + report_pow_of_two(cx, format!("1"), span); |
| 140 | + return; |
| 141 | + } |
| 142 | + |
| 143 | + let lhs_str = if rhs <= 31 { |
| 144 | + "1_u32" |
| 145 | + } else if rhs <= 63 { |
| 146 | + "1_u64" |
| 147 | + } else { |
| 148 | + "1_u127" |
| 149 | + }; |
| 150 | + |
| 151 | + report_pow_of_two(cx, format!("{} << {}", lhs_str, rhs), span); |
| 152 | + }, |
| 153 | + 10 => report_pow_of_ten(cx, span), |
| 154 | + _ => {}, |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +fn report_pow_of_two(cx: &LateContext<'_>, sugg: String, span: Span) { |
| 159 | + span_lint_and_sugg( |
| 160 | + cx, |
| 161 | + XOR_USED_AS_POW, |
| 162 | + span, |
| 163 | + "it appears you are trying to get a power of two, but `^` is not an exponentiation operator", |
| 164 | + "use a bitshift or constant instead", |
| 165 | + sugg, |
| 166 | + Applicability::MaybeIncorrect, |
| 167 | + ) |
| 168 | +} |
| 169 | + |
| 170 | +fn report_pow_of_ten(cx: &LateContext<'_>, span: Span) { |
| 171 | + span_lint_and_help( |
| 172 | + cx, |
| 173 | + XOR_USED_AS_POW, |
| 174 | + span, |
| 175 | + "`^` is not an exponentiation operator but appears to have been used as one", |
| 176 | + None, |
| 177 | + "did you mean to use .pow()?", |
| 178 | + ) |
| 179 | +} |
0 commit comments