Skip to content

Commit 13bd9b5

Browse files
committed
fix: needless_continue wrongly unmangled macros
1 parent 7117bd9 commit 13bd9b5

File tree

3 files changed

+140
-32
lines changed

3 files changed

+140
-32
lines changed

clippy_lints/src/needless_continue.rs

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use clippy_utils::diagnostics::span_lint_and_help;
2-
use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_context};
3-
use clippy_utils::{higher, is_from_proc_macro};
2+
use clippy_utils::higher;
3+
use clippy_utils::source::{indent_of, snippet_block, snippet_with_context};
44
use rustc_ast::Label;
55
use rustc_errors::Applicability;
66
use rustc_hir::{Block, Expr, ExprKind, LoopSource, StmtKind};
77
use rustc_lint::{LateContext, LateLintPass, LintContext};
88
use rustc_session::declare_lint_pass;
9-
use rustc_span::Span;
9+
use rustc_span::{ExpnKind, Span};
1010

1111
declare_clippy_lint! {
1212
/// ### What it does
@@ -132,7 +132,9 @@ declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]);
132132

133133
impl<'tcx> LateLintPass<'tcx> for NeedlessContinue {
134134
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
135-
if !expr.span.in_external_macro(cx.sess().source_map()) && !is_from_proc_macro(cx, expr) {
135+
// We cannot use `from_expansion` because for loops, while loops and while let loops are desugared
136+
// into `loop` expressions.
137+
if !matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(..)) {
136138
check_and_warn(cx, expr);
137139
}
138140
}
@@ -193,16 +195,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessContinue {
193195
/// - The expression node is a block with the first statement being a `continue`.
194196
fn needless_continue_in_else(else_expr: &Expr<'_>, label: Option<&Label>) -> bool {
195197
match else_expr.kind {
196-
ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
198+
ExprKind::Block(else_block, _) => is_first_block_stmt_continue(else_block, label),
197199
ExprKind::Continue(l) => compare_labels(label, l.label.as_ref()),
198200
_ => false,
199201
}
200202
}
201203

202204
fn is_first_block_stmt_continue(block: &Block<'_>, label: Option<&Label>) -> bool {
203205
block.stmts.first().is_some_and(|stmt| match stmt.kind {
204-
StmtKind::Semi(ref e) | StmtKind::Expr(ref e) => {
205-
if let ExprKind::Continue(ref l) = e.kind {
206+
StmtKind::Semi(e) | StmtKind::Expr(e) => {
207+
if let ExprKind::Continue(l) = e.kind {
206208
compare_labels(label, l.label.as_ref())
207209
} else {
208210
false
@@ -225,10 +227,10 @@ fn compare_labels(loop_label: Option<&Label>, continue_label: Option<&Label>) ->
225227
}
226228

227229
/// If `expr` is a loop expression (while/while let/for/loop), calls `func` with
228-
/// the AST object representing the loop block of `expr`.
229-
fn with_loop_block<'tcx, F>(expr: &Expr<'tcx>, mut func: F)
230+
/// the HIR object representing the loop block of `expr`.
231+
fn with_loop_block<F>(expr: &Expr<'_>, mut func: F)
230232
where
231-
F: FnMut(&Block<'tcx>, Option<&Label>),
233+
F: FnMut(&Block<'_>, Option<&Label>),
232234
{
233235
if let Some(higher::ForLoop { body, label, .. }) = higher::ForLoop::hir(expr)
234236
&& let ExprKind::Block(block, _) = &body.kind
@@ -264,9 +266,9 @@ where
264266
/// - The `if` condition expression,
265267
/// - The `then` block, and
266268
/// - The `else` expression.
267-
fn with_if_expr<'tcx, F>(expr: &Expr<'tcx>, mut func: F)
269+
fn with_if_expr<F>(expr: &Expr<'_>, mut func: F)
268270
where
269-
F: FnMut(&Expr<'tcx>, &Expr<'tcx>, &Block<'tcx>, &Expr<'tcx>),
271+
F: FnMut(&Expr<'_>, &Expr<'_>, &Block<'_>, &Expr<'_>),
270272
{
271273
if let Some(higher::If {
272274
cond,
@@ -288,20 +290,20 @@ enum LintType {
288290

289291
/// Data we pass around for construction of help messages.
290292
#[derive(Debug)]
291-
struct LintData<'tcx> {
293+
struct LintData<'hir> {
292294
/// The `if` expression encountered in the above loop.
293-
if_expr: &'tcx Expr<'tcx>,
295+
if_expr: &'hir Expr<'hir>,
294296
/// The condition expression for the above `if`.
295-
if_cond: &'tcx Expr<'tcx>,
297+
if_cond: &'hir Expr<'hir>,
296298
/// The `then` block of the `if` statement.
297-
if_block: &'tcx Block<'tcx>,
299+
if_block: &'hir Block<'hir>,
298300
/// The `else` block of the `if` statement.
299301
/// Note that we only work with `if` exprs that have an `else` branch.
300-
else_expr: &'tcx Expr<'tcx>,
302+
else_expr: &'hir Expr<'hir>,
301303
/// The 0-based index of the `if` statement in the containing loop block.
302304
stmt_idx: Option<usize>,
303305
/// The statements of the loop block.
304-
loop_block: &'tcx Block<'tcx>,
306+
loop_block: &'hir Block<'hir>,
305307
}
306308

307309
const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
@@ -366,7 +368,14 @@ fn suggestion_snippet_for_continue_inside_if(cx: &LateContext<'_>, data: &LintDa
366368
}
367369

368370
fn suggestion_snippet_for_continue_inside_else(cx: &LateContext<'_>, data: &LintData<'_>) -> String {
369-
let cond_code = snippet(cx, data.if_cond.span, "..");
371+
let mut applicability = Applicability::MachineApplicable;
372+
let (cond_code, _) = snippet_with_context(
373+
cx,
374+
data.if_cond.span,
375+
data.if_expr.span.ctxt(),
376+
"..",
377+
&mut applicability,
378+
);
370379

371380
// Region B
372381
let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)));
@@ -402,7 +411,7 @@ fn suggestion_snippet_for_continue_inside_else(cx: &LateContext<'_>, data: &Lint
402411
}
403412
lines.join("\n")
404413
} else {
405-
"".to_string()
414+
String::new()
406415
};
407416

408417
let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
@@ -417,7 +426,7 @@ fn check_last_stmt_in_expr<F>(cx: &LateContext<'_>, inner_expr: &Expr<'_>, func:
417426
where
418427
F: Fn(Option<&Label>, Span),
419428
{
420-
match &inner_expr.kind {
429+
match inner_expr.kind {
421430
ExprKind::Continue(continue_label) => {
422431
func(continue_label.label.as_ref(), inner_expr.span);
423432
},
@@ -432,7 +441,7 @@ where
432441
if !match_ty.is_unit() && !match_ty.is_never() {
433442
return;
434443
}
435-
for arm in arms.iter() {
444+
for arm in arms {
436445
check_last_stmt_in_expr(cx, arm.body, func);
437446
}
438447
},

tests/ui/needless_continue.rs

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,99 @@ mod issue_4077 {
246246
}
247247

248248
#[allow(clippy::let_unit_value)]
249-
fn issue14550(mut producer: impl Iterator<Item = Result<i32, u32>>) -> Result<u32, u32> {
250-
let mut counter = 2;
251-
loop {
252-
match producer.next().unwrap() {
253-
Ok(ok) => break Ok((ok + 1) as u32),
254-
Err(12) => {
255-
counter -= 1;
249+
mod issue14550 {
250+
fn match_with_value(mut producer: impl Iterator<Item = Result<i32, u32>>) -> Result<u32, u32> {
251+
let mut counter = 2;
252+
loop {
253+
match producer.next().unwrap() {
254+
Ok(ok) => break Ok((ok + 1) as u32),
255+
Err(12) => {
256+
counter -= 1;
257+
continue;
258+
},
259+
err => err?,
260+
};
261+
}
262+
}
263+
264+
fn inside_macro() {
265+
macro_rules! mac {
266+
($e:expr => $($rest:tt);*) => {
267+
loop {
268+
match $e {
269+
1 => continue,
270+
2 => break,
271+
n => println!("{n}"),
272+
}
273+
$($rest;)*
274+
}
275+
};
276+
}
277+
278+
mac!(2 => );
279+
mac!(1 => {println!("foobar")});
280+
}
281+
282+
mod partially_inside_macro {
283+
macro_rules! select {
284+
(
285+
$expr:expr,
286+
$( $pat:pat => $then:expr ),*
287+
) => {
288+
fn foo() {
289+
loop {
290+
match $expr {
291+
$(
292+
$pat => $then,
293+
)*
294+
}
295+
}
296+
}
297+
};
298+
}
299+
300+
select!(Some(1),
301+
Some(1) => {
302+
println!("one");
256303
continue;
257304
},
258-
err => err?,
259-
};
305+
Some(2) => {},
306+
None => break,
307+
_ => ()
308+
);
309+
310+
macro_rules! choose {
311+
(
312+
$expr:expr,
313+
$case:expr
314+
) => {
315+
fn bar() {
316+
loop {
317+
match $expr {
318+
$case => {
319+
println!("matched");
320+
continue;
321+
},
322+
_ => {
323+
println!("not matched");
324+
break;
325+
},
326+
}
327+
}
328+
}
329+
};
330+
}
331+
332+
choose!(todo!(), 5);
333+
}
334+
}
335+
336+
fn issue15548() {
337+
loop {
338+
if todo!() {
339+
} else {
340+
//~^ needless_continue
341+
continue;
342+
}
260343
}
261344
}

tests/ui/needless_continue.stderr

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,21 @@ LL | | }
220220
do_something();
221221
}
222222

223-
error: aborting due to 15 previous errors
223+
error: this `else` block is redundant
224+
--> tests/ui/needless_continue.rs:339:16
225+
|
226+
LL | } else {
227+
| ________________^
228+
LL | |
229+
LL | | continue;
230+
LL | | }
231+
| |_________^
232+
|
233+
= help: consider dropping the `else` clause and merging the code that follows (in the loop) with the `if` block
234+
if todo!() {
235+
// merged code follows:
236+
237+
}
238+
239+
error: aborting due to 16 previous errors
224240

0 commit comments

Comments
 (0)