]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/int_plus_one.rs
Replace `#[allow]` with `#[expect]` in Clippy
[rust.git] / clippy_lints / src / int_plus_one.rs
1 //! lint on blocks unnecessarily using >= with a + 1 or - 1
2
3 use clippy_utils::diagnostics::span_lint_and_sugg;
4 use clippy_utils::source::snippet_opt;
5 use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind};
6 use rustc_errors::Applicability;
7 use rustc_lint::{EarlyContext, EarlyLintPass};
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block
13     ///
14     /// ### Why is this bad?
15     /// Readability -- better to use `> y` instead of `>= y + 1`.
16     ///
17     /// ### Example
18     /// ```rust
19     /// # let x = 1;
20     /// # let y = 1;
21     /// if x >= y + 1 {}
22     /// ```
23     ///
24     /// Could be written as:
25     ///
26     /// ```rust
27     /// # let x = 1;
28     /// # let y = 1;
29     /// if x > y {}
30     /// ```
31     #[clippy::version = "pre 1.29.0"]
32     pub INT_PLUS_ONE,
33     complexity,
34     "instead of using `x >= y + 1`, use `x > y`"
35 }
36
37 declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]);
38
39 // cases:
40 // BinOpKind::Ge
41 // x >= y + 1
42 // x - 1 >= y
43 //
44 // BinOpKind::Le
45 // x + 1 <= y
46 // x <= y - 1
47
48 #[derive(Copy, Clone)]
49 enum Side {
50     Lhs,
51     Rhs,
52 }
53
54 impl IntPlusOne {
55     #[expect(clippy::cast_sign_loss)]
56     fn check_lit(lit: &Lit, target_value: i128) -> bool {
57         if let LitKind::Int(value, ..) = lit.kind {
58             return value == (target_value as u128);
59         }
60         false
61     }
62
63     fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option<String> {
64         match (binop, &lhs.kind, &rhs.kind) {
65             // case where `x - 1 >= ...` or `-1 + x >= ...`
66             (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
67                 match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
68                     // `-1 + x`
69                     (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
70                         Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
71                     },
72                     // `x - 1`
73                     (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
74                         Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
75                     },
76                     _ => None,
77                 }
78             },
79             // case where `... >= y + 1` or `... >= 1 + y`
80             (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs))
81                 if rhskind.node == BinOpKind::Add =>
82             {
83                 match (&rhslhs.kind, &rhsrhs.kind) {
84                     // `y + 1` and `1 + y`
85                     (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
86                         Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
87                     },
88                     (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
89                         Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
90                     },
91                     _ => None,
92                 }
93             },
94             // case where `x + 1 <= ...` or `1 + x <= ...`
95             (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _)
96                 if lhskind.node == BinOpKind::Add =>
97             {
98                 match (&lhslhs.kind, &lhsrhs.kind) {
99                     // `1 + x` and `x + 1`
100                     (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
101                         Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
102                     },
103                     (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
104                         Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
105                     },
106                     _ => None,
107                 }
108             },
109             // case where `... >= y - 1` or `... >= -1 + y`
110             (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
111                 match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
112                     // `-1 + y`
113                     (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
114                         Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
115                     },
116                     // `y - 1`
117                     (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
118                         Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
119                     },
120                     _ => None,
121                 }
122             },
123             _ => None,
124         }
125     }
126
127     fn generate_recommendation(
128         cx: &EarlyContext<'_>,
129         binop: BinOpKind,
130         node: &Expr,
131         other_side: &Expr,
132         side: Side,
133     ) -> Option<String> {
134         let binop_string = match binop {
135             BinOpKind::Ge => ">",
136             BinOpKind::Le => "<",
137             _ => return None,
138         };
139         if let Some(snippet) = snippet_opt(cx, node.span) {
140             if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) {
141                 let rec = match side {
142                     Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)),
143                     Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)),
144                 };
145                 return rec;
146             }
147         }
148         None
149     }
150
151     fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
152         span_lint_and_sugg(
153             cx,
154             INT_PLUS_ONE,
155             block.span,
156             "unnecessary `>= y + 1` or `x - 1 >=`",
157             "change it to",
158             recommendation,
159             Applicability::MachineApplicable, // snippet
160         );
161     }
162 }
163
164 impl EarlyLintPass for IntPlusOne {
165     fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
166         if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
167             if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
168                 Self::emit_warning(cx, item, rec);
169             }
170         }
171     }
172 }