Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,13 +417,19 @@ trait UnusedDelimLint {
}
// either function/method call, or something this lint doesn't care about
ref call_or_other => {
let (args_to_check, ctx) = match *call_or_other {
Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
MethodCall(ref call) => (&call.args[..], UnusedDelimsCtx::MethodArg),
let (args_to_check, ctx, callee_from_expansion) = match *call_or_other {
Call(ref callee, ref args) => {
(&args[..], UnusedDelimsCtx::FunctionArg, callee.span.from_expansion())
}
MethodCall(ref call) => (
&call.args[..],
UnusedDelimsCtx::MethodArg,
call.seg.ident.span.from_expansion(),
),
Closure(ref closure)
if matches!(closure.fn_decl.output, FnRetTy::Default(_)) =>
{
(&[closure.body.clone()][..], UnusedDelimsCtx::ClosureBody)
(&[closure.body.clone()][..], UnusedDelimsCtx::ClosureBody, false)
}
// actual catch-all arm
_ => {
Expand All @@ -438,6 +444,11 @@ trait UnusedDelimLint {
return;
}
for arg in args_to_check {
// Whether an expression is wrapped in a block can change which `macro_rules!`
// arm is taken. Don't report the braces as unused in that case. (Issue #158747)
if callee_from_expansion && Self::block_wraps_expanded_expr(arg) {
continue;
}

@folkertdev folkertdev Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this deserves a comment

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe

Whether an expression is wrapped in a block can change what arm of a macro_rules! is taken. Don't report the braces as unused that might be the case.

self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false);
}
return;
Expand Down Expand Up @@ -516,6 +527,20 @@ trait UnusedDelimLint {
false,
);
}

// Returns true for a user-written block whose only expression came from a macro expansion.
fn block_wraps_expanded_expr(value: &ast::Expr) -> bool {

@folkertdev folkertdev Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably could use a comment too

View changes since the review

if let ast::ExprKind::Block(ref block, None) = value.kind
&& block.rules == ast::BlockCheckMode::Default
&& !value.span.from_expansion()
&& let [stmt] = block.stmts.as_slice()
&& let ast::StmtKind::Expr(ref expr) = stmt.kind
{
expr.span.from_expansion()
} else {
false
}
}
}

declare_lint! {
Expand Down
40 changes: 40 additions & 0 deletions tests/ui/lint/unused-braces-macro-arg-issue-158747.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//@ check-pass
//@ run-rustfix

// Removing block braces can change how macro-generated calls match macro arguments.

#![warn(unused_braces)]

macro_rules! type_or_expr {
($x:ty) => {
identity(stringify!($x))
};
($x:expr) => {
identity($x)
};
}

macro_rules! call_expr {
($e:expr) => {
identity($e)
};
}

macro_rules! call_block {
($b:block) => {
identity($b)
};
}

fn identity<T>(x: T) -> T {
x
}

fn main() {
// should not warn
let _ = type_or_expr!({ format!("{}", 1) });
// should not warn
let _ = call_expr!(1);
//~^ WARN unnecessary braces around function argument
let _ = call_block!({ 1 });
}
40 changes: 40 additions & 0 deletions tests/ui/lint/unused-braces-macro-arg-issue-158747.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//@ check-pass
//@ run-rustfix

@folkertdev folkertdev Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these days we often like to put a one-sentence description of what this is testing and why at the top of these files

View changes since the review

// Removing block braces can change how macro-generated calls match macro arguments.

#![warn(unused_braces)]

macro_rules! type_or_expr {
($x:ty) => {
identity(stringify!($x))
};
($x:expr) => {
identity($x)
};
}

macro_rules! call_expr {
($e:expr) => {
identity($e)
};
}

macro_rules! call_block {
($b:block) => {
identity($b)
};
}

fn identity<T>(x: T) -> T {
x
}

fn main() {
// should not warn
let _ = type_or_expr!({ format!("{}", 1) });
// should not warn
let _ = call_expr!({ 1 });
//~^ WARN unnecessary braces around function argument
let _ = call_block!({ 1 });
}
19 changes: 19 additions & 0 deletions tests/ui/lint/unused-braces-macro-arg-issue-158747.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
warning: unnecessary braces around function argument
--> $DIR/unused-braces-macro-arg-issue-158747.rs:37:24
|
LL | let _ = call_expr!({ 1 });
| ^^ ^^
|
note: the lint level is defined here
--> $DIR/unused-braces-macro-arg-issue-158747.rs:6:9
|
LL | #![warn(unused_braces)]
| ^^^^^^^^^^^^^
help: remove these braces
|
LL - let _ = call_expr!({ 1 });
LL + let _ = call_expr!(1);
|

warning: 1 warning emitted

Loading