fix(semantic): reject a trailing macro repetition separator like rustc - #10313
Conversation
fe3b861 to
82bf327
Compare
25948c8 to
0c024e9
Compare
82bf327 to
7002ac1
Compare
0c024e9 to
ec48061
Compare
7002ac1 to
96e7d61
Compare
ec48061 to
0be1ab1
Compare
PR SummaryMedium Risk Overview That matches rustc’s Tests and corelib macro examples were updated: repetition matcher tests no longer expect trailing separators on Reviewed by Cursor Bugbot for commit e367efd. Bugbot is set up for automated code reviews on this repo. Configure here. |
96e7d61 to
a9ecb49
Compare
0be1ab1 to
36bb10c
Compare
36bb10c to
7905f7c
Compare
a9ecb49 to
5300e32
Compare
7905f7c to
52d9322
Compare
2748956 to
53e7e0f
Compare
52d9322 to
07e10f2
Compare
eytan-starkware
left a comment
There was a problem hiding this comment.
@eytan-starkware+AGNT made 4 comments.
Reviewable status: 0 of 4 files reviewed, 3 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 1000 at r2 (raw file):
let mut match_count = 0; loop { let mut inner_ctx = ctx.clone();
the check belongs above the ctx.clone(), and temp_iter is thrown away on break so peek() buys nothing over next(). as written every repetition pays one extra full MatcherContext clone on the terminating iteration.
let mut temp_iter = input_iter.clone();
// A separator only ever stands *between* two groups, so from the second group
// on it has to be consumed before the group - and only together with it. A
// separator that no group follows is not part of the repetition, and is left
// in the input for whatever comes after it to match, exactly as rustc's
// `macro_rules!` does.
if match_count > 0
&& let Some(expected_sep) = &expected_separator
{
let Some(ast::TokenTree::Token(token_leaf)) = temp_iter.next() else {
break;
};
if token_leaf.as_syntax_node().get_text_without_trivia(db) != *expected_sep
{
break;
}
}
let mut inner_ctx = ctx.clone();
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 1007 at r2 (raw file):
// in the input for whatever comes after it to match, exactly as rustc's // `macro_rules!` does. if match_count > 0
this puts the formatter and the matcher in direct contradiction.
cairo-format routes every macro-call token tree through the arg-list rules (formatter_impl.rs:960 -> token_tree_as_wrapped_arg_list), and ArgList sets set_comma_if_broken() (node_properties.rs:643). so when a call is long enough to break over lines the formatter adds a trailing comma - see the blessed array![...] output in crates/cairo-lang-formatter/test_data/expected_results/linebreaking.cairo:117, where the input had none.
so after this change, running the formatter over
m!(some_long_arg_one, some_long_arg_two, some_long_arg_three, some_long_arg_four, some_long_arg_five);against ($($x:expr),*) turns compiling code into No matching rule found in inline macro.
and the other direction, which this PR's own test documents (test_data/inline_macros:3010, "the formatter strips a trailing comma from a single-line macro call"), used to be a no-op and now changes the program: stripping the comma is exactly the u16 -> u8 rule flip pinned at test_data/inline_macros:4012.
the two #[cairofmt::skip]s in this PR are the symptom, not the fix. either the formatter stops touching trailing separators inside macro calls (base PR), or this lands as a footgun where formatting breaks the build.
corelib/src/test/language_features/macro_test.cairo line 215 at r2 (raw file):
// A separator only ever stands between two groups, so a trailing one is not part of the // repetition and the calls above reject it - as rustc's `macro_rules!` does. A macro that wants
"the calls above reject it" - there are no calls above any more, you removed them (matcher_plus!(1,), matcher_star!(3,), ...). say it about the macros, not the calls.
53e7e0f to
60c1d28
Compare
07e10f2 to
3c518b8
Compare
60c1d28 to
cb444e0
Compare
fac6c52 to
e02e2e6
Compare
cb444e0 to
cbd3fa7
Compare
A repetition's separator only ever stands between two consecutive groups, so the matcher now consumes it together with the group that follows it, rather than after the group that precedes it. A separator that no group follows is no longer absorbed: it is left in the input for whatever comes after the repetition to match, and if nothing does, the rule does not match. This aligns Cairo with rustc's `macro_rules!`, which rejects `m!(1, 2,)` against `($($x:expr),*)` with "unexpected end of macro invocation". It is a behavior break in both directions, since it can also flip a call from one rule to another: against a macro whose first rule is `($($x:expr),*)` and whose second is `($($x:expr,)*)`, `m!(1, 2,)` used to stop at rule 1 and now falls through to rule 2 - which is what rustc picks too. Trailing-separator tolerance is now spelled explicitly, as in rustc: either `$($x:expr,)*`, putting the separator in the group body, or `$($x:expr),* $(,)?`. The four corelib call sites that relied on the absorbed separator are moved onto a macro that asks for it that way.
e02e2e6 to
e367efd
Compare
cbd3fa7 to
55f3321
Compare

A repetition's separator only ever stands between two consecutive groups,
so the matcher now consumes it together with the group that follows it,
rather than after the group that precedes it. A separator that no group
follows is no longer absorbed: it is left in the input for whatever comes
after the repetition to match, and if nothing does, the rule does not
match.
This aligns Cairo with rustc's
macro_rules!, which rejectsm!(1, 2,)against
($($x:expr),*)with "unexpected end of macro invocation". It isa behavior break in both directions, since it can also flip a call from
one rule to another: against a macro whose first rule is
($($x:expr),*)and whose second is($($x:expr,)*),m!(1, 2,)usedto stop at rule 1 and now falls through to rule 2 - which is what rustc
picks too.
Trailing-separator tolerance is now spelled explicitly, as in rustc:
either
$($x:expr,)*, putting the separator in the group body, or$($x:expr),* $(,)?. The four corelib call sites that relied on theabsorbed separator are moved onto a macro that asks for it that way.