Skip to content

v2 grammar: offer binary_expr before match/if/loop (revives #11911) + the tree-shape control it left open - #12817

Merged
gunbai-bot[bot] merged 3 commits into
mainfrom
session/keen-fox-715-expr-order
Sep 30, 2026
Merged

gunbai-bot[bot] merged 3 commits into
mainfrom
session/keen-fox-715-expr-order

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

XL-2, per quiet-seal-543's ruling A on class E: revives #11911 with the control it left open.

Provenance

#11911 ("The expression choice offered binary_expr last, so a block heading a && chain could not parse") was closed unmerged by the operator account at exactly 2026-09-30 00:00. That timing looks like a bulk stale-PR sweep, not a rejection; no review or comment on the PR rejects the change. The grammar commit is cherry-picked unchanged. Its note on dag_grammar_expr_expr explains the ordering, what stays above binary_expr and why, and what was not established.

The defect, found independently

A v2 parse probe on main, one module per case:

case result
a && b (same line) parses
a then && b on the next line parses
return a then && b parses
match a {..} && b (same line) refuses
match a {..} then && b refuses
return match a {..} && b refuses

The newline is irrelevant. dag_grammar_expr_expr is an ordered choice that offered match_expr / if_expr / loop_expr before binary_expr, so the choice commits to the bare block and && b is left over. v1 reads a match as a primary and continues the operator loop after it (v1 02_parse parse_primary), which is the reference. binary_expr already reaches match_expr / if_expr / loop_expr through primary_expr_core, so offering it first admits the form without a new production.

The control #11911 left open

#11911 asserted that a bare block still parses, not that it yields the same node. After the reorder a bare match / if is reached as binary_expr -> primary_expr_core -> match_expr, so its parse path changes by construction. What must not change is what it lowers to. Added:

  • a_bare_match_still_lowers_to_the_match_itself_holds: the normalized arrow body is ComputationNode { behavior: Match }.
  • a_bare_if_still_lowers_to_the_branch_itself_holds: the normalized arrow body is ComputationNode { behavior: Branch }.

Both pass on main and on this branch. They go red if routing a bare block through binary_expr ever wraps or retains it.

The file's parses are now nullary values enrolled warm in v2.workflow.floor_pure_producer_share, and each claim reads one, so each claim stays within the new-witness eval-step budget.

Evidence (claim_batch, 30 GB BuildBuddy runner)

v2.test.parse.block_expr_as_binary_operand_parse:

  • main with only the test added: match_heading_a_binary_chain_parses_holds and if_heading_a_binary_chain_parses_holds FAIL; all 5 controls pass.
  • this branch: all 7 pass.

Other suites on this branch:

Parse count (the two class-E files)

Both files also carry an admit_callers trailing comma, so #12796's grammar change was applied to both arms.

main + #12796 this branch + #12796
test.claim.dispatch_preflight_witness_test refuses @6880 (&& match) parses
test.claim.approval_device_redemption_witness_test refuses @31802 (&& code_of_..) parses

#11911 measured the population wider: a whole-corpus census of 829 → 650 file refusals, 673 → 480 at parse, net −179. I have not re-derived that census at this head.

No emitted Rust changes: the v2 grammar has no stage0 mirror.

Land only via the merge queue.

🤖 Generated with Claude Code

briansrls and others added 3 commits September 30, 2026 17:52
…hain can parse

dag_grammar_expr_expr is an ordered Choice that offered match_expr, if_expr and
loop_expr before binary_expr. But dag_grammar_primary_expr_core's final three
alternatives already ARE those productions, so a block-shaped expression is a
legal binary operand by construction. The ordering denied it: given
`match a { ... } && b` the choice committed to match_expr, which succeeded
having consumed the match alone, and `&& b` reached no production -- the parse
ended with tokens remaining. Nothing was unrepresentable; the alternative that
represents it was never offered.

fn_literal, block_expr and arrow_lambda stay above binary_expr, where they
already were: primary_expr_core admits a braced field-initializer list, so a
block offered below could be taken as a record literal instead. The first tokens
of match/if/loop are disjoint from those three, so moving them below changes
nothing between them.

Evidence, against the live grammar rather than a model of it: the new witness
v2.test.parse.block_expr_as_binary_operand_parse runs source text through
dag_prepared_grammar. Two arms are red before this change -- a match and an if
heading a binary chain -- and three controls are green on both sides: each block
form standing alone, and an operand that was never block-shaped. Whole-corpus
census moves 829 file refusals to 650; 193 leave the parse stage and 14 of those
land deeper, at body lowering or normalize, which is the signature of a wall
moving back rather than vanishing.

Not established, and stated rather than assumed: that a bare block form produces
the same NODE as before, since the controls assert parsing and not shape; and
whether the trailing match/if/loop alternatives are now unreachable, which holds
exactly if binary_expr never fails where they would succeed. They are retained
rather than deleted because that argument is not made here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ft open, warm-enrol its parses

A bare match / if still lowers to the Match / Branch construct itself after the reorder (normalized
arrow body kind), so routing it through binary_expr neither wraps nor retains it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nion)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@briansrls briansrls 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.

APPROVE-MERGE at exact head 9e0536d, merge queue only.

No findings. The ordered-choice repair exposes binary_expr before the match/if/loop prefixes it already contains through primary_expr_core, rather than adding a second syntax form. The two formerly denied chain forms are discriminating reds, and the added normalized-tree controls close #11911's open shape question by requiring bare match/if bodies to remain Match/Branch themselves. Warm enrollment and the unchanged ordinary/bare controls bound the reordering.

This approval requires the actual merge_group candidate to pass against then-current main; no direct merge or check bypass.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 5e0d5a2 Sep 30, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/keen-fox-715-expr-order branch September 30, 2026 21:11
gunbai-bot Bot pushed a commit that referenced this pull request Oct 1, 2026
…ing a chain is held by #12817's module)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant