📝 Document the tagged-union matcher
This commit is contained in:
1 parent
c0fc090ee4
commit
8ab3c6dbc6
3 files changed
+59
-3
No files matched your search
+52
-3
@@ -3,9 +3,10 @@
|
||||
The type-level design of the public API and the limitations it carries. The
|
||||
user-facing reference is [README § API](../README.md#api).
|
||||
|
||||
The matcher is implemented in `src/primitive.ts` and re-exported from
|
||||
`src/index.ts` as `getMatcher` / `getMatcherW`; the rest of the library is
|
||||
placeholder code.
|
||||
The matchers are implemented in `src/primitive.ts` (`getMatcher` /
|
||||
`getMatcherW`) and `src/tagged-union.ts` (`getTaggedUnionMatcher` /
|
||||
`getTaggedUnionMatcherW`), re-exported from `src/index.ts`; the rest of the
|
||||
library is placeholder code.
|
||||
|
||||
## Matcher shape
|
||||
|
||||
@@ -92,6 +93,54 @@ Each factory is two overloads whose order is load-bearing:
|
||||
not a sound "rejected" oracle for a factory. Factory-negative tests use
|
||||
`@ts-expect-error` call sites (the test file only — the general ban stands).
|
||||
|
||||
## Tagged-union matcher
|
||||
|
||||
#### Decision (2026-09)
|
||||
|
||||
`getTaggedUnionMatcher` / `getTaggedUnionMatcherW` mirror the primitive pair
|
||||
with one extra curried step for the discriminant key:
|
||||
|
||||
```ts
|
||||
type Shape =
|
||||
| { kind: "circle"; radius: number }
|
||||
| { kind: "square"; side: number };
|
||||
|
||||
const area = getTaggedUnionMatcher<Shape>()("kind")({
|
||||
circle: (s) => Math.PI * s.radius ** 2,
|
||||
square: (s) => s.side ** 2,
|
||||
});
|
||||
const fallback = getTaggedUnionMatcher<Shape>()("kind")(
|
||||
{ circle: (s) => … },
|
||||
(s) => …, // s: { kind: "square"; side: number }
|
||||
);
|
||||
```
|
||||
|
||||
The key is a separate call because `K` is inferred from its literal argument and
|
||||
`T` is fixed by the first factory; one call could not infer both.
|
||||
`Discriminated<T>` restricts the key to properties whose values are tags.
|
||||
|
||||
#### Why
|
||||
|
||||
- **Same fallback/remainder machinery as the primitive matcher.** `HandledMembers`
|
||||
maps the handled tags to their members and `Exclude<T, …>` is the fallback's
|
||||
parameter; the redundant-fallback guard is the same F-bounded constraint. Only
|
||||
the "universe" changes — `T`'s members instead of primitive values.
|
||||
- **`T extends object`, not `Record<PropertyKey, unknown>`.** An `interface` has
|
||||
no implicit index signature, so the `Record` constraint would reject
|
||||
interface-based unions. The runtime reads the tag off `object` with one
|
||||
assertion, the tagged twin of the primitive dispatch's `shape as string | number`.
|
||||
- **`MapTaggedUnion` distributes with `Extract`.** A duplicated tag yields a
|
||||
union of members instead of dropping one.
|
||||
|
||||
#### Known issue
|
||||
|
||||
- Tags are `string | number` only. A `boolean` / `null` / `undefined`
|
||||
discriminant (`{ ok: true } | { ok: false }`) is rejected by `Discriminated`,
|
||||
because those values are not property keys; supporting them needs the
|
||||
primitive matcher's `PatternKey` / `PatternParam` projection.
|
||||
- A member's tag must be unique across the union; two members with the same tag
|
||||
collapse to a union under one handler.
|
||||
|
||||
## Primitive universe
|
||||
|
||||
#### Decision (2026-09)
|
||||
|
||||
Reference in new issue
Block a user