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 (
@@ -25,37 +27,126 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
2527 return false ;
2628 }
2729
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) {
3432 return false ;
3533 }
3634
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+ } ;
5248 }
5349
54- true
50+ // Lint if there are odd number of uncertain or negative results
51+ uncertain_count % 2 == 1 || negative_count % 2 == 1
5552 } ,
5653
5754 ( false , true ) => !cast_to. is_signed ( ) ,
5855
5956 ( _, _) => false ,
6057 }
6158}
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