11use clippy_utils:: consts:: { constant, Constant } ;
22use 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 } ;
55use rustc_lint:: LateContext ;
6- use rustc_middle:: ty:: { self , Ty } ;
6+ use rustc_middle:: ty:: { self , Ty , UintTy } ;
77
88use super :: CAST_SIGN_LOSS ;
99
10+ const METHODS_RET_POSITIVE : & [ & str ] = & [ "abs" , "checked_abs" , "rem_euclid" , "checked_rem_euclid" ] ;
11+
1012pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , cast_op : & Expr < ' _ > , cast_from : Ty < ' _ > , cast_to : Ty < ' _ > ) {
1113 if should_lint ( cx, cast_op, cast_from, cast_to) {
1214 span_lint (
@@ -18,44 +20,151 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
1820 }
1921}
2022
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~
2140fn should_lint ( cx : & LateContext < ' _ > , cast_op : & Expr < ' _ > , cast_from : Ty < ' _ > , cast_to : Ty < ' _ > ) -> bool {
2241 match ( cast_from. is_integral ( ) , cast_to. is_integral ( ) ) {
2342 ( true , true ) => {
2443 if !cast_from. is_signed ( ) || cast_to. is_signed ( ) {
2544 return false ;
2645 }
2746
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) {
3449 return false ;
3550 }
3651
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+ _ => ( ) ,
64+ } ;
5265 }
5366
54- true
67+ // Lint if there are odd number of uncertain or negative results
68+ uncertain_count % 2 == 1 || negative_count % 2 == 1
5569 } ,
5670
5771 ( false , true ) => !cast_to. is_signed ( ) ,
5872
5973 ( _, _) => false ,
6074 }
6175}
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+ let mut res = vec ! [ ] ;
149+
150+ #[ inline]
151+ fn collect_operands < ' a > ( expr : & ' a Expr < ' a > , operands : & mut Vec < & ' a Expr < ' a > > ) -> Option < ( ) > {
152+ match expr. kind {
153+ ExprKind :: Binary ( op, lhs, rhs) => {
154+ if matches ! ( op. node, BinOpKind :: Mul | BinOpKind :: Div | BinOpKind :: Rem ) {
155+ collect_operands ( lhs, operands) ;
156+ operands. push ( rhs) ;
157+ } else {
158+ // Things are complicated when there are other binary ops exist,
159+ // abort checking by returning `None` for now.
160+ return None ;
161+ }
162+ } ,
163+ _ => operands. push ( expr) ,
164+ }
165+ Some ( ( ) )
166+ }
167+
168+ collect_operands ( expr, & mut res) ?;
169+ Some ( res)
170+ }
0 commit comments