fix(semantic): reject the malformed macro rule declarations that silently do nothing - #10304
Conversation
ac7ee75 to
a53a9fc
Compare
bd8be0e to
91fa83e
Compare
| } | ||
| SemanticDiagnosticKind::MacroPlaceholderNamedAfterResolverModifier(name) => { | ||
| format!( | ||
| "`${}` is a resolver modifier, so a macro placeholder may not be named after \ |
There was a problem hiding this comment.
the explaination seems like too much.
There was a problem hiding this comment.
Shortened both messages to the bare statement.
There was a problem hiding this comment.
Shortened to the bare statement.
| "Macro repetition block is empty, so it matches nothing.".into() | ||
| } | ||
| SemanticDiagnosticKind::MacroRuleWithUnparsablePattern => { | ||
| "This macro rule's pattern could not be parsed, so the rule can never match a call." |
There was a problem hiding this comment.
the explaination seems like too much.
There was a problem hiding this comment.
Shortened to the bare statement.
PR SummaryMedium Risk Overview Macro declaration analysis now walks pattern repetitions and subtrees to reject empty New diagnostics E2205–E2208 are wired in Reviewed by Cursor Bugbot for commit 8372386. Bugbot is set up for automated code reviews on this repo. Configure here. |
a53a9fc to
e2eb1e2
Compare
c1f115d to
967a7ef
Compare
e2eb1e2 to
09a334a
Compare
1807ca6 to
e6ca70b
Compare
c860e0c to
4e8aa3e
Compare
eytan-starkware
left a comment
There was a problem hiding this comment.
@eytan-starkware+AGNT made 7 comments.
Reviewable status: 0 of 3 files reviewed, 6 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/expr/test_data/inline_macros line 3282 at r3 (raw file):
//! > ========================================================================== //! > Test a pattern capturing into the name `$defsite`.
every new case here is a top-level pattern element. check_pattern_elements recurses into repetitions and subtrees and none of that recursion is pinned - add ($($defsite:ident)*), ([$defsite:ident]) and a ?-with-separator nested inside an outer repetition.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 196 at r3 (raw file):
/// `Err` if any was reported, for the caller to mark the rule with - a defective element makes the /// rule's meaning unclear, so it must not expand. fn check_pattern_elements<'db>(
third full traversal of the pattern with the same four match arms - PatternPlaceholders::collect right below already visits every Param/Repetition/Subtree and already computes the placeholder name (so check_placeholder_name re-interns it). fold the name check into collect - it has both the name and the ptr - and this shrinks to the two repetition checks.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 259 at r3 (raw file):
diagnostics: &mut SemanticDiagnostics<'db>, ) -> Maybe<()> { let ast::OptionTerminalComma::TerminalComma(separator) = repetition.separator(db) else {
let-chain:
if let ast::OptionTerminalComma::TerminalComma(separator) = repetition.separator(db)
&& matches!(repetition.operator(db), ast::MacroRepetitionOperator::ZeroOrOne(_))
{
return Err(diagnostics.report(
separator.stable_ptr(db).untyped(),
SemanticDiagnosticKind::MacroRepetitionSeparatorWithZeroOrOne,
));
}
Ok(())
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 408 at r3 (raw file):
// which a reused name leaves ambiguous - checking it then reports the ambiguity as // a defect of the expansion, which it is not. if placeholders.reused_names.is_empty() {
this was rule_err.is_ok(), which for an unparsed pattern was always true, so the expansion was checked. now it's reused_names.is_empty(), which for an unparsed pattern depends on whether recovery happened to leave two nameless placeholders - i.e. a coin flip on whether a dropped rule's expansion gets diagnostics.
make it explicit: hoist the !pattern_is_parsed report + continue to the top of the loop, right after let pattern = rule_syntax.lhs(db);. then the if pattern_is_parsed wrapper, the nameless-placeholders comment above it and this whole question go away.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 426 at r3 (raw file):
// it. It is reported so that a call written for it points at the parse error rather // than failing with a bare "no matching rule". diagnostics.report(
the comment says this is so "a call written for it points at the parse error rather than failing with a bare 'no matching rule'" - but it doesn't. look at the pair golden: E1008 from the parser on the same rule, then this E2208 on an overlapping span, then the same E2158 on the call, unchanged. three diagnostics for one typo, and the confusing one is still there.
fix the comment, or make it actually replace the E2158.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 613 at r3 (raw file):
if let Some(repetition) = ast::MacroRepetition::cast(db, node) { self.rule_err =
expand_repetition never looks at the operator - in an expansion ? is just *. so $(...),? on the rhs today expands over all the driver's groups and emits the separator between them, correctly. this call turns working code into an error, and it isn't "silently does nothing" as the PR title claims. the doc's "allows at most one group" rationale only holds for the pattern.
either make the expansion honor ? (fail when the driver has more than one group) and then reject the separator, or keep the check pattern-only and drop this call.
4e8aa3e to
bb682b9
Compare
2dd6ba4 to
5ca0569
Compare
bb682b9 to
fb771d3
Compare
5ca0569 to
9753276
Compare
orizi
left a comment
There was a problem hiding this comment.
@orizi+AGNT made 6 comments and resolved 5 discussions.
Reviewable status: 0 of 3 files reviewed, 1 unresolved discussion (waiting on eytan-starkware+AGNT and TomerStarkware).
crates/cairo-lang-semantic/src/expr/test_data/inline_macros line 3282 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
every new case here is a top-level pattern element.
check_pattern_elementsrecurses into repetitions and subtrees and none of that recursion is pinned - add($($defsite:ident)*),([$defsite:ident])and a?-with-separator nested inside an outer repetition.
Added all three: ($($defsite:ident)*), ([$defsite:ident]), and a ,? nested inside an outer repetition.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 196 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
third full traversal of the pattern with the same four match arms -
PatternPlaceholders::collectright below already visits every Param/Repetition/Subtree and already computes the placeholder name (socheck_placeholder_namere-interns it). fold the name check intocollect- it has both the name and the ptr - and this shrinks to the two repetition checks.
Folded into PatternPlaceholders::collect; check_pattern_elements shrank to the repetition checks.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 259 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
let-chain:
if let ast::OptionTerminalComma::TerminalComma(separator) = repetition.separator(db) && matches!(repetition.operator(db), ast::MacroRepetitionOperator::ZeroOrOne(_)) { return Err(diagnostics.report( separator.stable_ptr(db).untyped(), SemanticDiagnosticKind::MacroRepetitionSeparatorWithZeroOrOne, )); } Ok(())
Done.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 408 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
this was
rule_err.is_ok(), which for an unparsed pattern was always true, so the expansion was checked. now it'sreused_names.is_empty(), which for an unparsed pattern depends on whether recovery happened to leave two nameless placeholders - i.e. a coin flip on whether a dropped rule's expansion gets diagnostics.make it explicit: hoist the
!pattern_is_parsedreport +continueto the top of the loop, right afterlet pattern = rule_syntax.lhs(db);. then theif pattern_is_parsedwrapper, the nameless-placeholders comment above it and this whole question go away.
Done - report + continue at the top of the loop; the wrapper, the nameless-placeholder comment and the coin flip are gone.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 426 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
the comment says this is so "a call written for it points at the parse error rather than failing with a bare 'no matching rule'" - but it doesn't. look at the
pairgolden: E1008 from the parser on the same rule, then this E2208 on an overlapping span, then the same E2158 on the call, unchanged. three diagnostics for one typo, and the confusing one is still there.fix the comment, or make it actually replace the E2158.
Fixed the comment - it claims only the no-shadowing effect now.
crates/cairo-lang-semantic/src/items/macro_declaration.rs line 613 at r3 (raw file):
Previously, eytan-starkware+AGNT (Agent AGNT for eytan-starkware) wrote…
expand_repetitionnever looks at the operator - in an expansion?is just*. so$(...),?on the rhs today expands over all the driver's groups and emits the separator between them, correctly. this call turns working code into an error, and it isn't "silently does nothing" as the PR title claims. the doc's "allows at most one group" rationale only holds for the pattern.either make the expansion honor
?(fail when the driver has more than one group) and then reject the separator, or keep the check pattern-only and drop this call.
Half-taken: the check stays because rustc rejects a separator on a ? expansion block with the same error (the golden quotes it), so dropping it would open a parity gap. What was wrong was the rationale - the doc now says a separator would simply never be emitted there, and the ? promise itself becomes enforced at expansion in the E2210 PR up the stack.
9753276 to
952557f
Compare
9056373 to
c1b78a0
Compare
952557f to
365d74c
Compare
365d74c to
a3ae693
Compare
c1b78a0 to
b1d0456
Compare
a3ae693 to
d20f422
Compare
b1d0456 to
d168b6a
Compare
d20f422 to
9bb15db
Compare
a40e519 to
e358fcd
Compare
8495163 to
827bc64
Compare
6e5b156 to
d9bfd11
Compare
32b563d to
dc0804b
Compare
d9bfd11 to
236c562
Compare
…ntly do nothing A pattern capturing into `$defsite` / `$callsite`, a `?` repetition taking a separator and an empty `$()` body are all accepted today and then contribute nothing, and a rule whose pattern has a parse error is dropped silently, so every call written for it fails with a bare "no matching rule".
236c562 to
8372386
Compare
dc0804b to
74aada1
Compare

A pattern capturing into
$defsite/$callsite, a?repetition taking aseparator and an empty
$()body are all accepted today and then contributenothing, and a rule whose pattern has a parse error is dropped silently, so
every call written for it fails with a bare "no matching rule".