Skip to content

Emitter seal reaches through nested coproducts (one member-type reader for the deserialize walk) - #12999

Merged
gunbai-bot[bot] merged 2 commits into
mainfrom
seal-transitive-coproduct
Oct 2, 2026
Merged

gunbai-bot[bot] merged 2 commits into
mainfrom
seal-transitive-coproduct

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Defect. v1.compiler.emit_rust type_expr_reaches_sealed_carrier walks a type looking for a sole_constructor record, so that any container reaching one drops Deserialize. A derived Deserialize would be a second, unsealed way to build the record (see dag/extdeps/languages/rust/capabilities.dag, the sealed-construction derive contract).

When the walk looked up a coproduct, it read the variants as if they were fields. child_type_node resolves a variant to the coproduct itself, which is already in the seen-set, so the walk stopped there. A seal one coproduct deep was caught by the top-level enum entry (enum_variant_payloads_forbid_deserialize). A seal two coproducts deep was not.

Where it surfaced. #12942 (MQ-5) puts a sealed BodyTermLoweredSeal under v2.std.node CoreEdgeLabel, three containers down from EdgeLabel. The containers kept their derive, and the self-host emitted crate refused with E0277: Rc<CoreEdgeLabel>/Box<CoreEdgeLabel> does not implement Deserialize. Main has no nested seal today, so the defect is latent there.

Fix. The two readings disagreed, so there is now one reader. member_type_nodes defines a declaration's member types: a product's fields, or a coproduct's variant payload fields. decl_member_type_nodes and members_forbid_deserialize route the recursive walk, item_forbids_deserialize and the enum emit site through it. enum_variant_payloads_forbid_deserialize and decl_children_forbid_deserialize are deleted.

This is option A of the choice the operator made on 2026-10-02: keep the existing rule (a sealed record has no Deserialize, transitively). The alternative, a hand-written Deserialize that always refuses, would have lowered every sealed type from a compile-time impossibility to a runtime refusal.

Controls (test.claim.emitter_sole_constructor_seal_witness_test):

  • nested_coproduct_over_a_seal_drops_deserialize (red on the old emitter): Outer holds Inner, which holds Sealed. Neither enum may derive Deserialize.
  • nested_plain_coproduct_keeps_deserialize: the same shape over an Int keeps the derive. This proves the negative pattern can match, so the red is not vacuous.

Execution. The claims need a corpus-scale claim_batch. BuildBuddy refuses it (HostBudgetUnreadable: the executor exposes no cgroup limit; see gunbc.host_budget_source), so CI is the executor.

  • Expected first run: the nested claim is red against the unregenerated stage0 mirror, and the generated lane reports mirror drift.
  • Then: the regenerated v1_compiler_emit_rust.rs turns it green.

Downstream risk, stated: once #12942's seal exists, EdgeLabel and Node lose Deserialize transitively. If the emitted compiler decodes a Node anywhere, #12942's build will name that site, and it is settled there (a decoded Node must never carry a seal anyway).

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 2 commits October 2, 2026 16:30
…e deserialize walk

type_expr_reaches_sealed_carrier read a looked-up coproduct's variants as
fields; child_type_node resolves a variant to the coproduct itself, already in
the seen-set, so a seal two coproducts deep was never reached and its
containers kept a derived Deserialize (a second, unsealed mint). The top-level
enum entry read variants correctly, so the two readings disagreed.
member_type_nodes now defines a declaration's member types once (product:
fields; coproduct: variant payload fields) and the walk and both entries use
it.

Controls in emitter_sole_constructor_seal_witness_test: a seal two
coproducts deep drops Deserialize from both containers; the same shape over
an Int keeps it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 00c367f Oct 2, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the seal-transitive-coproduct branch October 2, 2026 20:45
gunbai-bot Bot pushed a commit that referenced this pull request Oct 2, 2026
The stage0 v1_compiler_emit_rust.rs mirror conflicted between this branch's
pre-squash #12999 merge and main. Every src/v1/*.dag source here is identical to
main's, so main's mirror is the regeneration of this tree; it is taken as such.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Oct 2, 2026
…_field_selection_test (a superset of #12999's nested guard), VariantPattern arity per main

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