Skip to content

Commit 8178530

Browse files
authored
Extend {implicit,inverted}_saturating_sub to expressions (#14310)
changelog: [`implicit_saturating_sub`, `inverted_saturating_sub`]: extend lints from local variables to side-effect free expressions Noticed when #14308 introduced an implicit `saturating_sub` operation and didn't get tagged.
2 parents 9f9a822 + 029a6db commit 8178530

7 files changed

+140
-46
lines changed

clippy_lints/src/doc/mod.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -1010,11 +1010,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
10101010
start -= 1;
10111011
}
10121012

1013-
if start > range.start {
1014-
start - range.start
1015-
} else {
1016-
0
1017-
}
1013+
start.saturating_sub(range.start)
10181014
}
10191015
} else {
10201016
0

clippy_lints/src/implicit_saturating_sub.rs

+26-39
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
33
use clippy_utils::msrvs::{self, Msrv};
4-
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::sugg::{Sugg, make_binop};
55
use clippy_utils::{
6-
SpanlessEq, higher, is_in_const_context, is_integer_literal, path_to_local, peel_blocks, peel_blocks_with_stmt,
6+
SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt,
77
};
88
use rustc_ast::ast::LitKind;
99
use rustc_data_structures::packed::Pu128;
1010
use rustc_errors::Applicability;
11-
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, HirId, QPath};
11+
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath};
1212
use rustc_lint::{LateContext, LateLintPass};
1313
use rustc_session::impl_lint_pass;
1414
use rustc_span::Span;
@@ -170,22 +170,20 @@ fn check_gt(
170170
cx: &LateContext<'_>,
171171
condition_span: Span,
172172
expr_span: Span,
173-
big_var: &Expr<'_>,
174-
little_var: &Expr<'_>,
173+
big_expr: &Expr<'_>,
174+
little_expr: &Expr<'_>,
175175
if_block: &Expr<'_>,
176176
else_block: &Expr<'_>,
177177
msrv: Msrv,
178178
is_composited: bool,
179179
) {
180-
if let Some(big_var) = Var::new(big_var)
181-
&& let Some(little_var) = Var::new(little_var)
182-
{
180+
if is_side_effect_free(cx, big_expr) && is_side_effect_free(cx, little_expr) {
183181
check_subtraction(
184182
cx,
185183
condition_span,
186184
expr_span,
187-
big_var,
188-
little_var,
185+
big_expr,
186+
little_expr,
189187
if_block,
190188
else_block,
191189
msrv,
@@ -194,27 +192,17 @@ fn check_gt(
194192
}
195193
}
196194

197-
struct Var {
198-
span: Span,
199-
hir_id: HirId,
200-
}
201-
202-
impl Var {
203-
fn new(expr: &Expr<'_>) -> Option<Self> {
204-
path_to_local(expr).map(|hir_id| Self {
205-
span: expr.span,
206-
hir_id,
207-
})
208-
}
195+
fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
196+
eq_expr_value(cx, expr, expr)
209197
}
210198

211199
#[allow(clippy::too_many_arguments)]
212200
fn check_subtraction(
213201
cx: &LateContext<'_>,
214202
condition_span: Span,
215203
expr_span: Span,
216-
big_var: Var,
217-
little_var: Var,
204+
big_expr: &Expr<'_>,
205+
little_expr: &Expr<'_>,
218206
if_block: &Expr<'_>,
219207
else_block: &Expr<'_>,
220208
msrv: Msrv,
@@ -234,8 +222,8 @@ fn check_subtraction(
234222
cx,
235223
condition_span,
236224
expr_span,
237-
little_var,
238-
big_var,
225+
little_expr,
226+
big_expr,
239227
else_block,
240228
if_block,
241229
msrv,
@@ -247,17 +235,15 @@ fn check_subtraction(
247235
&& let ExprKind::Binary(op, left, right) = if_block.kind
248236
&& let BinOpKind::Sub = op.node
249237
{
250-
let local_left = path_to_local(left);
251-
let local_right = path_to_local(right);
252-
if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right {
238+
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
253239
// This part of the condition is voluntarily split from the one before to ensure that
254240
// if `snippet_opt` fails, it won't try the next conditions.
255-
if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
256-
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
257-
&& (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
241+
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
242+
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
243+
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
258244
{
259245
let sugg = format!(
260-
"{}{big_var_snippet}.saturating_sub({little_var_snippet}){}",
246+
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
261247
if is_composited { "{ " } else { "" },
262248
if is_composited { " }" } else { "" }
263249
);
@@ -271,11 +257,12 @@ fn check_subtraction(
271257
Applicability::MachineApplicable,
272258
);
273259
}
274-
} else if Some(little_var.hir_id) == local_left
275-
&& Some(big_var.hir_id) == local_right
276-
&& let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
277-
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
260+
} else if eq_expr_value(cx, left, little_expr)
261+
&& eq_expr_value(cx, right, big_expr)
262+
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr)
263+
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
278264
{
265+
let sugg = make_binop(BinOpKind::Sub, &big_expr_sugg, &little_expr_sugg);
279266
span_lint_and_then(
280267
cx,
281268
INVERTED_SATURATING_SUB,
@@ -284,12 +271,12 @@ fn check_subtraction(
284271
|diag| {
285272
diag.span_note(
286273
if_block.span,
287-
format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"),
274+
format!("this subtraction underflows when `{little_expr_sugg} < {big_expr_sugg}`"),
288275
);
289276
diag.span_suggestion(
290277
if_block.span,
291278
"try replacing it with",
292-
format!("{big_var_snippet} - {little_var_snippet}"),
279+
format!("{sugg}"),
293280
Applicability::MaybeIncorrect,
294281
);
295282
},

tests/ui/implicit_saturating_sub.fixed

+24
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,27 @@ fn regression_13524(a: usize, b: usize, c: bool) -> usize {
228228
123
229229
} else { b.saturating_sub(a) }
230230
}
231+
232+
fn with_side_effect(a: u64) -> u64 {
233+
println!("a = {a}");
234+
a
235+
}
236+
237+
fn arbitrary_expression() {
238+
let (a, b) = (15u64, 20u64);
239+
240+
let _ = (a * 2).saturating_sub(b);
241+
//~^ implicit_saturating_sub
242+
243+
let _ = a.saturating_sub(b * 2);
244+
//~^ implicit_saturating_sub
245+
246+
let _ = a.saturating_sub(b * 2);
247+
//~^ implicit_saturating_sub
248+
249+
let _ = if with_side_effect(a) > a {
250+
with_side_effect(a) - a
251+
} else {
252+
0
253+
};
254+
}

tests/ui/implicit_saturating_sub.rs

+24
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,27 @@ fn regression_13524(a: usize, b: usize, c: bool) -> usize {
302302
b - a
303303
}
304304
}
305+
306+
fn with_side_effect(a: u64) -> u64 {
307+
println!("a = {a}");
308+
a
309+
}
310+
311+
fn arbitrary_expression() {
312+
let (a, b) = (15u64, 20u64);
313+
314+
let _ = if a * 2 > b { a * 2 - b } else { 0 };
315+
//~^ implicit_saturating_sub
316+
317+
let _ = if a > b * 2 { a - b * 2 } else { 0 };
318+
//~^ implicit_saturating_sub
319+
320+
let _ = if a < b * 2 { 0 } else { a - b * 2 };
321+
//~^ implicit_saturating_sub
322+
323+
let _ = if with_side_effect(a) > a {
324+
with_side_effect(a) - a
325+
} else {
326+
0
327+
};
328+
}

tests/ui/implicit_saturating_sub.stderr

+19-1
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,23 @@ LL | | b - a
220220
LL | | }
221221
| |_____^ help: replace it with: `{ b.saturating_sub(a) }`
222222

223-
error: aborting due to 24 previous errors
223+
error: manual arithmetic check found
224+
--> tests/ui/implicit_saturating_sub.rs:314:13
225+
|
226+
LL | let _ = if a * 2 > b { a * 2 - b } else { 0 };
227+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `(a * 2).saturating_sub(b)`
228+
229+
error: manual arithmetic check found
230+
--> tests/ui/implicit_saturating_sub.rs:317:13
231+
|
232+
LL | let _ = if a > b * 2 { a - b * 2 } else { 0 };
233+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a.saturating_sub(b * 2)`
234+
235+
error: manual arithmetic check found
236+
--> tests/ui/implicit_saturating_sub.rs:320:13
237+
|
238+
LL | let _ = if a < b * 2 { 0 } else { a - b * 2 };
239+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a.saturating_sub(b * 2)`
240+
241+
error: aborting due to 27 previous errors
224242

tests/ui/manual_arithmetic_check-2.rs

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ fn main() {
2424
let result = if b <= a { 0 } else { a - b };
2525
//~^ inverted_saturating_sub
2626

27+
let result = if b * 2 <= a { 0 } else { a - b * 2 };
28+
//~^ inverted_saturating_sub
29+
30+
let result = if b <= a * 2 { 0 } else { a * 2 - b };
31+
//~^ inverted_saturating_sub
32+
33+
let result = if b + 3 <= a + 2 { 0 } else { (a + 2) - (b + 3) };
34+
//~^ inverted_saturating_sub
35+
2736
let af = 12f32;
2837
let bf = 13f32;
2938
// Should not lint!

tests/ui/manual_arithmetic_check-2.stderr

+37-1
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,41 @@ note: this subtraction underflows when `a < b`
7171
LL | let result = if b <= a { 0 } else { a - b };
7272
| ^^^^^
7373

74-
error: aborting due to 6 previous errors
74+
error: inverted arithmetic check before subtraction
75+
--> tests/ui/manual_arithmetic_check-2.rs:27:27
76+
|
77+
LL | let result = if b * 2 <= a { 0 } else { a - b * 2 };
78+
| ^^ --------- help: try replacing it with: `b * 2 - a`
79+
|
80+
note: this subtraction underflows when `a < b * 2`
81+
--> tests/ui/manual_arithmetic_check-2.rs:27:45
82+
|
83+
LL | let result = if b * 2 <= a { 0 } else { a - b * 2 };
84+
| ^^^^^^^^^
85+
86+
error: inverted arithmetic check before subtraction
87+
--> tests/ui/manual_arithmetic_check-2.rs:30:23
88+
|
89+
LL | let result = if b <= a * 2 { 0 } else { a * 2 - b };
90+
| ^^ --------- help: try replacing it with: `b - a * 2`
91+
|
92+
note: this subtraction underflows when `a * 2 < b`
93+
--> tests/ui/manual_arithmetic_check-2.rs:30:45
94+
|
95+
LL | let result = if b <= a * 2 { 0 } else { a * 2 - b };
96+
| ^^^^^^^^^
97+
98+
error: inverted arithmetic check before subtraction
99+
--> tests/ui/manual_arithmetic_check-2.rs:33:27
100+
|
101+
LL | let result = if b + 3 <= a + 2 { 0 } else { (a + 2) - (b + 3) };
102+
| ^^ ----------------- help: try replacing it with: `b + 3 - (a + 2)`
103+
|
104+
note: this subtraction underflows when `a + 2 < b + 3`
105+
--> tests/ui/manual_arithmetic_check-2.rs:33:49
106+
|
107+
LL | let result = if b + 3 <= a + 2 { 0 } else { (a + 2) - (b + 3) };
108+
| ^^^^^^^^^^^^^^^^^
109+
110+
error: aborting due to 9 previous errors
75111

0 commit comments

Comments
 (0)