]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs
Rollup merge of #106499 - lyming2007:issue-105946-fix, r=estebank
[rust.git] / src / tools / clippy / clippy_lints / src / manual_rem_euclid.rs
1 use clippy_utils::consts::{constant_full_int, FullInt};
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::msrvs::{self, Msrv};
4 use clippy_utils::source::snippet_with_applicability;
5 use clippy_utils::{in_constant, path_to_local};
6 use rustc_errors::Applicability;
7 use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
8 use rustc_lint::{LateContext, LateLintPass, LintContext};
9 use rustc_middle::lint::in_external_macro;
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
11
12 declare_clippy_lint! {
13     /// ### What it does
14     /// Checks for an expression like `((x % 4) + 4) % 4` which is a common manual reimplementation
15     /// of `x.rem_euclid(4)`.
16     ///
17     /// ### Why is this bad?
18     /// It's simpler and more readable.
19     ///
20     /// ### Example
21     /// ```rust
22     /// let x: i32 = 24;
23     /// let rem = ((x % 4) + 4) % 4;
24     /// ```
25     /// Use instead:
26     /// ```rust
27     /// let x: i32 = 24;
28     /// let rem = x.rem_euclid(4);
29     /// ```
30     #[clippy::version = "1.64.0"]
31     pub MANUAL_REM_EUCLID,
32     complexity,
33     "manually reimplementing `rem_euclid`"
34 }
35
36 pub struct ManualRemEuclid {
37     msrv: Msrv,
38 }
39
40 impl ManualRemEuclid {
41     #[must_use]
42     pub fn new(msrv: Msrv) -> Self {
43         Self { msrv }
44     }
45 }
46
47 impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
48
49 impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
50     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
51         if !self.msrv.meets(msrvs::REM_EUCLID) {
52             return;
53         }
54
55         if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::REM_EUCLID_CONST) {
56             return;
57         }
58
59         if in_external_macro(cx.sess(), expr.span) {
60             return;
61         }
62
63         if let ExprKind::Binary(op1, expr1, right) = expr.kind
64             && op1.node == BinOpKind::Rem
65             && let Some(const1) = check_for_unsigned_int_constant(cx, right)
66             && let ExprKind::Binary(op2, left, right) = expr1.kind
67             && op2.node == BinOpKind::Add
68             && let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right)
69             && let ExprKind::Binary(op3, expr3, right) = expr2.kind
70             && op3.node == BinOpKind::Rem
71             && let Some(const3) = check_for_unsigned_int_constant(cx, right)
72             // Also ensures the const is nonzero since zero can't be a divisor
73             && const1 == const2 && const2 == const3
74             && let Some(hir_id) = path_to_local(expr3)
75             && let Some(Node::Pat(_)) = cx.tcx.hir().find(hir_id) {
76                 // Apply only to params or locals with annotated types
77                 match cx.tcx.hir().find_parent(hir_id) {
78                     Some(Node::Param(..)) => (),
79                     Some(Node::Local(local)) => {
80                         let Some(ty) = local.ty else { return };
81                         if matches!(ty.kind, TyKind::Infer) {
82                             return;
83                         }
84                     }
85                     _ => return,
86                 };
87
88                 let mut app = Applicability::MachineApplicable;
89                 let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app);
90                 span_lint_and_sugg(
91                     cx,
92                     MANUAL_REM_EUCLID,
93                     expr.span,
94                     "manual `rem_euclid` implementation",
95                     "consider using",
96                     format!("{rem_of}.rem_euclid({const1})"),
97                     app,
98                 );
99         }
100     }
101
102     extract_msrv_attr!(LateContext);
103 }
104
105 // Checks if either the left or right expressions can be an unsigned int constant and returns that
106 // constant along with the other expression unchanged if so
107 fn check_for_either_unsigned_int_constant<'a>(
108     cx: &'a LateContext<'_>,
109     left: &'a Expr<'_>,
110     right: &'a Expr<'_>,
111 ) -> Option<(u128, &'a Expr<'a>)> {
112     check_for_unsigned_int_constant(cx, left)
113         .map(|int_const| (int_const, right))
114         .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
115 }
116
117 fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
118     let Some(int_const) = constant_full_int(cx, cx.typeck_results(), expr) else { return None };
119     match int_const {
120         FullInt::S(s) => s.try_into().ok(),
121         FullInt::U(u) => Some(u),
122     }
123 }