Skip to content

fix(semantic): reject a macro rule pattern that reuses a placeholder name - #10302

Open
orizi wants to merge 1 commit into
graph-plan/2026-08-03-macro-fixes/foundation.expand-per-groupfrom
graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3
Open

orizi wants to merge 1 commit into
graph-plan/2026-08-03-macro-fixes/foundation.expand-per-groupfrom
graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3

Conversation

@orizi

@orizi orizi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

A pattern using one name for two placeholders leaves the nesting depth of the
values it stands for ambiguous, so nothing can decide which of the two the
expansion means. It is now a declaration-time error, E2204, reported once per
reused name at that name's second use in the pattern. The rule's err is set,
so it is not expanded: neither the expression-position path (rule.err?) nor
the item-position one emits a second diagnostic on top of it. This resolves the
TODO(Dean): Verify uniqueness of param names..

The expansion check is skipped for such a rule. Its placeholder paths keep the
last occurrence of a reused name, so E2198/E2199 would measure the expansion
against an arbitrary depth and report the pattern's ambiguity as a defect of the
expansion. ([$([$($p:ident),*]),*] [$($p:ident),*]) => { fn $p() {} } reported
E2198: ... 'p' requires 1 repetition level(s) ... before this change - p is
at depth 2 in its first occurrence, and the "1" came from the second. That
misfire is replaced, not doubled up on.

Base re-verification. The witness ($($x:expr),* ; $x:expr) => { $x } on
m!(1, 2; 3) no longer expands to 1: driving an expansion block by the
repetition at its own depth already turned it into an expansion-time E2202
("has no captured value in this repetition") with no expansion. The
declaration-time defect is untouched, and other shapes still generate silently
wrong code - ([$($x:ident),*] [$($x:ident),*]) => { 0 $(+ $x)* } on
dup!([a, b][c, d]) expanded to 0 + a + b + c + d, concatenating both sibling
groups, and ($p:ident, $p:ident, $p:ident) => { $p } on dup!(a, b, c)
expanded to a. Both are E2204 now.

A hard error rather than a softer rollout: no Cairo macro rule in corelib/,
tests/, crates/ or examples/ reuses a placeholder name within one pattern -
the only hit was the golden re-blessed below. cairo-test -- tests/bug_samples --starknet (68 passed) and cairo-test -- corelib --filter macro (49 passed)
confirm it against the real corelib.

rustc's macro_rules! agrees, rejecting all four shapes at definition time with
error: duplicate matcher binding, its caret on the whole duplicate matcher
($x:expr) - the same anchor E2204 uses. ($($a:ident),* ; $b:expr) is
accepted there as it is here.

The re-blessed golden, "a repetition whose placeholder shares its name with a
non-repeating one", was the repro of the E2202 added when the expansion failure
paths stopped panicking, and its trigger was a duplicate name
(($y:ident, $($y:ident),*)). It now reports E2204 at the pattern's second
$y:ident instead of E2202 at the expansion's $($y),* block, so it was
retitled and its comment - which asserted that names are not verified to be
unique - rewritten. It doubles as the different-nesting-depths case. E2202 is
left without a golden as a result; no non-duplicate-name trigger for it is
known, and its arms are kept because a rule with a reused name is still
matched - every rule is matched before its err is honored - so the
value-dropping paths in the matcher stay reachable and stay defensive.

Names are unique per pattern, not per macro: a later rule of the same macro may
reuse them, which the negative-control golden pins.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

orizi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from e3f372f to 8cf3e0b Compare August 4, 2026 08:41
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch 2 times, most recently from 08026fe to b654579 Compare August 5, 2026 10:54
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from 8cf3e0b to acb286b Compare August 5, 2026 10:54
@orizi
orizi marked this pull request as ready for review August 5, 2026 11:10
@cursor

cursor Bot commented Aug 5, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes macro declaration semantics and error reporting for user-defined inline macros; invalid rules no longer expand, which may surface new compile errors but aligns with rustc and fixes previously silent wrong expansions.

Overview
User-defined inline macro rules now reject patterns that bind the same placeholder name more than once, emitting E2204 at the second binding and marking the rule invalid so it never expands (calls stay as written).

Declaration checking uses a new PatternPlaceholders walk to detect reused names and skips expansion-side placeholder validation for those rules, avoiding misleading depth/driver errors that came from treating the last occurrence as authoritative. Expansion repetition checking is refactored to precompute per-$() block driving depths via collect_block_driving_depths instead of scanning subtrees on the fly.

Tests are updated/added for sibling repetitions, post-repetition reuse, triple reuse (single diagnostic), and the case where distinct names across rules in one macro remain valid.

Reviewed by Cursor Bugbot for commit 99e75ec. Bugbot is set up for automated code reviews on this repo. Configure here.

@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from b654579 to e8f1e18 Compare August 5, 2026 11:59
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch 2 times, most recently from 1cf10bb to d009269 Compare August 5, 2026 15:15

@orizi orizi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@orizi+AGNT made 1 comment and resolved 1 discussion.
Reviewable status: 0 of 7 files reviewed, all discussions resolved (waiting on eytan-starkware and TomerStarkware).

let mut diagnostics = SemanticDiagnostics::new(callsite_module_id);
// Skipping the expansion of a macro call that had a parser error, as the reported parser errors
// already describe the problem.
if macro_call_syntax.as_syntax_node().descendants(db).any(|node| node.kind(db).is_missing()) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed at the source - the guard is #10297's; it now uses contains_missing, and this PR inherits it.

@eytan-starkware eytan-starkware left a comment

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.

@eytan-starkware+AGNT made 3 comments.
Reviewable status: 0 of 7 files reviewed, 2 unresolved discussions (waiting on eytan-starkware, orizi, and TomerStarkware).


a discussion (no related file):
Note: the comments below are from an automatic orizi-review run (Claude agents reviewing in Ori's style, findings adversarially verified before posting). Treat with the usual bot skepticism.


crates/cairo-lang-semantic/src/items/macro_declaration.rs line 377 at r1 (raw file):

    reused_names: OrderedHashMap<SmolStrId<'db>, SyntaxStablePtrId<'db>>,
    /// The path of the pattern elements being traversed.
    current_path: Vec<usize>,

current_path and next_rep_id are recursion scratch, not "placeholders the pattern defines" - after collect one is empty and the other meaningless, and a second collect on the same value keeps numbering from the old counter. keep them as params like the free function did and hand back the result:

impl<'db> PatternPlaceholders<'db> {
    fn collect(
        db: &'db dyn Database,
        elements: impl IntoIterator<Item = ast::MacroElement<'db>>,
    ) -> Self {
        let mut res = Self::default();
        res.collect_elements(db, elements, &mut vec![], &mut 0);
        res
    }
}

caller becomes let placeholders = PatternPlaceholders::collect(db, pattern_elements.elements(db));.


crates/cairo-lang-semantic/src/diagnostic.rs line 1218 at r1 (raw file):

            SemanticDiagnosticKind::DuplicateMacroPlaceholder(name) => {
                format!(
                    "Macro placeholder '{}' is already captured by this rule's pattern. A \

second sentence restates the first, and it's the only two-sentence message among its neighbours (Undefined macro placeholder: 'x'.).

                format!(
                    "Macro placeholder '{}' is already captured by this rule's pattern.",
                    name.long(db)
                )

@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch 2 times, most recently from 33e5dcd to a6418d1 Compare August 17, 2026 18:03

@orizi orizi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@orizi+AGNT made 2 comments and resolved 2 discussions.
Reviewable status: 0 of 7 files reviewed, all discussions resolved (waiting on TomerStarkware).


crates/cairo-lang-semantic/src/diagnostic.rs line 1218 at r1 (raw file):

Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…

second sentence restates the first, and it's the only two-sentence message among its neighbours (Undefined macro placeholder: 'x'.).

                format!(
                    "Macro placeholder '{}' is already captured by this rule's pattern.",
                    name.long(db)
                )

Trimmed to the one sentence.


crates/cairo-lang-semantic/src/items/macro_declaration.rs line 377 at r1 (raw file):

Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…

current_path and next_rep_id are recursion scratch, not "placeholders the pattern defines" - after collect one is empty and the other meaningless, and a second collect on the same value keeps numbering from the old counter. keep them as params like the free function did and hand back the result:

impl<'db> PatternPlaceholders<'db> {
    fn collect(
        db: &'db dyn Database,
        elements: impl IntoIterator<Item = ast::MacroElement<'db>>,
    ) -> Self {
        let mut res = Self::default();
        res.collect_elements(db, elements, &mut vec![], &mut 0);
        res
    }
}

caller becomes let placeholders = PatternPlaceholders::collect(db, pattern_elements.elements(db));.

Done, as suggested - collect builds and returns, collect_elements carries the scratch as params.

@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from a6418d1 to 0c878f6 Compare August 23, 2026 08:43
@orizi
orizi changed the base branch from main to graphite-base/10302 August 23, 2026 10:31
@orizi
orizi force-pushed the graphite-base/10302 branch from 302fe00 to c8a4835 Compare August 23, 2026 10:31
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from 0c878f6 to 37a120f Compare August 23, 2026 10:31
@orizi
orizi changed the base branch from graphite-base/10302 to graph-plan/2026-08-03-macro-fixes/formatter-no-macro-trailing-separator August 23, 2026 10:32
@orizi
orizi changed the base branch from graph-plan/2026-08-03-macro-fixes/formatter-no-macro-trailing-separator to graphite-base/10302 August 25, 2026 12:30
@orizi
orizi force-pushed the graphite-base/10302 branch from c8a4835 to e0c6b94 Compare August 25, 2026 12:30
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from 37a120f to 14ed2f7 Compare August 25, 2026 12:30
@orizi
orizi changed the base branch from graphite-base/10302 to graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group August 25, 2026 12:30
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from e0c6b94 to baae183 Compare August 26, 2026 19:59
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from 14ed2f7 to c3c400b Compare August 26, 2026 19:59

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c3c400b. Configure here.

Comment thread crates/cairo-lang-semantic/src/expr/test_data/inline_macros Outdated
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from c3c400b to e8a781b Compare August 27, 2026 04:32
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from 630f42b to 6d44048 Compare August 27, 2026 13:33
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from e8a781b to 50f49dc Compare August 27, 2026 13:33
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from 6d44048 to e892b4a Compare August 28, 2026 10:07
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch 2 times, most recently from 7758d81 to 5bd7273 Compare August 28, 2026 12:58
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from e892b4a to 2515ca7 Compare August 28, 2026 12:58
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from 5bd7273 to 5739b7d Compare September 14, 2026 04:51
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from 2515ca7 to 5563631 Compare September 14, 2026 04:51
…name

A pattern using one name for two placeholders leaves the nesting depth of the
values it stands for ambiguous, so nothing can decide which of the two the
expansion means. It is now a declaration-time error, E2204, reported once per
reused name at that name's second use in the pattern. The rule's `err` is set,
so it is not expanded: neither the expression-position path (`rule.err?`) nor
the item-position one emits a second diagnostic on top of it. This resolves the
`TODO(Dean): Verify uniqueness of param names.`.

The expansion check is skipped for such a rule. Its placeholder paths keep the
*last* occurrence of a reused name, so E2198/E2199 would measure the expansion
against an arbitrary depth and report the pattern's ambiguity as a defect of the
expansion. `([$([$($p:ident),*]),*] [$($p:ident),*]) => { fn $p() {} }` reported
`E2198: ... 'p' requires 1 repetition level(s) ...` before this change - `p` is
at depth 2 in its first occurrence, and the "1" came from the second. That
misfire is replaced, not doubled up on.

Base re-verification. The witness `($($x:expr),* ; $x:expr) => { $x }` on
`m!(1, 2; 3)` no longer expands to `1`: driving an expansion block by the
repetition at its own depth already turned it into an expansion-time E2202
("has no captured value in this repetition") with no expansion. The
declaration-time defect is untouched, and other shapes still generate silently
wrong code - `([$($x:ident),*] [$($x:ident),*]) => { 0 $(+ $x)* }` on
`dup!([a, b][c, d])` expanded to `0 + a + b + c + d`, concatenating both sibling
groups, and `($p:ident, $p:ident, $p:ident) => { $p }` on `dup!(a, b, c)`
expanded to `a`. Both are E2204 now.

A hard error rather than a softer rollout: no Cairo macro rule in `corelib/`,
`tests/`, `crates/` or `examples/` reuses a placeholder name within one pattern -
the only hit was the golden re-blessed below. `cairo-test -- tests/bug_samples
--starknet` (68 passed) and `cairo-test -- corelib --filter macro` (49 passed)
confirm it against the real corelib.

rustc's `macro_rules!` agrees, rejecting all four shapes at definition time with
`error: duplicate matcher binding`, its caret on the whole duplicate matcher
(`$x:expr`) - the same anchor E2204 uses. `($($a:ident),* ; $b:expr)` is
accepted there as it is here.

The re-blessed golden, "a repetition whose placeholder shares its name with a
non-repeating one", was the repro of the E2202 added when the expansion failure
paths stopped panicking, and its trigger was a duplicate name
(`($y:ident, $($y:ident),*)`). It now reports E2204 at the pattern's second
`$y:ident` instead of E2202 at the expansion's `$($y),*` block, so it was
retitled and its comment - which asserted that names are not verified to be
unique - rewritten. It doubles as the different-nesting-depths case. E2202 is
left without a golden as a result; no non-duplicate-name trigger for it is
known, and its arms are kept because a rule with a reused name is still
*matched* - every rule is matched before its `err` is honored - so the
value-dropping paths in the matcher stay reachable and stay defensive.

Names are unique per pattern, not per macro: a later rule of the same macro may
reuse them, which the negative-control golden pins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/foundation.expand-per-group branch from 5563631 to e7dc075 Compare September 14, 2026 06:56
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/matcher-correctness.pr2-duplicate-placeholder-f3 branch from 5739b7d to 99e75ec Compare September 14, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants