fix(semantic): keep the trailing trivia of an expansion's placeholders and blocks - #10308
Conversation
24ae8b8 to
442faa7
Compare
8e2c448 to
1be1edc
Compare
442faa7 to
3b95ab8
Compare
1be1edc to
2b0c05e
Compare
PR SummaryMedium Risk Overview Expansion now appends trailing trivia after placeholders and
Golden and new expansion tests document rustc divergences (separator-less Reviewed by Cursor Bugbot for commit f93a19e. Bugbot is set up for automated code reviews on this repo. Configure here. |
2b0c05e to
bd5f1e5
Compare
1445a00 to
f375cc8
Compare
bd5f1e5 to
483bc3a
Compare
15c7efd to
9c1f034
Compare
773fc2c to
a47a9a0
Compare
9c1f034 to
673a6ad
Compare
a47a9a0 to
00ba010
Compare
673a6ad to
cf768dc
Compare
00ba010 to
d746ee1
Compare
cf768dc to
facd145
Compare
d746ee1 to
8a4d2ad
Compare
facd145 to
c0ecdb6
Compare
8a4d2ad to
88b27aa
Compare
c0ecdb6 to
e4ed3aa
Compare
e4ed3aa to
3a8337b
Compare
orizi
left a comment
There was a problem hiding this comment.
@orizi+AGNT made 5 comments and resolved 5 discussions.
Reviewable status: 0 of 3 files reviewed, all discussions resolved (waiting on TomerStarkware).
crates/cairo-lang-semantic/src/expr/expansion_test_data/inline_macros line 685 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
this redeclares the exact
pairmacro fromTest expression captures consuming exactly what the parser consumed.above. drop the block and add one line to that test'sexpr_code:let call_spacing = pair!(1 , 2);it already covers the same rule with comment/whitespace variations, and it is already re-blessed here for the capture-side change.
Done - the block is gone and the consuming-exactly test gained the call_spacing line (its comment now covers the spacing claim too).
crates/cairo-lang-semantic/src/expr/test_data/inline_macros line 3717 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
the rule was rewritten to dodge glue this PR does not fix. a
$()block has no trivia between groups, so$($x)*still fuses them:grid!(1, 2; ...)would emit the literal12- same silent value corruption as the_x_ycase the PR is about, one level up.leaving a comment on the rule hides it. add a golden pinning the remaining behavior (e.g.
($($x:expr),*) => { $($x)* }over two captures), or say in the comment that it is a known divergence from rustc, which keeps the groups as separate tokens.
Both taken: added a golden - "Test the groups of a separator-less expansion repetition gluing together." - pinning cat!(1, 2) gluing to the literal 12 as a known divergence from rustc, and the grid comment now states the divergence and points at that pin instead of quietly dodging it.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 907 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
get_text_without_triviainterns the text into the db and you immediatelyto_stringit back - every captured expression in the crate leaves an interned string behind for nothing. take the span off the file content instead:let expr_node = as_expr_macro_token_tree(input_iter, file_id, db)?.as_syntax_node(); // The trivia around the expression belongs to the call, not to the expression - the // expansion spaces the value by its own trivia. text: expr_node.get_text_of_span(db, expr_node.span_without_trivia(db)).to_string(),
Done - the capture takes the span text off the file content directly; nothing is interned.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 1109 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
used once - inline it, like the
MacroParamarm does not.self.expand_repetition(&ast::MacroRepetition::from_syntax_node(db, node))?;
Done.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 1143 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
span_without_triviawalks for the leading width too, which you discard. there is an accessor for exactly this end.let span = TextSpan::new(node.span_end_without_trivia(db), node.span(db).end);
Done.
b23b35a to
7772740
Compare
3a8337b to
358b512
Compare
7772740 to
3fb6f32
Compare
02c983a to
2fe5085
Compare
3fb6f32 to
5bf5857
Compare
2fe5085 to
f1bccda
Compare
5ed765c to
f416c58
Compare
66839ff to
f140843
Compare
f416c58 to
705b401
Compare
f140843 to
fb8f1a6
Compare
705b401 to
b2eff47
Compare
…s and blocks
A macro rule's expansion is turned into text node by node, and the handlers for the two nodes whose
text is replaced - a placeholder and a `$()` block - returned before the terminal case that emits a
node's text, so the trivia those nodes carry was dropped. The space a rule writes after one of its
tokens is the only thing keeping the expanded tokens apart, so dropping it welded them together:
`macro glue { ($a:ident, $b:ident) => { { $a $b } }; }` expanded `glue!(_x, _y)` to `{ _x_y }`, an
identifier the call site then failed to find (E0006), and `macro keep_plus { ($a:ident + $b:ident)
=> { $a + $b }; }` expanded to `x+ y`. `rustc` never welds them: its expansion is a token stream, so
the equivalent `macro_rules!` rule keeps `_x` and `_y` two tokens and reports "macro expansion
ignores identifier `_y` and any tokens following".
`expand_node` now emits the trailing trivia of a placeholder and of a `$()` block after the text
that replaces it.
The leading trivia of those nodes stays dropped, deliberately: a placeholder's value has to start
exactly where its `CodeMapping` does, because `Resolution::new` maps a path of the expanded code
back to the call by `AsSegments::offset`, which is the offset of the path's syntax node *including*
its leading trivia. Emitting the indentation a rule writes before a placeholder moves the value past
the start of its mapping, the mapping is missed, and the value resolves at the definition site
instead - corelib's `sha256::dec_and_append_or_return!`, whose rule indents `$remaining` and `$arr`
on lines of their own, then fails to find them. Nothing is glued by dropping it either: whitespace
before a node is the trailing trivia of the token before it unless a newline separates them, and the
lexer puts that newline in the same trailing trivia, so only indentation is lost.
An `expr` capture is now recorded with `get_text_without_trivia` rather than `get_text`. The trivia
around the expression belongs to the call, not to the captured expression: with the rule's own
trivia now emitted too, keeping it would space the expansion by how the call happened to be written
- `pair!(1 , 2)` expanded to `(1 , 2)` - and it would double the separator wherever both are
present.
New goldens in crates/cairo-lang-semantic/src/expr/expansion_test_data/inline_macros, values
cross-checked by compiling and running the equivalent `macro_rules!` with rustc 1.96.0:
* "Test two placeholders adjacent in the expansion." - the shape above; `{ _x _y }` and E2133
instead of `{ _x_y }` and E0006. rustc rejects it too, with the two tokens separate.
* "Test a placeholder followed by a keyword in the expansion." - `for $i in $arr`, two captures.
The glue used to produce `for vin array![..]`, which no longer parsed; it now compiles clean.
rustc prints 3 for the equivalent call.
* "Test an expansion block whose group ends every placeholder against a keyword." - the same glue
inside a `$()` block, two groups of two captures each, so it has to be avoided per group. rustc
prints 6 for the equivalent call.
* "Test that the spacing of the call does not reach the expansion." - `pair!(1 , 2)` expands to
`(1, 2)`, pinning the capture-side change. rustc prints (1, 2).
Each of the four fails with only this commit's source changes reverted:
Test "Test two placeholders adjacent in the expansion." failed.
Output tag 'expanded_code' does not match:
<{ _x_y}
>{ _x _y }
Output tag 'diagnostics' does not match:
<error[E0006]: Identifier not found.
>error[E2133]: Missing semicolon
Test "Test a placeholder followed by a keyword in the expansion." failed.
`expect_diagnostics` is false, but diagnostics were generated:
Test "Test an expansion block whose group ends every placeholder against a keyword." failed.
`expect_diagnostics` is false, but diagnostics were generated:
Test "Test that the spacing of the call does not reach the expansion." failed.
Output tag 'expanded_code' does not match:
<(1 , 2)
>(1, 2)
The two that fail on the expectation rather than on an output tag report E2117 for the glued `vin` /
`ain` / `bin`, which is the defect; their `expanded_code` differs too, but the runner stops at the
expectation.
Seven existing goldens in that file are re-blessed, all of them the same whitespace being restored:
* "Test call-site `+` matched as a literal token between two captures." `x+ y` -> `x + y`. This is
the bug, not incidental churn: the rule writes `$a + $b` and the space after `$a` was dropped.
* "Test a non-repeating placeholder broadcast to every group of a repeating one."
`(10+ 1,10+ 2)` -> `(10 + 1,10 + 2)`, same rule shape inside a block.
* "Test a repeating placeholder broadcast into a deeper expansion block."
`((10* 1),..)` -> `((10 * 1),..)`, same, one nesting level deeper.
* "Test a deeper expansion block whose non-final broadcast group has no captures."
`((20* 3),..)` -> `((20 * 3),..)`, same rule, a group that matched nothing.
* "Test a doubly nested expansion block flattened into one sum." `1+2+3+4+0` -> `1 +2 +3 +4 + 0`:
the rule is `$($($x +)*)* 0`, so each group contributes `$x` and the space before its `+`, and
the space after the outer block is its trailing trivia.
* "Test a doubly nested expansion block whose non-final group matched nothing." `3+4+0` ->
`3 +4 + 0`, the same rule with an empty group.
* "Test a line comment after an expression capture." `(1 // c<NL>, 2)` -> `(1, 2)`: the comment is
the trailing trivia of the call's `1`, so it is no longer part of what `$a` captured. This is the
rustc value, and the golden's own note said so.
One golden in crates/cairo-lang-semantic/src/expr/test_data/inline_macros changes its rule rather
than its output: "Test expr placeholders in nested repetitions with separators (valid)." asserts
that a pattern is accepted, and its expansion `$($x)* $($($y)*)*` only parsed because the dropped
trivia glued the digits of `1 2 3 4 5 6` into the single literal `123456`. Unglued it is six
juxtaposed literals, so the expansion is now `0 $(+ $x)* $($(+ $y)*)*`, which keeps the rule's
subject - the pattern - untouched.
fb8f1a6 to
f93a19e
Compare

A macro rule's expansion is turned into text node by node, and the handlers for the two nodes whose
text is replaced - a placeholder and a
$()block - returned before the terminal case that emits anode's text, so the trivia those nodes carry was dropped. The space a rule writes after one of its
tokens is the only thing keeping the expanded tokens apart, so dropping it welded them together:
macro glue { ($a:ident, $b:ident) => { { $a $b } }; }expandedglue!(_x, _y)to{ _x_y }, anidentifier the call site then failed to find (E0006), and
macro keep_plus { ($a:ident + $b:ident) => { $a + $b }; }expanded tox+ y.rustcnever welds them: its expansion is a token stream, sothe equivalent
macro_rules!rule keeps_xand_ytwo tokens and reports "macro expansionignores identifier
_yand any tokens following".expand_nodenow emits the trailing trivia of a placeholder and of a$()block after the textthat replaces it.
The leading trivia of those nodes stays dropped, deliberately: a placeholder's value has to start
exactly where its
CodeMappingdoes, becauseResolution::newmaps a path of the expanded codeback to the call by
AsSegments::offset, which is the offset of the path's syntax node includingits leading trivia. Emitting the indentation a rule writes before a placeholder moves the value past
the start of its mapping, the mapping is missed, and the value resolves at the definition site
instead - corelib's
sha256::dec_and_append_or_return!, whose rule indents$remainingand$arron lines of their own, then fails to find them. Nothing is glued by dropping it either: whitespace
before a node is the trailing trivia of the token before it unless a newline separates them, and the
lexer puts that newline in the same trailing trivia, so only indentation is lost.
An
exprcapture is now recorded withget_text_without_triviarather thanget_text. The triviaaround the expression belongs to the call, not to the captured expression: with the rule's own
trivia now emitted too, keeping it would space the expansion by how the call happened to be written
pair!(1 , 2)expanded to(1 , 2)- and it would double the separator wherever both arepresent.
New goldens in crates/cairo-lang-semantic/src/expr/expansion_test_data/inline_macros, values
cross-checked by compiling and running the equivalent
macro_rules!with rustc 1.96.0:{ _x _y }and E2133instead of
{ _x_y }and E0006. rustc rejects it too, with the two tokens separate.for $i in $arr, two captures.The glue used to produce
for vin array![..], which no longer parsed; it now compiles clean.rustc prints 3 for the equivalent call.
inside a
$()block, two groups of two captures each, so it has to be avoided per group. rustcprints 6 for the equivalent call.
pair!(1 , 2)expands to(1, 2), pinning the capture-side change. rustc prints (1, 2).Each of the four fails with only this commit's source changes reverted:
The two that fail on the expectation rather than on an output tag report E2117 for the glued
vin/ain/bin, which is the defect; theirexpanded_codediffers too, but the runner stops at theexpectation.
Seven existing goldens in that file are re-blessed, all of them the same whitespace being restored:
+matched as a literal token between two captures."x+ y->x + y. This isthe bug, not incidental churn: the rule writes
$a + $band the space after$awas dropped.(10+ 1,10+ 2)->(10 + 1,10 + 2), same rule shape inside a block.((10* 1),..)->((10 * 1),..), same, one nesting level deeper.((20* 3),..)->((20 * 3),..), same rule, a group that matched nothing.1+2+3+4+0->1 +2 +3 +4 + 0:the rule is
$($($x +)*)* 0, so each group contributes$xand the space before its+, andthe space after the outer block is its trailing trivia.
3+4+0->3 +4 + 0, the same rule with an empty group.(1 // c<NL>, 2)->(1, 2): the comment isthe trailing trivia of the call's
1, so it is no longer part of what$acaptured. This is therustc value, and the golden's own note said so.
One golden in crates/cairo-lang-semantic/src/expr/test_data/inline_macros changes its rule rather
than its output: "Test expr placeholders in nested repetitions with separators (valid)." asserts
that a pattern is accepted, and its expansion
$($x)* $($($y)*)*only parsed because the droppedtrivia glued the digits of
1 2 3 4 5 6into the single literal123456. Unglued it is sixjuxtaposed literals, so the expansion is now
0 $(+ $x)* $($(+ $y)*)*, which keeps the rule'ssubject - the pattern - untouched.