Skip to content

feat(enum): implement a trait without passing its companion - #46

Merged
jo16oh merged 2 commits into
mainfrom
feat/enum-companionless-impltrait
Sep 19, 2026
Merged

jo16oh merged 2 commits into
mainfrom
feat/enum-companionless-impltrait

Conversation

@jo16oh

@jo16oh jo16oh commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Why

Val accepts a trait as a type argument when the trait implements nothing of its own:

const Row = Val.companion<Row>().implTrait<Wired>({ toWire });

An enum could not. The call failed with TS2558: Expected 2 type arguments, but got 1, and the
second argument lost its contextual type along with it. Nothing records the omission as a decision:
notes/design.md §15.2 documents only the companion form, and §9 has no entry.

src/enum.ts also carried its own thinner Takes and Passes, so five gates Val applies never
reached an enum: the mis-call message, a member name another trait answers to, a member name a
variant's field holds, Complete, and Alone.

What changed

  • Val's Takes, Complete, Passes and Alone are shared instead of copied. enum.ts drops
    its own, and both entry points publish the same two overloads Val does.
  • PayloadKeys distributes. keyof over the union of an enum's payloads keeps the shared fields
    alone, so a member shadowing one variant's field passed the gate.
  • The companion's position may hold the members themselves: trait.__valof_shared ?? trait, one
    token, matching val.ts.
  • api.md lists the type-argument form, and marks the arguments that were already optional.

Cost

The declarations shrink: the two duplicated aliases outweigh the overloads doubling across the two
entry points. §15.1 paid +2.1 kB for the same overloads on a Val.

before after budget
index.d.mts 58.81 kB 58.67 kB 64.00 kB
enum instantiations 32,238 32,646 90,000
core instantiations 5,733 5,733 10,000
production gzip, with Enum 106 B left 105 B left 1.38 kB

Tests

Each gate was removed from the source to confirm a test goes red, on both entry points: the sealer's
and the companion's. EnumBuilder had no coverage at first, and losing every gate left the suite
green.

One behaviour to note

Two gates now reject code that compiled before: a trait member shadowing one variant's field, and
two traits answering to the same member name. Both were holes rather than allowances, and Enum
ships from valof/experimental, so this is labelled as a fix rather than breaking. Say if it
belongs in the release notes as one.

- share val.ts's `Takes`, `Complete`, `Passes` and `Alone`; drop enum.ts's
  thinner copies
- with them, four gates the enum lacked: the mis-call message, a name another
  trait answers to, a name a variant's field holds, a companion that skipped a
  Final
- `PayloadKeys` distributes, so an enum answers for every variant rather than
  the shared fields alone
- declarations 58.81 to 58.67 kB
- api: the type-argument form was missing from both tables
- api: `.impl` and `.implTrait` read as taking a required argument; the
  enum's `.impl(fns?)` was the only row that said otherwise
- notes: §15.2 had no record of the asymmetry; it was never a rejected
  option, just unwritten
- notes: sharing val.ts's gates shrank the declarations, against §15.1's
  +2.1 kB for the same overloads on a Val
@jo16oh jo16oh added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 19, 2026
@jo16oh
jo16oh merged commit 130637e into main Sep 19, 2026
2 checks passed
@jo16oh jo16oh added bug Something isn't working and removed enhancement New feature or request labels Sep 24, 2026
@jo16oh
jo16oh deleted the feat/enum-companionless-impltrait branch September 24, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant