Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 74a2344

Browse files
Extend implicit_saturation_sub lint
1 parent 7381944 commit 74a2344

File tree

1 file changed

+218
-60
lines changed

1 file changed

+218
-60
lines changed

clippy_lints/src/implicit_saturating_sub.rs

Lines changed: 218 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
1+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::source::snippet_opt;
3+
use clippy_utils::{higher, is_integer_literal, path_to_local, peel_blocks, peel_blocks_with_stmt, SpanlessEq};
34
use rustc_ast::ast::LitKind;
45
use rustc_data_structures::packed::Pu128;
56
use rustc_errors::Applicability;
6-
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
7+
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, HirId, QPath};
78
use rustc_lint::{LateContext, LateLintPass};
89
use rustc_session::declare_lint_pass;
10+
use rustc_span::Span;
911

1012
declare_clippy_lint! {
1113
/// ### What it does
@@ -50,73 +52,229 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
5052

5153
// Check if the conditional expression is a binary operation
5254
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
55+
{
56+
check_with_condition(cx, expr, cond_op.node, cond_left, cond_right, then);
57+
} else if let Some(higher::If {
58+
cond,
59+
then: if_block,
60+
r#else: Some(else_block),
61+
}) = higher::If::hir(expr)
62+
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
63+
{
64+
check_manual_check(cx, expr, cond_op, cond_left, cond_right, if_block, else_block);
65+
}
66+
}
67+
}
5368

54-
// Ensure that the binary operator is >, !=, or <
55-
&& (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node)
69+
fn check_manual_check<'tcx>(
70+
cx: &LateContext<'tcx>,
71+
expr: &Expr<'tcx>,
72+
condition: &BinOp,
73+
left_hand: &Expr<'tcx>,
74+
right_hand: &Expr<'tcx>,
75+
if_block: &Expr<'tcx>,
76+
else_block: &Expr<'tcx>,
77+
) {
78+
let ty = cx.typeck_results().expr_ty(left_hand);
79+
if ty.is_numeric() && !ty.is_signed() {
80+
match condition.node {
81+
BinOpKind::Gt | BinOpKind::Ge => check_gt(
82+
cx,
83+
condition.span,
84+
expr.span,
85+
left_hand,
86+
right_hand,
87+
if_block,
88+
else_block,
89+
),
90+
BinOpKind::Lt | BinOpKind::Le => check_gt(
91+
cx,
92+
condition.span,
93+
expr.span,
94+
right_hand,
95+
left_hand,
96+
if_block,
97+
else_block,
98+
),
99+
_ => {},
100+
}
101+
}
102+
}
56103

57-
// Check if assign operation is done
58-
&& let Some(target) = subtracts_one(cx, then)
104+
fn check_gt(
105+
cx: &LateContext<'_>,
106+
condition_span: Span,
107+
expr_span: Span,
108+
big_var: &Expr<'_>,
109+
little_var: &Expr<'_>,
110+
if_block: &Expr<'_>,
111+
else_block: &Expr<'_>,
112+
) {
113+
if let Some(big_var) = Var::new(big_var)
114+
&& let Some(little_var) = Var::new(little_var)
115+
{
116+
check_subtraction(cx, condition_span, expr_span, big_var, little_var, if_block, else_block);
117+
}
118+
}
59119

60-
// Extracting out the variable name
61-
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
120+
struct Var {
121+
span: Span,
122+
hir_id: HirId,
123+
}
124+
125+
impl Var {
126+
fn new(expr: &Expr<'_>) -> Option<Self> {
127+
path_to_local(expr).map(|hir_id| Self {
128+
span: expr.span,
129+
hir_id,
130+
})
131+
}
132+
}
133+
134+
fn check_subtraction(
135+
cx: &LateContext<'_>,
136+
condition_span: Span,
137+
expr_span: Span,
138+
big_var: Var,
139+
little_var: Var,
140+
if_block: &Expr<'_>,
141+
else_block: &Expr<'_>,
142+
) {
143+
let if_block = peel_blocks(if_block);
144+
let else_block = peel_blocks(else_block);
145+
if is_integer_literal(if_block, 0) {
146+
// We need to check this case as well to prevent infinite recursion.
147+
if is_integer_literal(else_block, 0) {
148+
// Well, seems weird but who knows?
149+
return;
150+
}
151+
// If the subtraction is done in the `else` block, then we need to also revert the two
152+
// variables as it means that the check was reverted too.
153+
check_subtraction(cx, condition_span, expr_span, little_var, big_var, else_block, if_block);
154+
return;
155+
}
156+
if is_integer_literal(else_block, 0)
157+
&& let ExprKind::Binary(op, left, right) = if_block.kind
158+
&& let BinOpKind::Sub = op.node
159+
{
160+
let local_left = path_to_local(left);
161+
let local_right = path_to_local(right);
162+
if Some(big_var.hir_id) == local_left && Some(little_var.hir_id) == local_right {
163+
// This part of the condition is voluntarily split from the one before to ensure that
164+
// if `snippet_opt` fails, it won't try the next conditions.
165+
if let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
166+
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
167+
{
168+
span_lint_and_sugg(
169+
cx,
170+
IMPLICIT_SATURATING_SUB,
171+
expr_span,
172+
"manual arithmetic check found",
173+
"replace it with",
174+
format!("{big_var_snippet}.saturating_sub({little_var_snippet})"),
175+
Applicability::MachineApplicable,
176+
);
177+
}
178+
} else if Some(little_var.hir_id) == local_left
179+
&& Some(big_var.hir_id) == local_right
180+
&& let Some(big_var_snippet) = snippet_opt(cx, big_var.span)
181+
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
62182
{
63-
// Handle symmetric conditions in the if statement
64-
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
65-
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
66-
(cond_left, cond_right)
67-
} else {
68-
return;
69-
}
70-
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
71-
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
72-
(cond_right, cond_left)
73-
} else {
74-
return;
75-
}
183+
span_lint_and_then(
184+
cx,
185+
IMPLICIT_SATURATING_SUB,
186+
condition_span,
187+
"inverted arithmetic check before subtraction",
188+
|diag| {
189+
diag.span_note(
190+
if_block.span,
191+
format!("this subtraction underflows when `{little_var_snippet} < {big_var_snippet}`"),
192+
);
193+
diag.span_suggestion(
194+
if_block.span,
195+
"try replacing it with",
196+
format!("{big_var_snippet} - {little_var_snippet}"),
197+
Applicability::MaybeIncorrect,
198+
);
199+
},
200+
);
201+
}
202+
}
203+
}
204+
205+
fn check_with_condition<'tcx>(
206+
cx: &LateContext<'tcx>,
207+
expr: &Expr<'tcx>,
208+
cond_op: BinOpKind,
209+
cond_left: &Expr<'tcx>,
210+
cond_right: &Expr<'tcx>,
211+
then: &Expr<'tcx>,
212+
) {
213+
// Ensure that the binary operator is >, !=, or <
214+
if (BinOpKind::Ne == cond_op || BinOpKind::Gt == cond_op || BinOpKind::Lt == cond_op)
215+
216+
// Check if assign operation is done
217+
&& let Some(target) = subtracts_one(cx, then)
218+
219+
// Extracting out the variable name
220+
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
221+
{
222+
// Handle symmetric conditions in the if statement
223+
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
224+
if BinOpKind::Gt == cond_op || BinOpKind::Ne == cond_op {
225+
(cond_left, cond_right)
76226
} else {
77227
return;
78-
};
79-
80-
// Check if the variable in the condition statement is an integer
81-
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
228+
}
229+
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
230+
if BinOpKind::Lt == cond_op || BinOpKind::Ne == cond_op {
231+
(cond_right, cond_left)
232+
} else {
82233
return;
83234
}
235+
} else {
236+
return;
237+
};
84238

85-
// Get the variable name
86-
let var_name = ares_path.segments[0].ident.name.as_str();
87-
match cond_num_val.kind {
88-
ExprKind::Lit(cond_lit) => {
89-
// Check if the constant is zero
90-
if let LitKind::Int(Pu128(0), _) = cond_lit.node {
91-
if cx.typeck_results().expr_ty(cond_left).is_signed() {
92-
} else {
93-
print_lint_and_sugg(cx, var_name, expr);
94-
};
95-
}
96-
},
97-
ExprKind::Path(QPath::TypeRelative(_, name)) => {
98-
if name.ident.as_str() == "MIN"
99-
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
100-
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
101-
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
102-
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
103-
{
104-
print_lint_and_sugg(cx, var_name, expr);
105-
}
106-
},
107-
ExprKind::Call(func, []) => {
108-
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
109-
&& name.ident.as_str() == "min_value"
110-
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
111-
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
112-
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
113-
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
114-
{
239+
// Check if the variable in the condition statement is an integer
240+
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
241+
return;
242+
}
243+
244+
// Get the variable name
245+
let var_name = ares_path.segments[0].ident.name.as_str();
246+
match cond_num_val.kind {
247+
ExprKind::Lit(cond_lit) => {
248+
// Check if the constant is zero
249+
if let LitKind::Int(Pu128(0), _) = cond_lit.node {
250+
if cx.typeck_results().expr_ty(cond_left).is_signed() {
251+
} else {
115252
print_lint_and_sugg(cx, var_name, expr);
116-
}
117-
},
118-
_ => (),
119-
}
253+
};
254+
}
255+
},
256+
ExprKind::Path(QPath::TypeRelative(_, name)) => {
257+
if name.ident.as_str() == "MIN"
258+
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
259+
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
260+
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
261+
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
262+
{
263+
print_lint_and_sugg(cx, var_name, expr);
264+
}
265+
},
266+
ExprKind::Call(func, []) => {
267+
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
268+
&& name.ident.as_str() == "min_value"
269+
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
270+
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
271+
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
272+
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
273+
{
274+
print_lint_and_sugg(cx, var_name, expr);
275+
}
276+
},
277+
_ => (),
120278
}
121279
}
122280
}

0 commit comments

Comments
 (0)