)]}'
{
  "commit": "b59f80401233cef553ea8b8b7c18295dec96d19e",
  "tree": "7606976da07876fbaf9c2e43de49b6904100b038",
  "parents": [
    "7019104417996b2416b4ae7a8a7f394a29347c56",
    "c31d9703449494c4c0b78bf316f869de23cd2178"
  ],
  "author": {
    "name": "Samuel Tardieu",
    "email": "sam@rfc1149.net",
    "time": "Sat May 23 08:39:18 2026 +0000"
  },
  "committer": {
    "name": "GitHub",
    "email": "noreply@github.com",
    "time": "Sat May 23 08:39:18 2026 +0000"
  },
  "message": "fix(useless_format): fire on  wrapped in a block-producing macro (#17060)\n\n## Summary\n\nFixes a false negative in `useless_format`: the lint silently failed to\nfire on `format!(\"{}\", s)` when the call is the **tail expression of a\nblock produced by another macro**. This affects rustc\u0027s entire\n`define_helper!`-generated family (`with_forced_trimmed_paths!`,\n`with_no_trimmed_paths!`, `with_no_queries!`, etc).\n\nSpotted during review of rust-lang/rust-clippy#17058:\n\n\u003e Even as a macro argument, using `format!(\"{}\", some_string)` should\ntrigger it.\n\n## Reproducer\n\n```rust\n#![feature(decl_macro)]\n#![warn(clippy::useless_format)]\n\nmacro_rules! plain_mr { ($e:expr) \u003d\u003e { $e }; }\nmacro_rules! block_mr { ($e:expr) \u003d\u003e { { let _g: i32 \u003d 0; $e } }; }\npub macro plain_dm($e:expr) { $e }\npub macro block_dm($e:expr) { { let _g: i32 \u003d 0; $e } }\n\nfn s() -\u003e String { String::from(\"x\") }\n\nfn main() {\n    let _ \u003d format!(\"{}\", s());                  // (1) bare         \u003d\u003e lints\n    let _ \u003d plain_mr!(format!(\"{}\", s()));       // (2) mr, no block \u003d\u003e lints\n    let _ \u003d block_mr!(format!(\"{}\", s()));       // (3) mr + block   \u003d\u003e MISS (bug)\n    let _ \u003d plain_dm!(format!(\"{}\", s()));       // (4) dm, no block \u003d\u003e lints\n    let _ \u003d block_dm!(format!(\"{}\", s()));       // (5) dm + block   \u003d\u003e MISS (bug)\n}\n```\n\nBefore this PR, cases (3) and (5) are silently missed. After this PR all\nfive fire as expected. Note that the discriminator is block-wrapping,\nnot `macro_rules` vs `decl_macro`.\n\n## Root cause\n\n`clippy_lints/src/format.rs` previously used\n`root_macro_call_first_node`, which requires `first_node_in_macro \u003d\u003d\nSome(ExpnId::root())`. For `block_dm!(format!(...))`, the `format!` HIR\nparent is the `Block` emitted by `block_dm`, whose span lives in\n`block_dm`\u0027s expansion (sibling to `format!`\u0027s). `first_node_in_macro`\nreturns `Some(block_dm.expn)` rather than `Some(root)`, and the lint\nbails.\n\n## Fix\n\nReplace the gate with:\n\n```rust\nif let Some(macro_call) \u003d matching_root_macro_call(cx, expr.span, sym::format_macro)\n    \u0026\u0026 first_node_in_macro(cx, expr).is_some_and(|p_expn| p_expn !\u003d macro_call.expn)\n```\n\n- `matching_root_macro_call` preserves hygiene. The outermost macro on\n`expr.span`\u0027s backtrace must be `format!`, so a `format!` written inside\nanother macro\u0027s body (where the rewrite would target the macro\ndefinition) is still ignored.\n- `first_node_in_macro(..).is_some_and(|p| p !\u003d macro_call.expn)`\npreserves single-firing. `expr` must be the outermost node of\n`format!`\u0027s expansion. Without `p !\u003d macro_call.expn`, deeper nodes\nwhose parent is also in `format!`\u0027s expansion (including internal\n`format_args!` invocations) would slip through and the lint would fire\nmultiple times per call.\n\n## Test changes\n\n- `tests/ui/format.{rs,fixed,stderr}`: added `#![feature(decl_macro)]`\nand a `block_wrap` module covering all four pass-through and block-wrap\ncombinations across `macro_rules!` and `decl_macro`, as regression\ncoverage.\n- `tests/ui/unused_format_specs.{rs,1.fixed,2.fixed}`: added\n`clippy::useless_format` to the allow-list. The relaxed gate also starts\nfiring on `println!(\"{:.3}\", format!(\"abcde\"))`-style cases, which\nappear in this test\u0027s `.fixed` outputs (after `unused_format_specs`\nsuggests `format_args!` to `format!`). This is a latent true positive\npreviously masked by the strict gate. The test is scoped to\n`unused_format_specs` and should not be entangled with another lint\u0027s\ncoverage. The same pattern is already used by `tests/ui/format.rs`\nitself, which allows other unrelated lints.\n\n## Verification\n\n- `cargo test --test compile-test`: 1764 UI tests + 188 fixed checks +\n46 ui-cargo tests pass. 0 duplicate diagnostics.\n- `cargo test --test dogfood`: catches the same false negative in\nclippy\u0027s own `unnecessary_literal_unwrap.rs` (the line that\nrust-lang/rust-clippy#17059 is fixing manually), demonstrating the fix\non real-world code.\n\n## Related\n\n- Motivating discussion: rust-lang/rust-clippy#17058.\n- Companion manual fix that this PR\u0027s lint now catches automatically:\nrust-lang/rust-clippy#17059.\n- Test file: `tests/ui/format.rs`, see the new `mod block_wrap` at the\nbottom.\n\nchangelog: [`useless_format`] no longer misses `format!` calls that are\nthe tail expression of a block produced by another macro (e.g. rustc\u0027s\n`with_forced_trimmed_paths!`).\n",
  "tree_diff": []
}
