Skip to content

Commit 0e5dc8e

Browse files
committed
Auto merge of rust-lang#11883 - J-ZhengLi:issue11642, r=dswij
improve [`cast_sign_loss`], to skip warning on always positive expressions fixes: rust-lang#11642 changelog: improve [`cast_sign_loss`] to skip warning on always positive expressions Turns out this is change became quite big, and I still can't cover all the cases, like method calls such as `POSITIVE_NUM.mul(POSITIVE_NUM)`, or `NEGATIVE_NUM.div(NEGATIVE_NUM)`... but well, if I do, I'm scared that this will goes forever, so I stopped, unless it needs to be done, lol.
2 parents 788094c + eae2317 commit 0e5dc8e

File tree

3 files changed

+238
-26
lines changed

3 files changed

+238
-26
lines changed
+116-25
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use clippy_utils::consts::{constant, Constant};
22
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};
55
use rustc_lint::LateContext;
6-
use rustc_middle::ty::{self, Ty};
6+
use rustc_middle::ty::{self, Ty, UintTy};
77

88
use super::CAST_SIGN_LOSS;
99

10+
const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
11+
1012
pub(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+
}

tests/ui/cast.rs

+49
Original file line numberDiff line numberDiff line change
@@ -365,3 +365,52 @@ fn avoid_subtract_overflow(q: u32) {
365365
fn issue11426() {
366366
(&42u8 >> 0xa9008fb6c9d81e42_0e25730562a601c8_u128) as usize;
367367
}
368+
369+
fn issue11642() {
370+
fn square(x: i16) -> u32 {
371+
let x = x as i32;
372+
(x * x) as u32;
373+
x.pow(2) as u32;
374+
(-2_i32).pow(2) as u32
375+
}
376+
377+
let _a = |x: i32| -> u32 { (x * x * x * x) as u32 };
378+
379+
(-2_i32).pow(3) as u32;
380+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
381+
382+
let x: i32 = 10;
383+
(x * x) as u32;
384+
(x * x * x) as u32;
385+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
386+
387+
let y: i16 = -2;
388+
(y * y * y * y * -2) as u16;
389+
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
390+
(y * y * y * y * 2) as u16;
391+
(y * y * y * 2) as u16;
392+
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
393+
(y * y * y * -2) as u16;
394+
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
395+
396+
fn foo(a: i32, b: i32, c: i32) -> u32 {
397+
(a * a * b * b * c * c) as u32;
398+
(a * b * c) as u32;
399+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
400+
(a * -b * c) as u32;
401+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
402+
(a * b * c * c) as u32;
403+
(a * -2) as u32;
404+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
405+
(a * b * c * -2) as u32;
406+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
407+
(a / b) as u32;
408+
(a / b * c) as u32;
409+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
410+
(a / b + b * c) as u32;
411+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
412+
a.pow(3) as u32;
413+
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
414+
(a.abs() * b.pow(2) / c.abs()) as u32
415+
}
416+
}

tests/ui/cast.stderr

+73-1
Original file line numberDiff line numberDiff line change
@@ -444,5 +444,77 @@ help: ... or use `try_from` and handle the error accordingly
444444
LL | let c = u8::try_from(q / 1000);
445445
| ~~~~~~~~~~~~~~~~~~~~~~
446446

447-
error: aborting due to 51 previous errors
447+
error: casting `i32` to `u32` may lose the sign of the value
448+
--> $DIR/cast.rs:379:5
449+
|
450+
LL | (-2_i32).pow(3) as u32;
451+
| ^^^^^^^^^^^^^^^^^^^^^^
452+
453+
error: casting `i32` to `u32` may lose the sign of the value
454+
--> $DIR/cast.rs:384:5
455+
|
456+
LL | (x * x * x) as u32;
457+
| ^^^^^^^^^^^^^^^^^^
458+
459+
error: casting `i16` to `u16` may lose the sign of the value
460+
--> $DIR/cast.rs:388:5
461+
|
462+
LL | (y * y * y * y * -2) as u16;
463+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
464+
465+
error: casting `i16` to `u16` may lose the sign of the value
466+
--> $DIR/cast.rs:391:5
467+
|
468+
LL | (y * y * y * 2) as u16;
469+
| ^^^^^^^^^^^^^^^^^^^^^^
470+
471+
error: casting `i16` to `u16` may lose the sign of the value
472+
--> $DIR/cast.rs:393:5
473+
|
474+
LL | (y * y * y * -2) as u16;
475+
| ^^^^^^^^^^^^^^^^^^^^^^^
476+
477+
error: casting `i32` to `u32` may lose the sign of the value
478+
--> $DIR/cast.rs:398:9
479+
|
480+
LL | (a * b * c) as u32;
481+
| ^^^^^^^^^^^^^^^^^^
482+
483+
error: casting `i32` to `u32` may lose the sign of the value
484+
--> $DIR/cast.rs:400:9
485+
|
486+
LL | (a * -b * c) as u32;
487+
| ^^^^^^^^^^^^^^^^^^^
488+
489+
error: casting `i32` to `u32` may lose the sign of the value
490+
--> $DIR/cast.rs:403:9
491+
|
492+
LL | (a * -2) as u32;
493+
| ^^^^^^^^^^^^^^^
494+
495+
error: casting `i32` to `u32` may lose the sign of the value
496+
--> $DIR/cast.rs:405:9
497+
|
498+
LL | (a * b * c * -2) as u32;
499+
| ^^^^^^^^^^^^^^^^^^^^^^^
500+
501+
error: casting `i32` to `u32` may lose the sign of the value
502+
--> $DIR/cast.rs:408:9
503+
|
504+
LL | (a / b * c) as u32;
505+
| ^^^^^^^^^^^^^^^^^^
506+
507+
error: casting `i32` to `u32` may lose the sign of the value
508+
--> $DIR/cast.rs:410:9
509+
|
510+
LL | (a / b + b * c) as u32;
511+
| ^^^^^^^^^^^^^^^^^^^^^^
512+
513+
error: casting `i32` to `u32` may lose the sign of the value
514+
--> $DIR/cast.rs:412:9
515+
|
516+
LL | a.pow(3) as u32;
517+
| ^^^^^^^^^^^^^^^
518+
519+
error: aborting due to 63 previous errors
448520

0 commit comments

Comments
 (0)