From b50b5b25c460a5acbb5fa101720452567adcbea8 Mon Sep 17 00:00:00 2001 From: Yukang Date: Sat, 4 Jul 2026 09:51:59 +0800 Subject: [PATCH] Avoid unused braces lint for macro-provided call arguments --- compiler/rustc_lint/src/unused.rs | 33 +++++++++++++-- ...unused-braces-macro-arg-issue-158747.fixed | 40 +++++++++++++++++++ .../unused-braces-macro-arg-issue-158747.rs | 40 +++++++++++++++++++ ...nused-braces-macro-arg-issue-158747.stderr | 19 +++++++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/ui/lint/unused-braces-macro-arg-issue-158747.fixed create mode 100644 tests/ui/lint/unused-braces-macro-arg-issue-158747.rs create mode 100644 tests/ui/lint/unused-braces-macro-arg-issue-158747.stderr diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 5d1b82b0bb48c..4c22310571f1a 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -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 _ => { @@ -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; + } self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false); } return; @@ -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 { + 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! { diff --git a/tests/ui/lint/unused-braces-macro-arg-issue-158747.fixed b/tests/ui/lint/unused-braces-macro-arg-issue-158747.fixed new file mode 100644 index 0000000000000..840b33352939c --- /dev/null +++ b/tests/ui/lint/unused-braces-macro-arg-issue-158747.fixed @@ -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(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 }); +} diff --git a/tests/ui/lint/unused-braces-macro-arg-issue-158747.rs b/tests/ui/lint/unused-braces-macro-arg-issue-158747.rs new file mode 100644 index 0000000000000..3d03ce38fddaa --- /dev/null +++ b/tests/ui/lint/unused-braces-macro-arg-issue-158747.rs @@ -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(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 }); +} diff --git a/tests/ui/lint/unused-braces-macro-arg-issue-158747.stderr b/tests/ui/lint/unused-braces-macro-arg-issue-158747.stderr new file mode 100644 index 0000000000000..4c6ed914cc6a4 --- /dev/null +++ b/tests/ui/lint/unused-braces-macro-arg-issue-158747.stderr @@ -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 +