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 (
@@ -25,37 +27,126 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
25
27
return false ;
26
28
}
27
29
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
- {
30
+ // Don't lint if `cast_op` is known to be positive.
31
+ if let Sign :: ZeroOrPositive = expr_sign ( cx, cast_op, cast_from) {
34
32
return false ;
35
33
}
36
34
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
- }
35
+ let ( mut uncertain_count, mut negative_count) = ( 0 , 0 ) ;
36
+ // Peel off possible binary expressions, e.g. x * x * y => [x, x, y]
37
+ let Some ( exprs) = exprs_with_selected_binop_peeled ( cast_op) else {
38
+ // Assume cast sign lose if we cannot determine the sign of `cast_op`
39
+ return true ;
40
+ } ;
41
+ for expr in exprs {
42
+ let ty = cx. typeck_results ( ) . expr_ty ( expr) ;
43
+ match expr_sign ( cx, expr, ty) {
44
+ Sign :: Negative => negative_count += 1 ,
45
+ Sign :: Uncertain => uncertain_count += 1 ,
46
+ Sign :: ZeroOrPositive => ( ) ,
47
+ } ;
52
48
}
53
49
54
- true
50
+ // Lint if there are odd number of uncertain or negative results
51
+ uncertain_count % 2 == 1 || negative_count % 2 == 1
55
52
} ,
56
53
57
54
( false , true ) => !cast_to. is_signed ( ) ,
58
55
59
56
( _, _) => false ,
60
57
}
61
58
}
59
+
60
+ fn get_const_int_eval ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , ty : Ty < ' _ > ) -> Option < i128 > {
61
+ if let Constant :: Int ( n) = constant ( cx, cx. typeck_results ( ) , expr) ?
62
+ && let ty:: Int ( ity) = * ty. kind ( )
63
+ {
64
+ return Some ( sext ( cx. tcx , n, ity) ) ;
65
+ }
66
+ None
67
+ }
68
+
69
+ enum Sign {
70
+ ZeroOrPositive ,
71
+ Negative ,
72
+ Uncertain ,
73
+ }
74
+
75
+ fn expr_sign ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , ty : Ty < ' _ > ) -> Sign {
76
+ // Try evaluate this expr first to see if it's positive
77
+ if let Some ( val) = get_const_int_eval ( cx, expr, ty) {
78
+ return if val >= 0 { Sign :: ZeroOrPositive } else { Sign :: Negative } ;
79
+ }
80
+ // Calling on methods that always return non-negative values.
81
+ if let ExprKind :: MethodCall ( path, caller, args, ..) = expr. kind {
82
+ let mut method_name = path. ident . name . as_str ( ) ;
83
+
84
+ if method_name == "unwrap"
85
+ && let Some ( arglist) = method_chain_args ( expr, & [ "unwrap" ] )
86
+ && let ExprKind :: MethodCall ( inner_path, ..) = & arglist[ 0 ] . 0 . kind
87
+ {
88
+ method_name = inner_path. ident . name . as_str ( ) ;
89
+ }
90
+
91
+ if method_name == "pow"
92
+ && let [ arg] = args
93
+ {
94
+ return pow_call_result_sign ( cx, caller, arg) ;
95
+ } else if METHODS_RET_POSITIVE . iter ( ) . any ( |& name| method_name == name) {
96
+ return Sign :: ZeroOrPositive ;
97
+ }
98
+ }
99
+
100
+ Sign :: Uncertain
101
+ }
102
+
103
+ /// Return the sign of the `pow` call's result.
104
+ ///
105
+ /// If the caller is a positive number, the result is always positive,
106
+ /// If the `power_of` is a even number, the result is always positive as well,
107
+ /// Otherwise a [`Sign::Uncertain`] will be returned.
108
+ fn pow_call_result_sign ( cx : & LateContext < ' _ > , caller : & Expr < ' _ > , power_of : & Expr < ' _ > ) -> Sign {
109
+ let caller_ty = cx. typeck_results ( ) . expr_ty ( caller) ;
110
+ if let Some ( caller_val) = get_const_int_eval ( cx, caller, caller_ty)
111
+ && caller_val >= 0
112
+ {
113
+ return Sign :: ZeroOrPositive ;
114
+ }
115
+
116
+ if let Some ( Constant :: Int ( n) ) = constant ( cx, cx. typeck_results ( ) , power_of)
117
+ && clip ( cx. tcx , n, UintTy :: U32 ) % 2 == 0
118
+ {
119
+ return Sign :: ZeroOrPositive ;
120
+ }
121
+
122
+ Sign :: Uncertain
123
+ }
124
+
125
+ /// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
126
+ /// which the result could always be positive under certain condition.
127
+ ///
128
+ /// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will
129
+ /// return `None`
130
+ fn exprs_with_selected_binop_peeled < ' a > ( expr : & ' a Expr < ' _ > ) -> Option < Vec < & ' a Expr < ' a > > > {
131
+ #[ inline]
132
+ fn collect_operands < ' a > ( expr : & ' a Expr < ' a > , operands : & mut Vec < & ' a Expr < ' a > > ) -> Option < ( ) > {
133
+ match expr. kind {
134
+ ExprKind :: Binary ( op, lhs, rhs) => {
135
+ if matches ! ( op. node, BinOpKind :: Mul | BinOpKind :: Div | BinOpKind :: Rem ) {
136
+ collect_operands ( lhs, operands) ;
137
+ operands. push ( rhs) ;
138
+ } else {
139
+ // Things are complicated when there are other binary ops exist,
140
+ // abort checking by returning `None` for now.
141
+ return None ;
142
+ }
143
+ } ,
144
+ _ => operands. push ( expr) ,
145
+ }
146
+ Some ( ( ) )
147
+ }
148
+
149
+ let mut res = vec ! [ ] ;
150
+ collect_operands ( expr, & mut res) ?;
151
+ Some ( res)
152
+ }
0 commit comments