1- use crate :: utils:: { higher, in_macro, span_lint_and_sugg} ;
1+ use crate :: utils:: { higher, in_macro, match_qpath , span_lint_and_sugg, SpanlessEq } ;
22use if_chain:: if_chain;
33use rustc_ast:: ast:: LitKind ;
44use rustc_errors:: Applicability ;
@@ -9,17 +9,17 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
99declare_clippy_lint ! {
1010 /// **What it does:** Checks for implicit saturating subtraction.
1111 ///
12- /// **Why is this bad?** Simplicity and readability. Instead we can easily use an inbuilt function.
12+ /// **Why is this bad?** Simplicity and readability. Instead we can easily use an builtin function.
1313 ///
1414 /// **Known problems:** None.
1515 ///
1616 /// **Example:**
1717 ///
1818 /// ```rust
19- /// let end = 10;
20- /// let start = 5;
19+ /// let end: u32 = 10;
20+ /// let start: u32 = 5;
2121 ///
22- /// let mut i = end - start;
22+ /// let mut i: u32 = end - start;
2323 ///
2424 /// // Bad
2525 /// if i != 0 {
@@ -28,13 +28,13 @@ declare_clippy_lint! {
2828 /// ```
2929 /// Use instead:
3030 /// ```rust
31- /// let end = 10;
32- /// let start = 5;
31+ /// let end: u32 = 10;
32+ /// let start: u32 = 5;
3333 ///
34- /// let mut i = end - start;
34+ /// let mut i: u32 = end - start;
3535 ///
3636 /// // Good
37- /// i.saturating_sub(1);
37+ /// i = i .saturating_sub(1);
3838 /// ```
3939 pub IMPLICIT_SATURATING_SUB ,
4040 pedantic,
@@ -50,41 +50,124 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitSaturatingSub {
5050 }
5151 if_chain ! {
5252 if let Some ( ( ref cond, ref then, None ) ) = higher:: if_block( & expr) ;
53+
5354 // Check if the conditional expression is a binary operation
54- if let ExprKind :: Binary ( ref op, ref left, ref right) = cond. kind;
55- // Ensure that the binary operator is > or !=
56- if BinOpKind :: Ne == op. node || BinOpKind :: Gt == op. node;
57- if let ExprKind :: Path ( ref cond_path) = left. kind;
58- // Get the literal on the right hand side
59- if let ExprKind :: Lit ( ref lit) = right. kind;
60- if let LitKind :: Int ( 0 , _) = lit. node;
55+ if let ExprKind :: Binary ( ref cond_op, ref cond_left, ref cond_right) = cond. kind;
56+
57+ // Ensure that the binary operator is >, != and <
58+ if BinOpKind :: Ne == cond_op. node || BinOpKind :: Gt == cond_op. node || BinOpKind :: Lt == cond_op. node;
59+
6160 // Check if the true condition block has only one statement
6261 if let ExprKind :: Block ( ref block, _) = then. kind;
63- if block. stmts. len( ) == 1 ;
62+ if block. stmts. len( ) == 1 && block. expr. is_none( ) ;
63+
6464 // Check if assign operation is done
6565 if let StmtKind :: Semi ( ref e) = block. stmts[ 0 ] . kind;
66- if let ExprKind :: AssignOp ( ref op1, ref target, ref value) = e. kind;
67- if BinOpKind :: Sub == op1. node;
66+ if let Some ( target) = subtracts_one( cx, e) ;
67+
68+ // Extracting out the variable name
6869 if let ExprKind :: Path ( ref assign_path) = target. kind;
69- // Check if the variable in the condition and assignment statement are the same
70- if let ( QPath :: Resolved ( _, ref cres_path) , QPath :: Resolved ( _, ref ares_path) ) = ( cond_path, assign_path) ;
71- if cres_path. res == ares_path. res;
72- if let ExprKind :: Lit ( ref lit1) = value. kind;
73- if let LitKind :: Int ( assign_lit, _) = lit1. node;
70+ if let QPath :: Resolved ( _, ref ares_path) = assign_path;
71+
7472 then {
73+ // Handle symmetric conditions in the if statement
74+ let ( cond_var, cond_num_val) = if SpanlessEq :: new( cx) . eq_expr( cond_left, target) {
75+ if BinOpKind :: Gt == cond_op. node || BinOpKind :: Ne == cond_op. node {
76+ ( cond_left, cond_right)
77+ } else {
78+ return ;
79+ }
80+ } else if SpanlessEq :: new( cx) . eq_expr( cond_right, target) {
81+ if BinOpKind :: Lt == cond_op. node || BinOpKind :: Ne == cond_op. node {
82+ ( cond_right, cond_left)
83+ } else {
84+ return ;
85+ }
86+ } else {
87+ return ;
88+ } ;
89+
90+ // Check if the variable in the condition statement is an integer
91+ if !cx. tables. expr_ty( cond_var) . is_integral( ) {
92+ return ;
93+ }
94+
7595 // Get the variable name
7696 let var_name = ares_path. segments[ 0 ] . ident. name. as_str( ) ;
77- let applicability = Applicability :: MaybeIncorrect ;
78- span_lint_and_sugg(
79- cx,
80- IMPLICIT_SATURATING_SUB ,
81- expr. span,
82- "Implicitly performing saturating subtraction" ,
83- "try" ,
84- format!( "{}.saturating_sub({});" , var_name, assign_lit. to_string( ) ) ,
85- applicability
86- ) ;
97+ const INT_TYPES : [ & str ; 5 ] = [ "i8" , "i16" , "i32" , "i64" , "i128" ] ;
98+
99+ match cond_num_val. kind {
100+ ExprKind :: Lit ( ref cond_lit) => {
101+ // Check if the constant is zero
102+ if let LitKind :: Int ( 0 , _) = cond_lit. node {
103+ if cx. tables. expr_ty( cond_left) . is_signed( ) {
104+ } else {
105+ print_lint_and_sugg( cx, & var_name, expr) ;
106+ } ;
107+ }
108+ } ,
109+ ExprKind :: Path ( ref cond_num_path) => {
110+ if INT_TYPES . iter( ) . any( |int_type| match_qpath( cond_num_path, & [ int_type, "MIN" ] ) ) {
111+ print_lint_and_sugg( cx, & var_name, expr) ;
112+ } ;
113+ } ,
114+ ExprKind :: Call ( ref func, _) => {
115+ if let ExprKind :: Path ( ref cond_num_path) = func. kind {
116+ if INT_TYPES . iter( ) . any( |int_type| match_qpath( cond_num_path, & [ int_type, "min_value" ] ) ) {
117+ print_lint_and_sugg( cx, & var_name, expr) ;
118+ }
119+ } ;
120+ } ,
121+ _ => ( ) ,
122+ }
87123 }
88124 }
89125 }
90126}
127+
128+ fn subtracts_one < ' a > ( cx : & LateContext < ' _ , ' _ > , expr : & Expr < ' a > ) -> Option < & ' a Expr < ' a > > {
129+ match expr. kind {
130+ ExprKind :: AssignOp ( ref op1, ref target, ref value) => {
131+ if_chain ! {
132+ if BinOpKind :: Sub == op1. node;
133+ // Check if literal being subtracted is one
134+ if let ExprKind :: Lit ( ref lit1) = value. kind;
135+ if let LitKind :: Int ( 1 , _) = lit1. node;
136+ then {
137+ Some ( target)
138+ } else {
139+ None
140+ }
141+ }
142+ } ,
143+ ExprKind :: Assign ( ref target, ref value, _) => {
144+ if_chain ! {
145+ if let ExprKind :: Binary ( ref op1, ref left1, ref right1) = value. kind;
146+ if BinOpKind :: Sub == op1. node;
147+
148+ if SpanlessEq :: new( cx) . eq_expr( left1, target) ;
149+
150+ if let ExprKind :: Lit ( ref lit1) = right1. kind;
151+ if let LitKind :: Int ( 1 , _) = lit1. node;
152+ then {
153+ Some ( target)
154+ } else {
155+ None
156+ }
157+ }
158+ } ,
159+ _ => None ,
160+ }
161+ }
162+
163+ fn print_lint_and_sugg ( cx : & LateContext < ' _ , ' _ > , var_name : & str , expr : & Expr < ' _ > ) {
164+ span_lint_and_sugg (
165+ cx,
166+ IMPLICIT_SATURATING_SUB ,
167+ expr. span ,
168+ "Implicitly performing saturating subtraction" ,
169+ "try" ,
170+ format ! ( "{} = {}.saturating_sub({});" , var_name, var_name, 1 . to_string( ) ) ,
171+ Applicability :: MachineApplicable ,
172+ ) ;
173+ }
0 commit comments