Skip to content

feat(sum): named record as a sum-variant payload (M4) - #156

Merged
assapir merged 2 commits into
mainfrom
feat/nested-composites
Aug 24, 2026
Merged

feat(sum): named record as a sum-variant payload (M4)#156
assapir merged 2 commits into
mainfrom
feat/nested-composites

Conversation

@assapir

@assapir assapir commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

A sum-type variant can now carry a previously-declared named record as its payload — e.g. Method = Get / Post(Body) where Body is a record. This is the confirmed M4 "authoritative composite types" gap and unblocks the HTTP client's Method/Request/Response (#143).

Scope is part (A) only (named record as a sum payload). Part (B) — a named composite as a record field — is intentionally deferred.

Step-0 state (before this change)

  • Boxed = Box(Point) (single-variant) never parsed as a sum: a single-variant Name(Payload) is parsed as an expression, so it failed with "Undefined variable". Multi-variant sums are the real path (and match Method).
  • A multi-variant sum with a record payload (Boxed = Box(Point) / Empty) failed type-checking at the declaration: error: Type mismatch: expected Num, got Named { name: "Point", fields: [] }. The checker rejected any non-scalar payload up front, and the parsed named payload carried empty fields.
  • So the gap was in the checker (payload rejection + unresolved named type), with a matching codegen gap (a bound record payload was not registered as a record, so p.field errored "Field access not fully implemented").

What changed

  • Checker (decls.rs): a sum declaration now resolves each variant payload. Built-in scalars (Num/Text/Bool/$) pass through; a named type must resolve to an already-declared record (its fields carried through). Arrays are rejected by the parser; a nested sum, an unknown name, and heterogeneous concrete types per position are rejected by the checker. The per-position consistency check runs over the resolved variants.
  • Codegen (matching.rs): when a match binds a record-typed payload, it is registered like any record local (record_types + var_named_types), so field reads and method calls on the binding resolve. The payload rides in the tagged-union slot by pointer (the record ABI), consistent with the uniform layout — no new boxing path.

Preserved invariants

  • Sum payloads stay "consistent type per position" (the position may now be a named record).
  • Num/Text/Bool/$/array payloads and the Result boxing path are unchanged.

Ships with

  • examples/nested_composites.ql — a Method = Get / Post(Body) sum: construct, match, read a Text field, call a method; self-asserting, exits 0 (JIT + AOT).
  • Run tests (field read, method + Text field, nullary sibling by tag) and type-error tests (nested sum rejected, unknown name rejected) in tests/run_test.rs.
  • docs/LANGUAGE.md sum-type section, feature matrix, and Known limitations updated.

Gate

cargo fmt --check, cargo clippy --all-targets -- -D warnings, and the full cargo test suite under RUSTFLAGS=-D warnings all pass.

PARKED — do not merge without explicit approval.

🤖 Generated with Claude Code

assapir and others added 2 commits August 21, 2026 16:01
A sum variant may now carry a previously-declared record type as its
payload (e.g. `Method = Get / Post(Body)`), not only a built-in scalar.
The checker resolves the named payload to its full record type at the
declaration, so a match arm binds it at that type and reads its fields
and calls its methods. Codegen registers the bound payload as a record
(by pointer, the record ABI, consistent with the uniform slot layout).

The "consistent payload type per position" invariant is preserved; the
named type must be a record (nesting another sum is rejected) and must
be declared above the sum (no hoisting).

Ships an example, run + type-error tests, and LANGUAGE.md updates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Extract `track_named_record_binding` and reuse it from the match-arm
  payload binding and the `=`-binding site (removes duplication).
- Checker: decide a named payload's acceptability by whether the name is a
  registered record (env lookup) rather than by field count, so the reject
  path is about registration/kind, not an emptiness proxy.
- Debug info: a record-payload sum slot is a pointer (the record ABI), so
  emit its DWARF slot as a pointer instead of the record struct by value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@assapir
assapir merged commit bcbfed4 into main Aug 24, 2026
2 checks passed
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