1
1
use clippy_utils:: consts:: { constant, Constant } ;
2
2
use clippy_utils:: diagnostics:: span_lint;
3
- use clippy_utils:: { method_chain_args, sext} ;
4
- use rustc_hir:: { Expr , ExprKind } ;
3
+ use clippy_utils:: { clip , method_chain_args, sext} ;
4
+ use rustc_hir:: { BinOpKind , Expr , ExprKind } ;
5
5
use rustc_lint:: LateContext ;
6
- use rustc_middle:: ty:: { self , Ty } ;
6
+ use rustc_middle:: ty:: { self , Ty , UintTy } ;
7
7
8
8
use super :: CAST_SIGN_LOSS ;
9
9
10
+ const METHODS_RET_POSITIVE : & [ & str ] = & [ "abs" , "checked_abs" , "rem_euclid" , "checked_rem_euclid" ] ;
11
+
10
12
pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , cast_op : & Expr < ' _ > , cast_from : Ty < ' _ > , cast_to : Ty < ' _ > ) {
11
13
if should_lint ( cx, cast_op, cast_from, cast_to) {
12
14
span_lint (
@@ -18,44 +20,150 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
18
20
}
19
21
}
20
22
23
+ /// (Remove after final code review)
24
+ /// When does a mathmatic expression always resulting a positive value?
25
+ /// 1. Even number of same variables: `x*x`
26
+ /// 2. Odd number of same variables but, ignore the ones that a guarenteed to be positive, there are
27
+ /// even number of the vairables left: `x*x*x.abs()*x.abs()*x*x`
28
+ /// 3. Any number of any variables, but all them are guarenteed to be positive:
29
+ /// `x.abs()*y.abs()*z.abs()`
30
+ /// 4. Any number of any variables, ignoreing the guarenteed ones, there are even number of same
31
+ /// variables left: `x.abs()*y.abs()*x*x*y*y`
32
+ /// 5. Any number that calls `pow*` with even number: `x.pow(2)`
33
+ ///
34
+ /// TODO:
35
+ /// 1. ~A function called `expr_is_always_positive`~
36
+ /// 2. ~A function to peel binary op (Skip ops other than Mul, Div)~
37
+ /// 3. ~When peeling binary op, collect each expr~
38
+ /// 4. ~For each expr, check if they are always positive, then count them if not~
39
+ /// 5. ~Detect above cases and call it for the day~
21
40
fn should_lint ( cx : & LateContext < ' _ > , cast_op : & Expr < ' _ > , cast_from : Ty < ' _ > , cast_to : Ty < ' _ > ) -> bool {
22
41
match ( cast_from. is_integral ( ) , cast_to. is_integral ( ) ) {
23
42
( true , true ) => {
24
43
if !cast_from. is_signed ( ) || cast_to. is_signed ( ) {
25
44
return false ;
26
45
}
27
46
28
- // Don't lint for positive constants.
29
- let const_val = constant ( cx, cx. typeck_results ( ) , cast_op) ;
30
- if let Some ( Constant :: Int ( n) ) = const_val
31
- && let ty:: Int ( ity) = * cast_from. kind ( )
32
- && sext ( cx. tcx , n, ity) >= 0
33
- {
47
+ // Don't lint if `cast_op` is known to be positive.
48
+ if let Sign :: ZeroOrPositive = expr_sign ( cx, cast_op, cast_from) {
34
49
return false ;
35
50
}
36
51
37
- // Don't lint for the result of methods that always return non-negative values.
38
- if let ExprKind :: MethodCall ( path, ..) = cast_op. kind {
39
- let mut method_name = path. ident . name . as_str ( ) ;
40
- let allowed_methods = [ "abs" , "checked_abs" , "rem_euclid" , "checked_rem_euclid" ] ;
41
-
42
- if method_name == "unwrap"
43
- && let Some ( arglist) = method_chain_args ( cast_op, & [ "unwrap" ] )
44
- && let ExprKind :: MethodCall ( inner_path, ..) = & arglist[ 0 ] . 0 . kind
45
- {
46
- method_name = inner_path. ident . name . as_str ( ) ;
47
- }
48
-
49
- if allowed_methods. iter ( ) . any ( |& name| method_name == name) {
50
- return false ;
51
- }
52
+ let ( mut uncertain_count, mut negative_count) = ( 0 , 0 ) ;
53
+ // Peel off possible binary expressions, e.g. x * x * y => [x, x, y]
54
+ let Some ( exprs) = exprs_with_selected_binop_peeled ( cast_op) else {
55
+ // Assume cast sign lose if we cannot determine the sign of `cast_op`
56
+ return true ;
57
+ } ;
58
+ for expr in exprs {
59
+ let ty = cx. typeck_results ( ) . expr_ty ( expr) ;
60
+ match expr_sign ( cx, expr, ty) {
61
+ Sign :: Negative => negative_count += 1 ,
62
+ Sign :: Uncertain => uncertain_count += 1 ,
63
+ Sign :: ZeroOrPositive => ( ) ,
64
+ } ;
52
65
}
53
66
54
- true
67
+ // Lint if there are odd number of uncertain or negative results
68
+ uncertain_count % 2 == 1 || negative_count % 2 == 1
55
69
} ,
56
70
57
71
( false , true ) => !cast_to. is_signed ( ) ,
58
72
59
73
( _, _) => false ,
60
74
}
61
75
}
76
+
77
+ fn get_const_int_eval ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , ty : Ty < ' _ > ) -> Option < i128 > {
78
+ if let Constant :: Int ( n) = constant ( cx, cx. typeck_results ( ) , expr) ?
79
+ && let ty:: Int ( ity) = * ty. kind ( )
80
+ {
81
+ return Some ( sext ( cx. tcx , n, ity) ) ;
82
+ }
83
+ None
84
+ }
85
+
86
+ enum Sign {
87
+ ZeroOrPositive ,
88
+ Negative ,
89
+ Uncertain ,
90
+ }
91
+
92
+ fn expr_sign ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , ty : Ty < ' _ > ) -> Sign {
93
+ // Try evaluate this expr first to see if it's positive
94
+ if let Some ( val) = get_const_int_eval ( cx, expr, ty) {
95
+ return if val >= 0 { Sign :: ZeroOrPositive } else { Sign :: Negative } ;
96
+ }
97
+ // Calling on methods that always return non-negative values.
98
+ if let ExprKind :: MethodCall ( path, caller, args, ..) = expr. kind {
99
+ let mut method_name = path. ident . name . as_str ( ) ;
100
+
101
+ if method_name == "unwrap"
102
+ && let Some ( arglist) = method_chain_args ( expr, & [ "unwrap" ] )
103
+ && let ExprKind :: MethodCall ( inner_path, ..) = & arglist[ 0 ] . 0 . kind
104
+ {
105
+ method_name = inner_path. ident . name . as_str ( ) ;
106
+ }
107
+
108
+ if method_name == "pow"
109
+ && let [ arg] = args
110
+ {
111
+ return pow_call_result_sign ( cx, caller, arg) ;
112
+ } else if METHODS_RET_POSITIVE . iter ( ) . any ( |& name| method_name == name) {
113
+ return Sign :: ZeroOrPositive ;
114
+ }
115
+ }
116
+
117
+ Sign :: Uncertain
118
+ }
119
+
120
+ /// Return the sign of the `pow` call's result.
121
+ ///
122
+ /// If the caller is a positive number, the result is always positive,
123
+ /// If the `power_of` is a even number, the result is always positive as well,
124
+ /// Otherwise a [`Sign::Uncertain`] will be returned.
125
+ fn pow_call_result_sign ( cx : & LateContext < ' _ > , caller : & Expr < ' _ > , power_of : & Expr < ' _ > ) -> Sign {
126
+ let caller_ty = cx. typeck_results ( ) . expr_ty ( caller) ;
127
+ if let Some ( caller_val) = get_const_int_eval ( cx, caller, caller_ty)
128
+ && caller_val >= 0
129
+ {
130
+ return Sign :: ZeroOrPositive ;
131
+ }
132
+
133
+ if let Some ( Constant :: Int ( n) ) = constant ( cx, cx. typeck_results ( ) , power_of)
134
+ && clip ( cx. tcx , n, UintTy :: U32 ) % 2 == 0
135
+ {
136
+ return Sign :: ZeroOrPositive ;
137
+ }
138
+
139
+ Sign :: Uncertain
140
+ }
141
+
142
+ /// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
143
+ /// which the result could always be positive under certain condition.
144
+ ///
145
+ /// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will
146
+ /// return `None`
147
+ fn exprs_with_selected_binop_peeled < ' a > ( expr : & ' a Expr < ' _ > ) -> Option < Vec < & ' a Expr < ' a > > > {
148
+ #[ inline]
149
+ fn collect_operands < ' a > ( expr : & ' a Expr < ' a > , operands : & mut Vec < & ' a Expr < ' a > > ) -> Option < ( ) > {
150
+ match expr. kind {
151
+ ExprKind :: Binary ( op, lhs, rhs) => {
152
+ if matches ! ( op. node, BinOpKind :: Mul | BinOpKind :: Div | BinOpKind :: Rem ) {
153
+ collect_operands ( lhs, operands) ;
154
+ operands. push ( rhs) ;
155
+ } else {
156
+ // Things are complicated when there are other binary ops exist,
157
+ // abort checking by returning `None` for now.
158
+ return None ;
159
+ }
160
+ } ,
161
+ _ => operands. push ( expr) ,
162
+ }
163
+ Some ( ( ) )
164
+ }
165
+
166
+ let mut res = vec ! [ ] ;
167
+ collect_operands ( expr, & mut res) ?;
168
+ Some ( res)
169
+ }
0 commit comments