Skip to content

Commit 3cb4e86

Browse files
committed
Macro call guard and lint suggestion refactor
1 parent 3f42120 commit 3cb4e86

File tree

4 files changed

+61
-450
lines changed

4 files changed

+61
-450
lines changed
Lines changed: 23 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
use clippy_config::Conf;
2-
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::diagnostics::span_lint;
33
use clippy_utils::msrvs::{self, Msrv};
4-
use clippy_utils::source::snippet_with_applicability;
5-
use clippy_utils::sugg::Sugg;
4+
65
use clippy_utils::{is_mutable, path_to_local};
76
use rustc_ast::{BinOpKind, LitIntType, LitKind};
87
use rustc_data_structures::packed::Pu128;
9-
use rustc_errors::Applicability;
10-
use rustc_hir::intravisit::{Visitor, walk_expr, walk_stmt};
11-
use rustc_hir::{Expr, ExprKind, PatKind, QPath};
8+
use rustc_hir::intravisit::{Visitor, walk_expr};
9+
use rustc_hir::{Expr, ExprKind};
1210
use rustc_lint::{LateContext, LateLintPass};
1311
use rustc_middle::ty::{self};
1412
use rustc_session::impl_lint_pass;
15-
use std::fmt::Write;
1613

1714
declare_clippy_lint! {
1815
/// ### What it does
@@ -79,12 +76,16 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
7976
return;
8077
}
8178

79+
// Skip if either lhs or rhs is a macro call
80+
if lhs.span.from_expansion() || rhs.span.from_expansion() {
81+
return;
82+
}
83+
8284
if let BinOpKind::Ge | BinOpKind::Gt | BinOpKind::Le | BinOpKind::Lt = op.node {
8385
SubExprVisitor {
8486
cx,
8587
if_expr: expr,
86-
if_body_block: if_expr.then,
87-
else_block: if_expr.r#else,
88+
8889
condition_lhs: lhs,
8990
condition_rhs: rhs,
9091
condition_op: op.node,
@@ -98,8 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualCheckedSub {
9899
struct SubExprVisitor<'cx, 'tcx> {
99100
cx: &'cx LateContext<'tcx>,
100101
if_expr: &'tcx Expr<'tcx>,
101-
if_body_block: &'tcx Expr<'tcx>,
102-
else_block: Option<&'tcx Expr<'tcx>>,
102+
103103
condition_lhs: &'tcx Expr<'tcx>,
104104
condition_rhs: &'tcx Expr<'tcx>,
105105
condition_op: BinOpKind,
@@ -110,20 +110,25 @@ impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> {
110110
if let ExprKind::Binary(op, sub_lhs, sub_rhs) = expr.kind
111111
&& let BinOpKind::Sub = op.node
112112
{
113+
// // Skip if either sub_lhs or sub_rhs is a macro call
114+
if sub_lhs.span.from_expansion() || sub_rhs.span.from_expansion() {
115+
return;
116+
}
117+
113118
if let ExprKind::Lit(lit) = self.condition_lhs.kind
114119
&& self.condition_op == BinOpKind::Lt
115120
&& let LitKind::Int(Pu128(0), _) = lit.node
116121
&& (is_referencing_same_variable(sub_lhs, self.condition_rhs))
117122
{
118-
self.emit_lint(expr, Some(sub_rhs));
123+
self.emit_lint();
119124
}
120125

121126
if let ExprKind::Lit(lit) = self.condition_rhs.kind
122127
&& self.condition_op == BinOpKind::Gt
123128
&& let LitKind::Int(Pu128(0), _) = lit.node
124129
&& (is_referencing_same_variable(sub_lhs, self.condition_lhs))
125130
{
126-
self.emit_lint(expr, Some(sub_rhs));
131+
self.emit_lint();
127132
}
128133

129134
if self.condition_op == BinOpKind::Ge
@@ -132,7 +137,7 @@ impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> {
132137
&& (is_referencing_same_variable(sub_rhs, self.condition_rhs)
133138
|| are_literals_equal(sub_rhs, self.condition_rhs))
134139
{
135-
self.emit_lint(expr, None);
140+
self.emit_lint();
136141
}
137142

138143
if self.condition_op == BinOpKind::Le
@@ -141,7 +146,7 @@ impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> {
141146
&& (is_referencing_same_variable(sub_rhs, self.condition_lhs)
142147
|| are_literals_equal(sub_rhs, self.condition_lhs))
143148
{
144-
self.emit_lint(expr, None);
149+
self.emit_lint();
145150
}
146151
}
147152

@@ -150,62 +155,12 @@ impl<'tcx> Visitor<'tcx> for SubExprVisitor<'_, 'tcx> {
150155
}
151156

152157
impl<'tcx> SubExprVisitor<'_, 'tcx> {
153-
fn emit_lint(&mut self, expr: &Expr<'tcx>, sub_rhs_expr: Option<&'tcx Expr<'tcx>>) {
154-
let mut applicability = Applicability::MachineApplicable;
155-
156-
let body_snippet = snippet_with_applicability(self.cx, self.if_body_block.span, "..", &mut applicability);
157-
158-
let checked_sub_result_var_name = match self.condition_op {
159-
BinOpKind::Lt | BinOpKind::Gt => generate_unique_var_name("decremented", self.if_body_block),
160-
_ => generate_unique_var_name("difference", self.if_body_block),
161-
};
162-
163-
let binary_expr_snippet = snippet_with_applicability(self.cx, expr.span, "..", &mut applicability);
164-
let updated_usage_context_snippet = body_snippet
165-
.as_ref()
166-
.replace(binary_expr_snippet.as_ref(), &checked_sub_result_var_name);
167-
168-
let lhs_snippet = Sugg::hir(self.cx, self.condition_lhs, "..").maybe_paren();
169-
170-
let rhs_snippet = match sub_rhs_expr {
171-
Some(sub_rhs) => Sugg::hir(self.cx, sub_rhs, "..").maybe_paren(),
172-
None => Sugg::hir(self.cx, self.condition_rhs, "..").maybe_paren(),
173-
};
174-
175-
// TODO: Check result variable is not already in scope
176-
let mut suggestion = match self.condition_op {
177-
BinOpKind::Le => {
178-
format!(
179-
"if let Some({checked_sub_result_var_name}) = {rhs_snippet}.checked_sub({lhs_snippet}) {updated_usage_context_snippet}"
180-
)
181-
},
182-
183-
BinOpKind::Lt => {
184-
format!(
185-
"if let Some({checked_sub_result_var_name}) = {}.checked_sub({rhs_snippet}) {updated_usage_context_snippet}",
186-
Sugg::hir(self.cx, self.condition_rhs, "..").maybe_paren()
187-
)
188-
},
189-
_ => {
190-
format!(
191-
"if let Some({checked_sub_result_var_name}) = {lhs_snippet}.checked_sub({rhs_snippet}) {updated_usage_context_snippet}"
192-
)
193-
},
194-
};
195-
196-
if let Some(else_expr) = self.else_block {
197-
let else_snippet = snippet_with_applicability(self.cx, else_expr.span, "..", &mut applicability);
198-
write!(suggestion, " else {else_snippet}").unwrap();
199-
}
200-
201-
span_lint_and_sugg(
158+
fn emit_lint(&mut self) {
159+
span_lint(
202160
self.cx,
203161
MANUAL_CHECKED_SUB,
204162
self.if_expr.span,
205-
"manual re-implementation of checked subtraction",
206-
"consider using `.checked_sub()`",
207-
suggestion,
208-
applicability,
163+
"manual re-implementation of checked subtraction - consider using `.checked_sub()`",
209164
);
210165
}
211166
}
@@ -236,93 +191,3 @@ fn is_referencing_same_variable<'tcx>(expr1: &'tcx Expr<'_>, expr2: &'tcx Expr<'
236191

237192
false
238193
}
239-
240-
/// Generates a unique variable name based on the provided base name.
241-
/// If the base name already exists in the scope, appends a number to make it unique.
242-
fn generate_unique_var_name<'tcx>(base_name: &str, scope_expr: &'tcx Expr<'tcx>) -> String {
243-
struct VarNameVisitor<'cx> {
244-
base_name: &'cx str,
245-
is_name_in_scope: bool,
246-
}
247-
248-
impl<'tcx> Visitor<'tcx> for VarNameVisitor<'_> {
249-
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
250-
if let ExprKind::Path(QPath::Resolved(_, path)) = &expr.kind
251-
&& let Some(segment) = path.segments.last()
252-
{
253-
let name = segment.ident.name.to_string();
254-
if name == self.base_name {
255-
self.is_name_in_scope = true;
256-
}
257-
}
258-
259-
walk_expr(self, expr);
260-
}
261-
262-
fn visit_stmt(&mut self, stmt: &'tcx rustc_hir::Stmt<'_>) {
263-
if let rustc_hir::StmtKind::Let(let_stmt) = &stmt.kind
264-
&& let PatKind::Binding(_, _, ident, _) = &let_stmt.pat.kind
265-
{
266-
let name = ident.name.to_string();
267-
if name == self.base_name {
268-
self.is_name_in_scope = true;
269-
}
270-
}
271-
272-
walk_stmt(self, stmt);
273-
}
274-
}
275-
276-
let mut visitor = VarNameVisitor {
277-
base_name,
278-
is_name_in_scope: false,
279-
};
280-
281-
if let ExprKind::Block(block, _) = &scope_expr.kind {
282-
for stmt in block.stmts {
283-
visitor.visit_stmt(stmt);
284-
}
285-
286-
// Visit the optional expression at the end of the block
287-
if let Some(expr) = block.expr {
288-
visitor.visit_expr(expr);
289-
}
290-
} else {
291-
visitor.visit_expr(scope_expr);
292-
}
293-
294-
if !visitor.is_name_in_scope {
295-
return base_name.to_string();
296-
}
297-
298-
// If the base name is in scope, append a number
299-
// Keep incrementing until we find a name that's not in scope
300-
let mut counter = 1;
301-
loop {
302-
let candidate = format!("{base_name}_{counter}");
303-
304-
let mut candidate_visitor = VarNameVisitor {
305-
base_name: &candidate,
306-
is_name_in_scope: false,
307-
};
308-
309-
// Check if the candidate name is in scope
310-
if let ExprKind::Block(block, _) = &scope_expr.kind {
311-
for stmt in block.stmts {
312-
candidate_visitor.visit_stmt(stmt);
313-
}
314-
315-
if let Some(expr) = block.expr {
316-
candidate_visitor.visit_expr(expr);
317-
}
318-
} else {
319-
candidate_visitor.visit_expr(scope_expr);
320-
}
321-
322-
if !candidate_visitor.is_name_in_scope {
323-
return candidate;
324-
}
325-
326-
counter += 1;
327-
}
328-
}

tests/ui/manual_checked_sub.fixed

Lines changed: 0 additions & 137 deletions
This file was deleted.

0 commit comments

Comments
 (0)