feat(rules): add macro correctness checks to Rust review#475
Conversation
|
✅ OpenCodeReview: No supported files changed. |
c125b41 to
917aa41
Compare
|
cc @MuoDoo |
917aa41 to
857a0f5
Compare
|
Self-review follow-up — correcting one item in this PR. While verifying each rule item against macro_rules! add { ($a:expr, $b:expr) => { $a + $b }; }
macro_rules! square { ($x:expr) => { $x * $x }; }
add!(1, 2) * 3 // == 9, not 7
square!(1 + 1) // == 4, not 3Precedence leaking is only reachable through token-tree interpolation: macro_rules! dbl_tt { ($($t:tt)*) => { $($t)* * 2 }; }
dbl_tt!(1 + 1) // == 3 <- leaksI've narrowed that bullet to The other two items are verified as genuinely compiler-invisible:
Happy to adjust scope or wording further. |
There was a problem hiding this comment.
Pull request overview
Adds Rust macro correctness guidance to the system Rust review rules documentation, covering common macro_rules! and procedural-macro footguns to improve defect detection during reviews.
Changes:
- Introduces a new “Macros and Metaprogramming” section with checks for double-evaluation,
$crate::pathing,ttprecedence hazards, proc-macro error reporting, and hygiene/collision pitfalls. - Adds a guard instruction to only flag these items when the diff defines a
macro_rules!or procedural macro (not for ordinary macro invocations).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Only flag when the diff actually defines a `macro_rules!` or procedural macro; do not report on ordinary macro invocations. | ||
| - An `$x:expr` fragment interpolated more than once in the expansion, so the caller's expression — and any side effects — runs multiple times; bind it to a `let` once inside the expansion | ||
| - Exported or publicly used macros that reference items without `$crate::`, so name resolution breaks or binds the wrong item when the macro is invoked from another crate | ||
| - Token-tree (`$t:tt`) fragments re-emitted without parentheses, where operator precedence can silently change the intended meaning; `:expr` fragments and the expansion as a whole are already atomic, so this applies to token-level interpolation only |
There was a problem hiding this comment.
Good catch — rephrased to drop the overloaded term. The bullet now reads “only to token-level (tt) interpolation, since an :expr fragment and a whole expansion are each parsed as one complete expression,” which also avoids colliding with the atomic/atomics usage in the Concurrency section. Pushed.
rust.md covered ownership, unsafe, async and atomics but had no coverage of macros — a high-bug-density area of Rust. Add a scoped "Macros and Metaprogramming" section for the well-known macro_rules!/ proc-macro footguns: $expr double-evaluation, missing $crate::, unparenthesized expansion precedence, and proc-macros panicking instead of emitting compile_error!/syn::Error. Guarded to fire only when a macro is defined, preserving the doc's precision-over-recall bias so it does not flag ordinary macro invocations. No wiring changes: **/*.rs already resolves to rust.md and .rs is already in the extension allowlist.
857a0f5 to
62e8ee9
Compare
MuoDoo
left a comment
There was a problem hiding this comment.
LGTM. Very clean change.
What & why
internal/config/rules/rule_docs/rust.mdis otherwise thorough — ownership/lifetimes,unsafeboundaries, async cancellation, atomics ordering — but has no coverage of macros, a high-bug-density corner of Rust. This adds a scoped#### Macros and Metaprogrammingsection for the well-knownmacro_rules!/ procedural-macro footguns:$x:exprfragment interpolated more than once → the caller's expression (and its side effects) runs multiple times$crate::→ name resolution breaks when invoked from another crate$t:tt) fragments re-emitted without parentheses → operator precedence can silently change meaningpanic!/unwrap()on malformed input instead of emittingsyn::Error/compile_error!The section is guarded — "only flag when the diff actually defines a
macro_rules!or procedural macro" — mirroring how the existing async/concurrency sections gate themselves, so it preserves the doc's precision-over-recall bias and does not fire on ordinary macro invocations.No wiring changes:
**/*.rsalready maps torust.mdinsystem_rules.json, and.rsis already insupported_file_types.json.Why review has to catch these (the compiler doesn't)
Verified against
rustc:$crate::— the defining crate compiles with no warning at all; a consumer crate then fails witherror[E0433]: failed to resolve: use of unresolved module or unlinked crate. The breakage is displaced into someone else's build, which is exactly why it survives local CI.Does it change reviewer behavior? (indicative A/B eval)
The section was injected via the existing
--rulemerge mechanism as the only variable between arms, same model on both arms, against a fixture of planted macro defects:rust.md)Small N — indicative, not statistical.
Disclosure: the fixture originally planted three defects, but follow-up verification with
rustcshowed one of them (operator precedence on an:exprexpansion) is not a real Rust defect — macro expansion is AST-level, so:exprfragments and whole expansions are atomic. That rule bullet has been narrowed tott-fragment interpolation (see the comment below), and the eval's signal should be read as resting on the two independently verified defects above.go test ./internal/config/rules/...passes.