Skip to content

Commit ba5cedd

Browse files
committed
Extend {implicit,inverted}_saturating_sub to expressions
Side-effect free expressions are eligible to these lints, whereas previously only local variables were checked.
1 parent 527ab05 commit ba5cedd

File tree

6 files changed

+140
-41
lines changed

6 files changed

+140
-41
lines changed

clippy_lints/src/implicit_saturating_sub.rs

Lines changed: 27 additions & 39 deletions
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;
@@ -174,22 +174,20 @@ fn check_gt(
174174
cx: &LateContext<'_>,
175175
condition_span: Span,
176176
expr_span: Span,
177-
big_var: &Expr<'_>,
178-
little_var: &Expr<'_>,
177+
big_expr: &Expr<'_>,
178+
little_expr: &Expr<'_>,
179179
if_block: &Expr<'_>,
180180
else_block: &Expr<'_>,
181181
msrv: &Msrv,
182182
is_composited: bool,
183183
) {
184-
if let Some(big_var) = Var::new(big_var)
185-
&& let Some(little_var) = Var::new(little_var)
186-
{
184+
if is_side_effect_free(cx, big_expr) && is_side_effect_free(cx, little_expr) {
187185
check_subtraction(
188186
cx,
189187
condition_span,
190188
expr_span,
191-
big_var,
192-
little_var,
189+
big_expr,
190+
little_expr,
193191
if_block,
194192
else_block,
195193
msrv,
@@ -198,27 +196,18 @@ fn check_gt(
198196
}
199197
}
200198

201-
struct Var {
202-
span: Span,
203-
hir_id: HirId,
204-
}
205-
206-
impl Var {
207-
fn new(expr: &Expr<'_>) -> Option<Self> {
208-
path_to_local(expr).map(|hir_id| Self {
209-
span: expr.span,
210-
hir_id,
211-
})
212-
}
199+
/// Return the expression if it is side effect free.
200+
fn is_side_effect_free(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
201+
eq_expr_value(cx, expr, expr)
213202
}
214203

215204
#[allow(clippy::too_many_arguments)]
216205
fn check_subtraction(
217206
cx: &LateContext<'_>,
218207
condition_span: Span,
219208
expr_span: Span,
220-
big_var: Var,
221-
little_var: Var,
209+
big_expr: &Expr<'_>,
210+
little_expr: &Expr<'_>,
222211
if_block: &Expr<'_>,
223212
else_block: &Expr<'_>,
224213
msrv: &Msrv,
@@ -238,8 +227,8 @@ fn check_subtraction(
238227
cx,
239228
condition_span,
240229
expr_span,
241-
little_var,
242-
big_var,
230+
little_expr,
231+
big_expr,
243232
else_block,
244233
if_block,
245234
msrv,
@@ -251,17 +240,15 @@ fn check_subtraction(
251240
&& let ExprKind::Binary(op, left, right) = if_block.kind
252241
&& let BinOpKind::Sub = op.node
253242
{
254-
let local_left = path_to_local(left);
255-
let local_right = path_to_local(right);
256-
if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right {
243+
if eq_expr_value(cx, left, big_expr) && eq_expr_value(cx, right, little_expr) {
257244
// This part of the condition is voluntarily split from the one before to ensure that
258245
// if `snippet_opt` fails, it won't try the next conditions.
259-
if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
260-
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
261-
&& (!is_in_const_context(cx) || msrv.meets(msrvs::SATURATING_SUB_CONST))
246+
if (!is_in_const_context(cx) || msrv.meets(msrvs::SATURATING_SUB_CONST))
247+
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
248+
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
262249
{
263250
let sugg = format!(
264-
"{}{big_var_snippet}.saturating_sub({little_var_snippet}){}",
251+
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
265252
if is_composited { "{ " } else { "" },
266253
if is_composited { " }" } else { "" }
267254
);
@@ -275,11 +262,12 @@ fn check_subtraction(
275262
Applicability::MachineApplicable,
276263
);
277264
}
278-
} else if Some(little_var.hir_id) == local_left
279-
&& Some(big_var.hir_id) == local_right
280-
&& let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
281-
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
265+
} else if eq_expr_value(cx, left, little_expr)
266+
&& eq_expr_value(cx, right, big_expr)
267+
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr)
268+
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
282269
{
270+
let sugg = make_binop(BinOpKind::Sub, &big_expr_sugg, &little_expr_sugg);
283271
span_lint_and_then(
284272
cx,
285273
INVERTED_SATURATING_SUB,
@@ -288,12 +276,12 @@ fn check_subtraction(
288276
|diag| {
289277
diag.span_note(
290278
if_block.span,
291-
format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"),
279+
format!("this subtraction underflows when `{little_expr_sugg} < {big_expr_sugg}`"),
292280
);
293281
diag.span_suggestion(
294282
if_block.span,
295283
"try replacing it with",
296-
format!("{big_var_snippet} - {little_var_snippet}"),
284+
format!("{sugg}"),
297285
Applicability::MaybeIncorrect,
298286
);
299287
},

tests/ui/implicit_saturating_sub.fixed

Lines changed: 24 additions & 0 deletions
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

Lines changed: 24 additions & 0 deletions
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

Lines changed: 19 additions & 1 deletion
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 37 additions & 1 deletion
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)