]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/manual_rem_euclid.rs
Rollup merge of #98507 - xFrednet:rfc-2383-manual-expectation-magic, r=wesleywiser
[rust.git] / 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::source::snippet_with_applicability;
4 use clippy_utils::{in_constant, meets_msrv, msrvs, path_to_local};
5 use rustc_errors::Applicability;
6 use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
7 use rustc_lint::{LateContext, LateLintPass, LintContext};
8 use rustc_middle::lint::in_external_macro;
9 use rustc_semver::RustcVersion;
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.63.0"]
31     pub MANUAL_REM_EUCLID,
32     complexity,
33     "manually reimplementing `rem_euclid`"
34 }
35
36 pub struct ManualRemEuclid {
37     msrv: Option<RustcVersion>,
38 }
39
40 impl ManualRemEuclid {
41     #[must_use]
42     pub fn new(msrv: Option<RustcVersion>) -> 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 !meets_msrv(self.msrv, msrvs::REM_EUCLID) {
52             return;
53         }
54
55         if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, 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                 // Apply only to params or locals with annotated types
76                 match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
77                     Some(Node::Param(..)) => (),
78                     Some(Node::Local(local)) => {
79                         let Some(ty) = local.ty else { return };
80                         if matches!(ty.kind, TyKind::Infer) {
81                             return;
82                         }
83                     }
84                     _ => return,
85                 };
86
87                 let mut app = Applicability::MachineApplicable;
88                 let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app);
89                 span_lint_and_sugg(
90                     cx,
91                     MANUAL_REM_EUCLID,
92                     expr.span,
93                     "manual `rem_euclid` implementation",
94                     "consider using",
95                     format!("{rem_of}.rem_euclid({const1})"),
96                     app,
97                 );
98         }
99     }
100
101     extract_msrv_attr!(LateContext);
102 }
103
104 // Checks if either the left or right expressions can be an unsigned int constant and returns that
105 // constant along with the other expression unchanged if so
106 fn check_for_either_unsigned_int_constant<'a>(
107     cx: &'a LateContext<'_>,
108     left: &'a Expr<'_>,
109     right: &'a Expr<'_>,
110 ) -> Option<(u128, &'a Expr<'a>)> {
111     check_for_unsigned_int_constant(cx, left)
112         .map(|int_const| (int_const, right))
113         .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
114 }
115
116 fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
117     let Some(int_const) = constant_full_int(cx, cx.typeck_results(), expr) else { return None };
118     match int_const {
119         FullInt::S(s) => s.try_into().ok(),
120         FullInt::U(u) => Some(u),
121     }
122 }