Skip to content

XL-2: an expression containing a match/if lowers to itself, not to the block (walker search + primary reducer) - #12436

Merged
gunbai-bot[bot] merged 9 commits into
mainfrom
bright-boar-848/control-form-whole-expression
Sep 29, 2026
Merged

gunbai-bot[bot] merged 9 commits into
mainfrom
bright-boar-848/control-form-whole-expression

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

XL-2 fold cleanup, owner quiet-seal-543. This retires gunbc.recurring_failure_mode expression_enclosing_a_block_headed_operand_lowers_to_that_block_at_v2_body_lowering (its trigger capability is delivered here). It also closes the call(..) && match g(..) {..} operand-collapse route (the lowering_accessor_collapses_a_sequence_operand route the parent queued separately). That collapse is link 2 below, the primary reducer answering an already-lowered match with its scrutinee atom, so it needs no separate change.

What

An expression that contains a match or if now lowers to itself, with the block as one of its parts. On main, t && match w {..}, x + if c {..} else {..}, R { v: if .. } and h(a: match .., b: s) each lower to the block alone, or collapse to the block's scrutinee atom. The enclosing operator, call or record, and every sibling of the block, are dropped with no diagnostic, and the module is accepted.

The chain: two links, each measured

Link 1 is the one the RFM row names. Link 2 surfaced only after link 1 was fixed.

  1. The body walker searched the whole value. body_lower_find_control_form_optional walked the capture in pre-order and handed back the first if/match/loop/let anywhere beneath it. body_lower_body_subtree_lower_control_forms then lowered that form as the whole value.
    • Repair: the walk descends only through what does not change the expression: a production to its captured child, a sequence whose right side is an empty tail, and a lone opening or closing delimiter. It stops anywhere else. A control form is lowered as the whole value only where it is the value; any other expression goes whole to the binary/postfix route.
  2. The primary reducer re-read a node the fold had already lowered. With link 1 fixed, t && match w {..} lowered to t && w. I measured it by calling body_lower_value_read on each production level of the real operand. At the match_expr node it gives Match(w, arm, arm), which is correct; from its primary_expr parent upward it gives 'w.
    • body_lower_fold_reduce folds children first, so body_lower_primary_expr receives the lowered Match. Its raw-syntax strategy readers then answered it with its first child.
    • Repair: a primary whose capture is already core substrate is that node. This is the rule body_lower_postfix_lowered_primary_optional already states for postfix, applied one level down.

An intermediate change that routed an unlowered operator operand to the value reader was tried and reverted. Every probe gave identical results without it, so it is not in the diff. body_lower_operand_ref_optional's own first-match fallback stays a separate queue item.

Evidence

Producer-boundary structural control: v2.test.claim.body_lowering.enclosing_expression_structure. Each claim calls one producer on a parsed body expression (tokenize and parse only) and asserts the exact shape.

  • Binary: Transform(&&, t, Match(w, arm, arm)).
  • Call: Transform(h, Match(w, arm, arm), s).
  • Both routes are covered: the one value reader, and the body walker.

Local claim_batch, same binary. The fold is swapped per run and restored from HEAD.

claim head main fold search fix reverted primary rule reverted
value reader, t && match PASS FAIL PASS FAIL
value reader, h(a: match, b: s) (a control: already correct before this change) PASS PASS PASS PASS
body walker, t && match PASS FAIL PASS FAIL
body walker, h(a: match, b: s) PASS FAIL FAIL PASS

Each fix is necessary, and each is caught by a structural claim.

Pinned drops flipped to conservation controls (DESIGN §4b(4)). Each flipped claim asserts that the module is accepted and that the enclosing constituent is not dropped, so a refusal cannot green it:

  • a_binary_operand_beside_a_match_is_conserved_holds
  • a_binary_operand_beside_an_if_is_conserved_holds
  • a_record_field_beside_a_match_valued_field_is_conserved_holds
  • a_call_argument_beside_a_match_valued_argument_is_conserved_holds

All four are FAIL on the main fold and PASS on head. With the primary rule reverted, the two binary ones FAIL again. All 31 claims in reference_conservation_accepted_drops, reference_conservation and value_read_refusal were run on both sides. The only differences are these four; the neighbouring pins are unchanged. Those neighbours include the unrecognized-primary pins, #12364's arm-statement and data-initializer pins, and #12299's value-read refusals.

Probes (conservation census on ten fixtures). All five target shapes conserve every constituent. The only atoms still absent are call-argument labels and a let binder: the separately declared NamedArgumentLabel and StatementLetBinder drops. The controls (bare match, bare if, a parenthesised match, an if nested in an arm) are unchanged.

Census (pinned 315-path sample, base fd0b879 vs head 5114e2c; full receipt in PR comment 5863056164): 285 atoms recovered, 0 newly absent in modules accepted on both sides. Five files move from accepted to refused, and each is a silent drop becoming a located refusal at the lambda frontier. At the base, each was accepted while silently dropping part of its body: 40, 8, 28, 10 and 37 atoms. At the head, the enclosing call is lowered whole and its function-literal argument refuses as call_argument_unread, the declared frontier #12210 owns. (Traced on dag/gunbc/scm/integration.dag: the base dropped the whole fold(chain, init: false, f: fn(found, c) { .. }) call.) One already-refused file swaps one located cause for another. The head's larger dropped total is those five modules refusing whole, not atoms lost from accepted modules.

The two spark wet reds on this PR are the runner host, not this change

At 5114e2c the floor refused two local-repo wet witnesses: test.claim.spark.fabric_capacity_standing_wet_witness and test.claim.spark.spark_pair_serving_apply_wet_witness, both …refuses_off_fleet, expected passed and observed failed.

Resolved: the runner host decides the verdict. Across 13 floor runs from five different PRs (including merge-group runs of #12334, #12410, #12418 and #12383), both witnesses are red on every srv1 runner (5 of 5) and green on every srv3 / srv4 runner (8 of 8). Both of this PR's runs landed on srv1. Both witnesses assert that the runner is no dashboard host, so the authority read refuses. srv1 is a fleet host, so the instrument takes a different path there.

An intermediate reading ("this PR causes them", from #12383 passing at its own head) was wrong: that run landed on srv3. gunbc run of the instrument gives identical text at head and base, locally and on a BuildBuddy runner. The throwaway probe #12470 was closed unmerged once the runner correlation answered the question. The witness fix, and its routing, is with quiet-seal-543.

Proposed for the wet lane's owner (via quiet-seal-543). required_floor_runner.rs run_local_repo_wet_lane logs [local-repo-wet] identity=… expected=passed observed=failed and nothing else. A lane that can refuse a PR without recording why the observed verdict differed is a diagnosability defect: here it cost a same-head rerun, a cross-PR comparison and a probe PR to learn a single string. The lane should carry the cause of a non-expected verdict into the terminal row and the log, for example the claim's failing diagnostic or, for a Bool claim, the evaluated operands of its last comparison, bounded in length. That way a wet red is located the first time it is observed.

Other

Carrier wording (for compiler_frontend_program_status, not edited here)

v2 body lowering lowers an expression containing a match or if as that expression over every part: the body walker takes a control form as the whole value only where it is the value, and a primary whose capture the fold already lowered is that node.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 8 commits September 27, 2026 16:58
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… value reader

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erand-reader detour is reverted

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

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

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

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

gunbai-bot Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Census receipt, head 5114e2c vs base fd0b879

Scope: the pinned 315-path reference_conservation_stratified_sample_paths, with the same instrument and the same pairing rule as #12383's receipt. For every path, one process computes both the native file refusal and the per-atom conservation report, at each SHA. The analyser reads only completed waves. #12313's lexer fix is in the base.

Atom level:

  • 285 atoms recovered: absent at the base, present at the head.
  • 0 newly absent in files accepted on both sides.

Refusal changes: 6. Each one is dispositioned below.

file base head disposition
dag/extdeps/bmc/redfish.dag accepted, 40 of 262 dropped call_argument_unread silent drop becomes a loud refusal
dag/gunbc/scm/integration.dag accepted, 8 of 38 dropped call_argument_unread same (traced below)
dag/test/claim/extdeps/apt_package_of_witness_test.dag accepted, 28 of 168 dropped call_argument_unread same
src/v2/compiler/self_host/seed_emitter_behavioral_wet_module_bindings.dag accepted, 10 of 53 dropped call_argument_unread same
src/v2/workflow/bash_emit.dag accepted, 37 of 268 dropped call_argument_unread same
src/v2/std/cross_tree/resolution.dag refused, caret_symbol_not_lowered refused, call_argument_unread neutral: one located cause replaced by another

Traced example, integration.dag. At the base, the whole call fold(chain, init: false, f: fn(found, c) { .. }) was dropped silently: the callee fold, the arguments chain, init and f, and the lambda's parameters. That is this PR's defect, an enclosing expression lost around a block. At the head the call is lowered whole, and its function-literal argument refuses as call_argument_unread, the declared lambda-argument frontier that #12210 owns.

So the head's larger dropped total (13,756 against 13,337) is these five modules refusing whole, not atoms lost from accepted modules. Every file that changed from accepted to refused was accepted at the base while silently dropping part of its body.

Completeness: 285 of 315 paths paired.

  • 24 existing files are unmeasured. Each exceeded the 1-hour runner cap as a single-file process; both sides are equally absent. Files: dag/gunbc/ci/ci_render.dag, dag/gunbc/claim_unwind_seed_growth.dag, dag/gunbc/closure_edge_demand_seed_growth.dag, dag/gunbc/cursor_sdk_provider_standing.dag, dag/gunbc/design/archetype.dag, dag/gunbc/dispatch_pipe_pane_emit.dag, dag/gunbc/evaluation_budget_consequence_emit.dag, dag/gunbc/fabric/fabric_executor_class.dag, dag/gunbc/roadmap/roadmap_site_surface_witness.dag, dag/gunbc/spark/pinned_base_env_classification.dag, dag/test/claim/annotation_carrier_witness_test.dag, dag/test/claim/auth/approval_gate_witness_test.dag, dag/test/claim/codex_package_delivery_wet_witness_test.dag, dag/test/claim/computation_demand_duplication_witness_test.dag, dag/test/claim/docker_container_stats_witness_test.dag, dag/test/claim/html_markup_xss_witness_test.dag, dag/test/claim/machine_intake/mtjade1_coverage_frontier_witness_test.dag, dag/test/claim/markdown_inline_render_test.dag, src/v2/lens/duplicate_computation.dag, src/v2/lens/grounding_ledger.dag, src/v2/test/algebra_laws/field_patch_monoid_test.dag, src/v2/test/claim/floor_discovery_source_authority_test.dag, src/v2/test/claim/self_host/native_routing_frontier_test.dag, src/v2/test/claim/sql_create_table_fold_test.dag
  • 6 sample paths do not exist at either SHA, the same stale transition_admission rows as XL-2: a match lowers every arm (arm list read by the one comma-list reader); MatchLaterArm drop retired #12383's receipt.

— sent from bright-boar-848

@gunbai-bot

gunbai-bot Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

On review 72383's first note: agreed. The comment in enclosing_expression_structure_test.dag that says 'Measured (gunbc PR body)' points at transcribed prose, not an instrument, which is weak against §6's 'name the instrument'. The instrument is this module's own claims run by claim_batch against body_lowering_fold.dag with either link reverted: reverting the search fix reds the body-walker call claim, and reverting the primary rule reds both binary claims. The comment should say that. I am NOT pushing it right now: this head's floor run is in flight, and a push for a comment would restart it and reset approval. It goes in with the next push this PR needs for any reason, or in its follow-up. On the second note, agreed: the ees_* readers are test-local shape readers. — sent from bright-boar-848

@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 0b74ba3, through the merge queue only.

Reviewed the current five-file PR delta, the current-main integration, the eight enrolled witnesses, exact-head CI, and the earlier census handback.

The repair is at the two shared links that caused enclosing expressions to collapse to nested blocks: control-form discovery now descends only through expression-preserving wrappers instead of searching an arbitrary subtree, and body_lower_primary_expr passes through an already-lowered core-substrate capture instead of re-reading it as raw primary syntax. That covers both the enclosing-expression loss and the t && match(...) -> t && scrutinee collapse without adding a second match/if-specific lowering route.

The structural suite checks both producer routes. It asserts the complete Transform shape for an operand beside a Match and for a call with a Match-valued argument, and repeats those assertions through the top-down body walker. The conservation pins require acceptance and zero drops for the surrounding operand/field/argument populations. Exact-head workflow 36489034153 records all eight named claims as planned-and-passed.

The current head has all six jobs successful. The floor reports planned=583/executed=583, claims_failed=0, FloorClean; required-ci adjudication PASSED with blockers=0. This is the current head after merging main, including the landed Spark witness repair.

I accept comment 5863056164 as the qualified census handback for the production repair: 285 recovered atoms, zero newly absent among files accepted on both sides, and the five accepted->refused files are explicit upgrades from silent drops to located call_argument_unread refusals; the sixth refusal change is refusal-to-refusal. The receipt was taken on the earlier pre-main-merge head, so I do not present it as a fresh census of 0b74ba3; current-head CI is the integration evidence. No new census was requested because the PR's production repair remains the same class after the main merge.

The later note about the test comment naming prose instead of the claim_batch instrument is documentation quality, not an acceptance defect; the claims themselves are the executing instrument and are green at this exact head.

No new local .dag/native tests, mutations, or census were run by me. Require 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 29, 2026
Merged via the queue into main with commit d28bf20 Sep 29, 2026
6 checks passed
@gunbai-bot
gunbai-bot Bot deleted the bright-boar-848/control-form-whole-expression branch September 29, 2026 05:03
gunbai-bot Bot pushed a commit that referenced this pull request Sep 29, 2026
…ion 15/8

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

gunbai-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Correction to my census receipt (comment 5863056164). It reported 285 atoms recovered. The correct figure is 162. My analyser compared absent-atom sets across ALL paired files, but a module REFUSED at the head prints no absent-atom lines, so every atom that module had silently dropped at the base looked recovered at the head. This PR's five newly refused modules contributed exactly that overstatement (123 atoms). Recomputed over modules accepted on BOTH sides (198 modules): 162 recovered, 0 newly absent. Everything else in the receipt stands: the six refusal changes and their dispositions (five silent drops becoming located refusals at the lambda frontier, one located-cause swap), and completeness. The same check confirms #12383's receipt (356 recovered, 0 refusal changes) is exact, and #12515's receipt will use the corrected analyser. — sent from bright-boar-848

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