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 } ;
2
2
use if_chain:: if_chain;
3
3
use rustc_ast:: ast:: LitKind ;
4
4
use rustc_errors:: Applicability ;
@@ -9,17 +9,17 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
9
9
declare_clippy_lint ! {
10
10
/// **What it does:** Checks for implicit saturating subtraction.
11
11
///
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.
13
13
///
14
14
/// **Known problems:** None.
15
15
///
16
16
/// **Example:**
17
17
///
18
18
/// ```rust
19
- /// let end = 10;
20
- /// let start = 5;
19
+ /// let end: u32 = 10;
20
+ /// let start: u32 = 5;
21
21
///
22
- /// let mut i = end - start;
22
+ /// let mut i: u32 = end - start;
23
23
///
24
24
/// // Bad
25
25
/// if i != 0 {
@@ -28,13 +28,13 @@ declare_clippy_lint! {
28
28
/// ```
29
29
/// Use instead:
30
30
/// ```rust
31
- /// let end = 10;
32
- /// let start = 5;
31
+ /// let end: u32 = 10;
32
+ /// let start: u32 = 5;
33
33
///
34
- /// let mut i = end - start;
34
+ /// let mut i: u32 = end - start;
35
35
///
36
36
/// // Good
37
- /// i.saturating_sub(1);
37
+ /// i = i .saturating_sub(1);
38
38
/// ```
39
39
pub IMPLICIT_SATURATING_SUB ,
40
40
pedantic,
@@ -50,41 +50,124 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitSaturatingSub {
50
50
}
51
51
if_chain ! {
52
52
if let Some ( ( ref cond, ref then, None ) ) = higher:: if_block( & expr) ;
53
+
53
54
// 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
+
61
60
// Check if the true condition block has only one statement
62
61
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
+
64
64
// Check if assign operation is done
65
65
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
68
69
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
+
74
72
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
+
75
95
// Get the variable name
76
96
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
+ }
87
123
}
88
124
}
89
125
}
90
126
}
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