Skip to content

Commit 9a318a9

Browse files
committed
Lint test case false positives corrected
1 parent 98b1666 commit 9a318a9

File tree

5 files changed

+380
-101
lines changed

5 files changed

+380
-101
lines changed

clippy_lints/src/manual_checked_sub.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ declare_clippy_lint! {
3333
#[clippy::version = "1.86.0"]
3434
pub MANUAL_CHECKED_SUB,
3535
complexity,
36-
"default lint description"
36+
"Checks for manual re-implementations of checked subtraction."
3737
}
3838

3939
pub struct ManualCheckedSub {
@@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
5959

6060
let applicability = Applicability::MachineApplicable;
6161

62-
if let ExprKind::If(drop_temp_expr, body_block, _) = expr.kind
62+
if let ExprKind::If(drop_temp_expr, body_block, else_block) = expr.kind
6363
&& let ExprKind::DropTemps(condition_expr) = drop_temp_expr.kind
6464
&& let ExprKind::Binary(op, lhs, rhs) = condition_expr.kind
6565
&& is_unsigned_int(cx, &lhs)
@@ -69,6 +69,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
6969
SubExprVisitor {
7070
cx,
7171
applicability,
72+
if_expr: expr,
73+
if_body_block: body_block,
74+
else_block,
7275
condition_lhs: &lhs,
7376
condition_rhs: &rhs,
7477
}
@@ -82,6 +85,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
8285
SubExprVisitor {
8386
cx,
8487
applicability,
88+
if_expr: expr,
89+
if_body_block: body_block,
90+
else_block,
8591
condition_lhs: &lhs,
8692
condition_rhs: &rhs,
8793
}
@@ -94,6 +100,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
94100

95101
struct SubExprVisitor<'a, 'tcx> {
96102
cx: &'a LateContext<'tcx>,
103+
if_expr: &'tcx Expr<'tcx>,
104+
if_body_block: &'tcx Expr<'tcx>,
105+
else_block: Option<&'tcx Expr<'tcx>>,
97106
applicability: Applicability,
98107
condition_lhs: &'tcx Expr<'tcx>,
99108
condition_rhs: &'tcx Expr<'tcx>,
@@ -127,21 +136,43 @@ impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> {
127136

128137
impl<'a, 'tcx> SubExprVisitor<'a, 'tcx> {
129138
fn build_suggession(&mut self, expr: &Expr<'tcx>) {
139+
// if let Some(else_expr) = self.else_block {
140+
// println!("ELSE BLOCK in suggestion:::: {:#?}", else_expr);
141+
// }
142+
143+
let body_snippet = snippet_with_applicability(self.cx, self.if_body_block.span, "..", &mut self.applicability);
144+
145+
let binary_expr_snippet = snippet_with_applicability(self.cx, expr.span, "..", &mut self.applicability);
146+
let updated_usage_context_snippet = body_snippet.as_ref().replace(binary_expr_snippet.as_ref(), "result");
147+
// let else_snippet = snippet_with_applicability(self.cx, self.if_else_block.span, "..", &mut
148+
// self.applicability);
149+
130150
let lhs_snippet = snippet_with_applicability(self.cx, self.condition_lhs.span, "..", &mut self.applicability);
131151
let rhs_snippet = snippet_with_applicability(self.cx, self.condition_rhs.span, "..", &mut self.applicability);
132152

133153
let lhs_needs_parens = matches!(self.condition_lhs.kind, ExprKind::Cast(..));
134154

135-
let suggestion = if lhs_needs_parens {
136-
format!("({}).checked_sub({})", lhs_snippet, rhs_snippet)
155+
let mut suggestion = if lhs_needs_parens {
156+
format!(
157+
"if let Some(result) = ({}).checked_sub({}) {}",
158+
lhs_snippet, rhs_snippet, updated_usage_context_snippet
159+
)
137160
} else {
138-
format!("{}.checked_sub({})", lhs_snippet, rhs_snippet)
161+
format!(
162+
"if let Some(result) = {}.checked_sub({}) {}",
163+
lhs_snippet, rhs_snippet, updated_usage_context_snippet
164+
)
139165
};
140166

167+
if let Some(else_expr) = self.else_block {
168+
let else_snippet = snippet_with_applicability(self.cx, else_expr.span, "..", &mut self.applicability);
169+
suggestion.push_str(&format!(" else {}", else_snippet));
170+
}
171+
141172
span_lint_and_sugg(
142173
self.cx,
143174
MANUAL_CHECKED_SUB,
144-
expr.span,
175+
self.if_expr.span,
145176
"manual re-implementation of checked subtraction",
146177
"consider using `.checked_sub()`",
147178
suggestion,
@@ -212,7 +243,7 @@ fn is_referencing_same_variable<'tcx>(expr1: &'tcx Expr<'_>, expr2: &'tcx Expr<'
212243

213244
fn get_resolved_path_id<'tcx>(expr: &'tcx Expr<'_>) -> Option<Res> {
214245
if let ExprKind::Path(QPath::Resolved(_, path)) = &expr.kind {
215-
path.segments.first().map(|segment| segment.res)
246+
path.segments.first().and_then(|segment| Some(segment.res))
216247
} else {
217248
None
218249
}

tests/ui/implicit_saturating_sub.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
#![allow(unused_assignments, unused_mut, clippy::assign_op_pattern)]
1+
#![allow(
2+
unused_assignments,
3+
unused_mut,
4+
clippy::assign_op_pattern,
5+
clippy::manual_checked_sub
6+
)]
27
#![warn(clippy::implicit_saturating_sub)]
38

49
use std::cmp::PartialEq;

tests/ui/manual_checked_sub.fixed

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,141 @@
11
#![allow(clippy::collapsible_if)]
22
#![warn(clippy::manual_checked_sub)]
33

4-
//Positive test, lint
54
fn positive_tests() {
65
let a: u32 = 10;
76
let b: u32 = 5;
7+
let c = 3;
88

9-
// Case 1: a - b inside an if a >= b
10-
if a >= b {
11-
let _ = a.checked_sub(b);
9+
// Basic subtraction inside an if
10+
if let Some(result) = a.checked_sub(b) {
11+
//~^ manual_checked_sub
12+
let c = result;
13+
}
14+
15+
// Basic subtraction inside an if with an else block
16+
if let Some(result) = a.checked_sub(b) {
17+
//~^ manual_checked_sub
18+
let c = result;
19+
} else {
20+
let c = a + b;
1221
}
1322

14-
// Case 2: a - 1 inside an if a > 0
15-
if a > 0 {
16-
let _ = a.checked_sub(0);
23+
// Decrementing inside an if condition
24+
if let Some(result) = a.checked_sub(0) {
25+
//~^ manual_checked_sub
26+
let _ = result;
1727
}
1828

19-
// Case 3: Both suffixed, should lint
20-
if 10u32 >= 5u32 {
21-
let _ = 10u32.checked_sub(5u32);
29+
// Variable subtraction used in a function call
30+
if let Some(result) = a.checked_sub(b) {
31+
//~^ manual_checked_sub
32+
some_function(result);
33+
}
34+
35+
// Subtracting two suffixed literals
36+
if let Some(result) = 10u32.checked_sub(5u32) {
37+
//~^ manual_checked_sub
38+
let _ = result;
2239
}
2340

2441
let x: i32 = 10;
2542
let y: i32 = 5;
2643

27-
// Case 4: casted variables inside an if, should lint
28-
if x as u32 >= y as u32 {
29-
let _ = (x as u32).checked_sub(y as u32);
44+
// Casted variables inside an if
45+
if let Some(result) = (x as u32).checked_sub(y as u32) {
46+
//~^ manual_checked_sub
47+
let _ = result;
48+
}
49+
50+
// Casted variable and literal inside an if
51+
if let Some(result) = (x as u32).checked_sub(5u32) {
52+
//~^ manual_checked_sub
53+
let _ = result;
54+
}
55+
56+
// Casting subtraction result
57+
if let Some(result) = a.checked_sub(b) {
58+
//~^ manual_checked_sub
59+
let _ = result as u64;
60+
}
61+
62+
if let Some(result) = a.checked_sub(b) {
63+
//~^ manual_checked_sub
64+
macro_rules! subtract {
65+
() => {
66+
result
67+
};
68+
}
69+
let _ = subtract!();
70+
}
71+
72+
struct Example {
73+
value: u32,
74+
}
75+
76+
if let Some(result) = a.checked_sub(b) {
77+
//~^ manual_checked_sub
78+
let _ = Example { value: result };
3079
}
3180

32-
// Case 5: casted variable and literal inside an if, should lint
33-
if x as u32 >= 5u32 {
34-
let _ = (x as u32).checked_sub(5u32);
81+
// Subtraction inside a nested if, should lint
82+
if let Some(result) = a.checked_sub(b) {
83+
//~^ manual_checked_sub
84+
if c > 0 {
85+
let _ = result;
86+
}
3587
}
3688
}
3789

38-
// Negative tests, no lint
90+
fn some_function(a: u32) {}
91+
3992
fn negative_tests() {
4093
let a: u32 = 10;
4194
let b: u32 = 5;
4295

43-
// Case 1: Uses checked_sub() correctly, no lint
96+
// Uses `.checked_sub()`, should not trigger
4497
let _ = a.checked_sub(b);
4598

46-
// Case 2: Works with signed integers (i32), no lint
99+
// Works with signed integers (i32), should not trigger
47100
let x: i32 = 10;
48101
let y: i32 = 5;
102+
49103
if x >= y {
50104
let _ = x - y;
51105
}
52106

53-
// Case 3: If condition doesn't match, no lint
107+
// If condition does not match subtraction variables
54108
if a == b {
55109
let _ = a - b;
56110
}
57111

58-
// Case 4: a - b inside an if a > b
112+
// a - b inside an if a > b (shouldn't lint)
59113
if a > b {
60114
let _ = a - b;
61115
}
62116

63-
// Case 5: Unsuffixed literals, no lint
117+
// Unsuffixed literals (shouldn't lint)
64118
if 10 >= 5 {
65119
let _ = 10 - 5;
66120
}
67121

68-
// Case 6: Suffixed lhs, unsuffixed rhs, no lint
122+
// Suffixed LHS, unsuffixed RHS (shouldn't lint)
69123
if 10u32 >= 5 {
70124
let _ = 10u32 - 5;
71125
}
72126

73-
// Case 7: Unsuffixed lhs, suffixed rhs, no lint
127+
// Unsuffixed LHS, suffixed RHS (shouldn't lint)
74128
if 10 >= 5u32 {
75129
let _ = 10 - 5u32;
76130
}
77-
}
78-
79-
fn edge_cases() {
80-
let a: u32 = 10;
81-
let b: u32 = 5;
82-
let c = 3;
83131

84-
// Edge Case 1: Subtraction inside a nested if
132+
// Using `.wrapping_sub()`, should not trigger
85133
if a >= b {
86-
if c > 0 {
87-
let _ = a.checked_sub(b);
88-
}
89-
}
90-
91-
if a > b {
92-
if a - b > c {
93-
let _ = a - b; // This subtraction is safe because `a > b` is checked
94-
}
134+
let _ = a.wrapping_sub(b);
95135
}
96136
}
97137

98138
fn main() {
99139
positive_tests();
100140
negative_tests();
101-
edge_cases();
102141
}

0 commit comments

Comments
 (0)