1+ use clippy_utils:: consts:: { ConstEvalCtxt , FullInt } ;
12use clippy_utils:: diagnostics:: span_lint_and_then;
23use clippy_utils:: sugg:: Sugg ;
34use clippy_utils:: visitors:: { Descend , for_each_expr_without_closures} ;
@@ -55,6 +56,7 @@ impl LateLintPass<'_> for ManualCheckedDiv {
5556 && is_integer ( cx, divisor)
5657 && let Some ( block) = branch_block ( then, r#else, branch)
5758 {
59+ let divisor_excludes_minus_one = excludes_minus_one ( cx, cond, divisor) ;
5860 let mut eq = SpanlessEq :: new ( cx) . deny_side_effects ( ) . paths_by_resolution ( ) ;
5961 if !eq. eq_expr ( divisor, divisor) {
6062 return ;
@@ -70,6 +72,14 @@ impl LateLintPass<'_> for ManualCheckedDiv {
7072 && eq. eq_expr ( rhs, divisor)
7173 && is_integer ( cx, lhs)
7274 {
75+ let lhs_ty = cx. typeck_results ( ) . expr_ty ( lhs) ;
76+ if let ty:: Int ( int_ty) = lhs_ty. peel_refs ( ) . kind ( )
77+ && !divisor_excludes_minus_one
78+ && min_and_minus_one ( cx, lhs, rhs, * int_ty)
79+ {
80+ return ControlFlow :: Break ( ( ) ) ;
81+ }
82+
7383 match first_use {
7484 None => first_use = Some ( UseKind :: Division ) ,
7585 Some ( UseKind :: Other ) => return ControlFlow :: Break ( ( ) ) ,
@@ -78,7 +88,6 @@ impl LateLintPass<'_> for ManualCheckedDiv {
7888
7989 let lhs_snip = Sugg :: hir_with_applicability ( cx, lhs, "_" , & mut applicability) ;
8090 let rhs_snip = Sugg :: hir_with_applicability ( cx, rhs, "_" , & mut applicability) ;
81- let lhs_ty = cx. typeck_results ( ) . expr_ty ( lhs) ;
8291 let lhs_sugg = lhs_snip. maybe_paren ( ) . to_string ( ) ;
8392 let type_suffix = if matches ! (
8493 lhs. kind,
@@ -174,6 +183,49 @@ fn is_zero(expr: &Expr<'_>) -> bool {
174183 is_integer_literal ( expr, 0 )
175184}
176185
186+ fn min_and_minus_one ( cx : & LateContext < ' _ > , lhs : & Expr < ' _ > , rhs : & Expr < ' _ > , int_ty : ty:: IntTy ) -> bool {
187+ let lhs_val = int_value ( cx, lhs) ;
188+ let rhs_val = int_value ( cx, rhs) ;
189+
190+ let lhs_maybe_min = lhs_val. is_none_or ( |val| match val {
191+ FullInt :: S ( signed) => signed == int_min_value ( int_ty, cx) ,
192+ FullInt :: U ( _) => false ,
193+ } ) ;
194+ let rhs_maybe_minus_one = rhs_val. is_none_or ( |val| matches ! ( val, FullInt :: S ( -1 ) ) ) ;
195+
196+ lhs_maybe_min && rhs_maybe_minus_one
197+ }
198+
199+ fn int_min_value ( int_ty : ty:: IntTy , cx : & LateContext < ' _ > ) -> i128 {
200+ let bits = match int_ty {
201+ ty:: IntTy :: Isize => cx. tcx . data_layout . pointer_size ( ) . bits ( ) ,
202+ ty:: IntTy :: I8 => 8 ,
203+ ty:: IntTy :: I16 => 16 ,
204+ ty:: IntTy :: I32 => 32 ,
205+ ty:: IntTy :: I64 => 64 ,
206+ ty:: IntTy :: I128 => 128 ,
207+ } ;
208+ -( 1_i128 << ( bits - 1 ) )
209+ }
210+
211+ fn int_value ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> Option < FullInt > {
212+ let ecx = ConstEvalCtxt :: new ( cx) ;
213+ ecx. eval_full_int ( expr, expr. span . ctxt ( ) )
214+ }
215+
216+ fn excludes_minus_one ( cx : & LateContext < ' _ > , cond : & Expr < ' _ > , divisor : & Expr < ' _ > ) -> bool {
217+ let ExprKind :: Binary ( binop, lhs, rhs) = cond. kind else {
218+ return false ;
219+ } ;
220+
221+ let mut eq = SpanlessEq :: new ( cx) . deny_side_effects ( ) . paths_by_resolution ( ) ;
222+ match binop. node {
223+ BinOpKind :: Gt if is_zero ( rhs) && eq. eq_expr ( lhs, divisor) => true ,
224+ BinOpKind :: Lt if is_zero ( lhs) && eq. eq_expr ( rhs, divisor) => true ,
225+ _ => false ,
226+ }
227+ }
228+
177229fn is_integer ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
178230 matches ! (
179231 cx. typeck_results( ) . expr_ty( expr) . peel_refs( ) . kind( ) ,
0 commit comments