Skip to content

feat(semantic): match and emit non-comma macro repetition separators - #10312

Open
orizi wants to merge 1 commit into
graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enablefrom
graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion
Open

feat(semantic): match and emit non-comma macro repetition separators#10312
orizi wants to merge 1 commit into
graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enablefrom
graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion

Conversation

@orizi

@orizi orizi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

The parser already accepts any single token in a repetition's separator slot, but
repetition_separator still returned None for anything but a comma, so the
semantic side silently ignored it: $($x:ident);* matched m!(a b) and did not
match m!(a; b). The helper now returns the declared token as written, and every
reader of it - the matcher, the expansion, the ?-operator check and the expr
follow-set check - honors it.

The matcher and the expansion needed no change of their own: the matcher already
compared the separator by text against the next call token, and the expansion
already pushed the separator's text between groups. Both were reached only for a
comma; both are now reached for any token. The helper returns a SyntaxNode
rather than a typed terminal, as no reader looks at which token it is - they take
its text, or its stable pointer to report on.

Two declaration checks change with it, which is the point of routing them through
the same helper rather than a scope slip:

  • E2206, a separator on a ? block, now fires for $($x:ident);?. A separator
    only ever appears between two groups and ? allows at most one, and now that
    the matcher consumes the ; this is exactly the hole the check exists to close.
    rustc reports "the ? macro repetition operator does not take a separator" for
    the same pattern.
  • E2209, an expr capture followed by a token that would swallow the pattern's
    own, now sees a non-comma separator as a follower. $($x:expr)|* is rejected,
    $($x:expr);* is not, as ; is in the follow set. rustc reports "$x:expr is
    followed by |, which is not allowed for expr fragments; allowed there are:
    =>, , or ;".

The trailing-separator behavior is byte-identical to before this PR. The matcher
loop that absorbs a trailing separator is untouched - it consumes a separator and
then fails to match another group, exactly as it did for a comma - so a call
writing m!(a; b;) against $($x:ident);* is accepted just as m!(a, b,) was.
F13, aligning that with rustc's rejection, is deliberately left for the next PR;
the existing golden "Test a trailing separator absorbed by a repetition
(deliberate divergence from rustc)" still passes unchanged.

Restack of the parser track onto the semantic stack

This branch is the head of the capture-fidelity sub-stack (72de34a80) with the two
parser-track commits cherry-picked on top. refactor(syntax): generalize the macro repetition separator node conflicted in one region of macro_declaration.rs: the
semantic stack had replaced the whole expand_macro_rule_ex free function with the
ExpansionContext struct that the parser track's diff was anchored to. The
resolution keeps ExpansionContext verbatim and hand-ports the parser track's two
intents onto it - the repetition_separator helper, moved next to
check_repetition_separator since find_first_repetition_param, the function it
had been placed before, no longer exists - and the three remaining
ast::OptionTerminalComma readers that the semantic stack had added or moved:
check_repetition_separator, check_expr_follow_set and
ExpansionContext::expand_repetition. The fourth reader, in
is_macro_rule_match_ex, applied cleanly. feat(parser): accept any single-token macro repetition separator applied with no conflict. No golden moved during the
restack.

Goldens

Four expansion goldens (expr position, test_expand_expr) and three diagnostics
goldens, every one of them cross-checked against rustc's macro_rules!:

  • $($x:expr);* expanding $(total = total + $x);*; into two statements -
    totals!(1 + 2; 3); rustc prints 6.
  • $($x:ident)|* expanding $($x)|* - bits!(A | B); rustc prints 3.
  • the nested $($($x:ident)+);*, which only a non-comma separator makes
    expressible, with two captures per group and two outer groups -
    nested!(A B; C D) expanding to ((A,B),(C,D)); rustc prints ((1, 2), (4, 8)).
  • a zero-match list beside a two-group list in one call -
    parts!([] [A; B]) expanding to ((), (A,B)); rustc prints ((), (1, 2)). The
    two-group list is what makes this golden discriminate: an empty list alone
    matches a rule that ignores its separator just as well.
  • a call writing ,, and a call writing nothing, where the rule writes ; - both
    report E2158 No matching rule found, not a panic. The separator-less m!(a b)
    call is the discriminator here for the same reason: the , call was already
    rejected before this change, by the leftover-input check.
  • the two declaration checks above.

Every one of the seven fails with only this change reverted - that is, with
repetition_separator put back to its comma-only body on top of this same stack:

test expr::test::expand_inline_macros::inline_macros ... FAILED
test expr::test::expr_diagnostics::inline_macros ... FAILED
Test "Test a `;`-separated repetition matched and expanded with its separator." failed.
  `expect_diagnostics` is false, but diagnostics were generated
Test "Test a repetition separated by a token that is neither a comma nor a semicolon." failed.
Test "Test a nested repetition whose outer groups a `;` separates." failed.
Test "Test a `;`-separated repetition matching no input at all." failed.
Test "Test a call separating groups with `,` where the rule separates them with `;`." failed.
Test "Test a `?` pattern repetition taking a non-comma separator." failed.
Test "Test an expr placeholder followed by a separator outside its follow set." failed.
test result: FAILED. 1 passed; 2 failed

No pre-existing golden fails in that run, and none churned when blessing.

Because the two widened declaration checks can reach macro declarations written in
test data of other crates, the churn claim was checked beyond the three gate
crates: no .cairo file under crates/, corelib/ or tests/ declares a
repetition with a non-comma separator, and cairo-lang-starknet,
cairo-lang-plugins, cairo-lang-lowering, cairo-lang-sierra-generator,
cairo-lang-defs, cairo-lang-compiler, cairo-lang-formatter, cairo-lang-doc,
tests, cairo-test corelib (737 passed) and cairo-test tests/bug_samples --starknet (69 passed) are all green.

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

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

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.

@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from fe3b861 to 82bf327 Compare August 4, 2026 08:41
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enable branch from 3ca8704 to c863a6e Compare August 4, 2026 08:56
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from 82bf327 to 7002ac1 Compare August 4, 2026 08:56
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enable branch from c863a6e to bec3c71 Compare August 5, 2026 10:54
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from 7002ac1 to 96e7d61 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 inline macro matching and expansion semantics (a core language feature), though behavior is rustc-aligned and heavily covered by new goldens.

Overview
Macro repetition separators are no longer limited to commas at the semantic layer: repetition_separator returns whatever single token the rule declares (as a SyntaxNode), so matching, expansion, and declaration checks all honor ;, |, and, etc. the same way the parser already allowed.

Calls like m!(a; b) now match $($x:ident);* instead of being treated as separator-less; wrong separators still fail with E2158. Expansion reuses the separator text between groups and inserts spaces when an identifier/keyword separator would otherwise glue to adjacent captures.

Declaration diagnostics routed through the same helper now fire for non-comma cases too: E2206 on $($x:ident);?, and E2209 when expr captures are followed by separators outside ,, ;, or => (e.g. $($x:expr)|*). The duplicate declared_separator_token helper is removed.

New expansion and diagnostics goldens cover ;/|/nested/empty-list cases and separator mismatch behavior (rustc-aligned where noted).

Reviewed by Cursor Bugbot for commit 5300e32. 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/separator-grammar.sep-matcher-expansion branch from 96e7d61 to 9b11f1c Compare August 5, 2026 11:59
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enable branch from bec3c71 to 1ae8fbe Compare August 5, 2026 11: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.

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 9b11f1c. Configure here.

Comment thread crates/cairo-lang-semantic/src/items/macro_declaration.rs Outdated
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from 9b11f1c to e612131 Compare August 5, 2026 12:39

@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 3 files reviewed, all discussions resolved.

Comment thread crates/cairo-lang-semantic/src/items/macro_declaration.rs Outdated
@orizi
orizi changed the base branch from graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enable to graphite-base/10312 August 5, 2026 15:15
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from e612131 to b85c935 Compare August 5, 2026 16:52
@orizi
orizi changed the base branch from graphite-base/10312 to main August 5, 2026 16:53
The parser already accepts any single token in a repetition's separator slot, but
`repetition_separator` still returned `None` for anything but a comma, so the
semantic side silently ignored it: `$($x:ident);*` matched `m!(a b)` and did not
match `m!(a; b)`. The helper now returns the declared token as written, and every
reader of it - the matcher, the expansion, the `?`-operator check and the `expr`
follow-set check - honors it.

The matcher and the expansion needed no change of their own: the matcher already
compared the separator by text against the next call token, and the expansion
already pushed the separator's text between groups. Both were reached only for a
comma; both are now reached for any token. The helper returns a `SyntaxNode`
rather than a typed terminal, as no reader looks at which token it is - they take
its text, or its stable pointer to report on.

Two declaration checks change with it, which is the point of routing them through
the same helper rather than a scope slip:

* E2206, a separator on a `?` block, now fires for `$($x:ident);?`. A separator
  only ever appears between two groups and `?` allows at most one, and now that
  the matcher consumes the `;` this is exactly the hole the check exists to close.
  rustc reports "the `?` macro repetition operator does not take a separator" for
  the same pattern.
* E2209, an `expr` capture followed by a token that would swallow the pattern's
  own, now sees a non-comma separator as a follower. `$($x:expr)|*` is rejected,
  `$($x:expr);*` is not, as `;` is in the follow set. rustc reports "`$x:expr` is
  followed by `|`, which is not allowed for `expr` fragments; allowed there are:
  `=>`, `,` or `;`".

The trailing-separator behavior is byte-identical to before this PR. The matcher
loop that absorbs a trailing separator is untouched - it consumes a separator and
then fails to match another group, exactly as it did for a comma - so a call
writing `m!(a; b;)` against `$($x:ident);*` is accepted just as `m!(a, b,)` was.
F13, aligning that with rustc's rejection, is deliberately left for the next PR;
the existing golden "Test a trailing separator absorbed by a repetition
(deliberate divergence from rustc)" still passes unchanged.

Restack of the parser track onto the semantic stack
---------------------------------------------------

This branch is the head of the capture-fidelity sub-stack (72de34a80) with the two
parser-track commits cherry-picked on top. `refactor(syntax): generalize the macro
repetition separator node` conflicted in one region of `macro_declaration.rs`: the
semantic stack had replaced the whole `expand_macro_rule_ex` free function with the
`ExpansionContext` struct that the parser track's diff was anchored to. The
resolution keeps `ExpansionContext` verbatim and hand-ports the parser track's two
intents onto it - the `repetition_separator` helper, moved next to
`check_repetition_separator` since `find_first_repetition_param`, the function it
had been placed before, no longer exists - and the three remaining
`ast::OptionTerminalComma` readers that the semantic stack had added or moved:
`check_repetition_separator`, `check_expr_follow_set` and
`ExpansionContext::expand_repetition`. The fourth reader, in
`is_macro_rule_match_ex`, applied cleanly. `feat(parser): accept any single-token
macro repetition separator` applied with no conflict. No golden moved during the
restack.

Goldens
-------

Four expansion goldens (expr position, `test_expand_expr`) and three diagnostics
goldens, every one of them cross-checked against rustc's `macro_rules!`:

* `$($x:expr);*` expanding `$(total = total + $x);*;` into two statements -
  `totals!(1 + 2; 3)`; rustc prints 6.
* `$($x:ident)|*` expanding `$($x)|*` - `bits!(A | B)`; rustc prints 3.
* the nested `$($($x:ident)+);*`, which only a non-comma separator makes
  expressible, with two captures per group and two outer groups -
  `nested!(A B; C D)` expanding to `((A,B),(C,D))`; rustc prints `((1, 2), (4, 8))`.
* a zero-match list beside a two-group list in one call -
  `parts!([] [A; B])` expanding to `((), (A,B))`; rustc prints `((), (1, 2))`. The
  two-group list is what makes this golden discriminate: an empty list alone
  matches a rule that ignores its separator just as well.
* a call writing `,`, and a call writing nothing, where the rule writes `;` - both
  report E2158 `No matching rule found`, not a panic. The separator-less `m!(a b)`
  call is the discriminator here for the same reason: the `,` call was already
  rejected before this change, by the leftover-input check.
* the two declaration checks above.

Every one of the seven fails with only this change reverted - that is, with
`repetition_separator` put back to its comma-only body on top of this same stack:

    test expr::test::expand_inline_macros::inline_macros ... FAILED
    test expr::test::expr_diagnostics::inline_macros ... FAILED
    Test "Test a `;`-separated repetition matched and expanded with its separator." failed.
      `expect_diagnostics` is false, but diagnostics were generated
    Test "Test a repetition separated by a token that is neither a comma nor a semicolon." failed.
    Test "Test a nested repetition whose outer groups a `;` separates." failed.
    Test "Test a `;`-separated repetition matching no input at all." failed.
    Test "Test a call separating groups with `,` where the rule separates them with `;`." failed.
    Test "Test a `?` pattern repetition taking a non-comma separator." failed.
    Test "Test an expr placeholder followed by a separator outside its follow set." failed.
    test result: FAILED. 1 passed; 2 failed

No pre-existing golden fails in that run, and none churned when blessing.

Because the two widened declaration checks can reach macro declarations written in
test data of other crates, the churn claim was checked beyond the three gate
crates: no `.cairo` file under `crates/`, `corelib/` or `tests/` declares a
repetition with a non-comma separator, and `cairo-lang-starknet`,
`cairo-lang-plugins`, `cairo-lang-lowering`, `cairo-lang-sierra-generator`,
`cairo-lang-defs`, `cairo-lang-compiler`, `cairo-lang-formatter`, `cairo-lang-doc`,
`tests`, `cairo-test corelib` (737 passed) and `cairo-test tests/bug_samples
--starknet` (69 passed) are all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@orizi
orizi changed the base branch from main to graphite-base/10312 August 6, 2026 11:33
@orizi
orizi force-pushed the graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-matcher-expansion branch from b85c935 to 5300e32 Compare August 6, 2026 11:33
@orizi
orizi changed the base branch from graphite-base/10312 to graph-plan/2026-08-03-macro-fixes/separator-grammar.sep-parser-enable August 6, 2026 11:34
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.

2 participants