Skip to content

Commit 428d901

Browse files
committed
Enforced divisor purity and first use
1 parent 898e816 commit 428d901

File tree

3 files changed

+100
-16
lines changed

3 files changed

+100
-16
lines changed

clippy_lints/src/manual_checked_div.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,27 @@ impl LateLintPass<'_> for ManualCheckedDiv {
5555
&& is_integer(cx, divisor)
5656
&& let Some(block) = branch_block(then, r#else, branch)
5757
{
58-
let mut eq = SpanlessEq::new(cx);
58+
let mut eq = SpanlessEq::new(cx).deny_side_effects().paths_by_resolution();
59+
if !eq.eq_expr(divisor, divisor) {
60+
return;
61+
}
62+
5963
let mut applicability = Applicability::MaybeIncorrect;
6064
let mut divisions = Vec::new();
65+
let mut first_use = None;
6166

62-
for_each_expr_without_closures(block, |e| {
67+
let found_early_use = for_each_expr_without_closures(block, |e| {
6368
if let ExprKind::Binary(binop, lhs, rhs) = e.kind
6469
&& binop.node == BinOpKind::Div
6570
&& eq.eq_expr(rhs, divisor)
6671
&& is_integer(cx, lhs)
6772
{
73+
match first_use {
74+
None => first_use = Some(UseKind::Division),
75+
Some(UseKind::Other) => return ControlFlow::Break(()),
76+
Some(UseKind::Division) => {},
77+
}
78+
6879
let lhs_snip = Sugg::hir_with_applicability(cx, lhs, "_", &mut applicability);
6980
let rhs_snip = Sugg::hir_with_applicability(cx, rhs, "_", &mut applicability);
7081
let lhs_ty = cx.typeck_results().expr_ty(lhs);
@@ -93,28 +104,41 @@ impl LateLintPass<'_> for ManualCheckedDiv {
93104
divisions.push((e.span, suggestion));
94105

95106
ControlFlow::<(), _>::Continue(Descend::No)
107+
} else if eq.eq_expr(e, divisor) {
108+
if first_use.is_none() {
109+
first_use = Some(UseKind::Other);
110+
}
111+
ControlFlow::<(), _>::Continue(Descend::Yes)
96112
} else {
97113
ControlFlow::<(), _>::Continue(Descend::Yes)
98114
}
99115
});
100116

101-
if !divisions.is_empty() {
102-
let mut spans: Vec<_> = divisions.iter().map(|(span, _)| *span).collect();
103-
spans.push(cond.span);
104-
span_lint_and_then(
105-
cx,
106-
MANUAL_CHECKED_DIV,
107-
MultiSpan::from_spans(spans),
108-
"manual checked division",
109-
|diag| {
110-
diag.multipart_suggestion("consider using `checked_div`", divisions, applicability);
111-
},
112-
);
117+
if found_early_use.is_some() || first_use != Some(UseKind::Division) || divisions.is_empty() {
118+
return;
113119
}
120+
121+
let mut spans: Vec<_> = divisions.iter().map(|(span, _)| *span).collect();
122+
spans.push(cond.span);
123+
span_lint_and_then(
124+
cx,
125+
MANUAL_CHECKED_DIV,
126+
MultiSpan::from_spans(spans),
127+
"manual checked division",
128+
|diag| {
129+
diag.multipart_suggestion("consider using `checked_div`", divisions, applicability);
130+
},
131+
);
114132
}
115133
}
116134
}
117135

136+
#[derive(Copy, Clone, Eq, PartialEq)]
137+
enum UseKind {
138+
Division,
139+
Other,
140+
}
141+
118142
fn divisor_from_condition<'tcx>(cond: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, NonZeroBranch)> {
119143
let ExprKind::Binary(binop, lhs, rhs) = cond.kind else {
120144
return None;

tests/ui/manual_checked_div.fixed

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
fn main() {
44
let a = 10u32;
5-
let b = 5u32;
5+
let mut b = 5u32;
66

77
// Should trigger lint
88
if b != 0 {
@@ -33,4 +33,34 @@ fn main() {
3333
//~^ manual_checked_div
3434
let _result = 10_i32.checked_div(c);
3535
}
36+
37+
// Should NOT trigger (side effects in divisor)
38+
if counter() > 0 {
39+
let _ = 32 / counter();
40+
}
41+
42+
// Should NOT trigger (divisor used before division)
43+
if b > 0 {
44+
use_value(b);
45+
let _ = a / b;
46+
}
47+
48+
// Should NOT trigger (divisor may change during evaluation)
49+
if b > 0 {
50+
g(inc_and_return_value(&mut b), a / b);
51+
}
52+
}
53+
54+
fn counter() -> u32 {
55+
println!("counter");
56+
1
3657
}
58+
59+
fn use_value(_v: u32) {}
60+
61+
fn inc_and_return_value(x: &mut u32) -> u32 {
62+
*x += 1;
63+
*x
64+
}
65+
66+
fn g(_lhs: u32, _rhs: u32) {}

tests/ui/manual_checked_div.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
fn main() {
44
let a = 10u32;
5-
let b = 5u32;
5+
let mut b = 5u32;
66

77
// Should trigger lint
88
if b != 0 {
@@ -33,4 +33,34 @@ fn main() {
3333
//~^ manual_checked_div
3434
let _result = 10 / c;
3535
}
36+
37+
// Should NOT trigger (side effects in divisor)
38+
if counter() > 0 {
39+
let _ = 32 / counter();
40+
}
41+
42+
// Should NOT trigger (divisor used before division)
43+
if b > 0 {
44+
use_value(b);
45+
let _ = a / b;
46+
}
47+
48+
// Should NOT trigger (divisor may change during evaluation)
49+
if b > 0 {
50+
g(inc_and_return_value(&mut b), a / b);
51+
}
52+
}
53+
54+
fn counter() -> u32 {
55+
println!("counter");
56+
1
3657
}
58+
59+
fn use_value(_v: u32) {}
60+
61+
fn inc_and_return_value(x: &mut u32) -> u32 {
62+
*x += 1;
63+
*x
64+
}
65+
66+
fn g(_lhs: u32, _rhs: u32) {}

0 commit comments

Comments
 (0)