♻️ Share the Matchable universe between both matchers

This commit is contained in:
tmu committed 2026-09-22 10:54:27 +00:00
1 parent d88a4eeb45
commit 6228b4922d
4 files changed
+24 -17

No files matched your search

+9 -5
View File
@@ -97,9 +97,9 @@ Each factory is two overloads whose order is load-bearing:
#### Decision (2026-09) #### Decision (2026-09)
`src/matcher-shared.ts` holds the six universe-agnostic pieces both matchers `src/matcher-shared.ts` holds the seven universe-agnostic pieces both matchers
use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, the shared
`PatternKey` / `PatternParam` key projection. `Matchable` universe, and the `PatternKey` / `PatternParam` key projection.
#### Why #### Why
@@ -107,6 +107,10 @@ use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the
keeps the two matchers' message from drifting; the other pieces appear keeps the two matchers' message from drifting; the other pieces appear
verbatim in both public signatures or are the same projection over each verbatim in both public signatures or are the same projection over each
matcher's universe. matcher's universe.
- **`Matchable` is one definition, not two.** The primitive-union matcher's
universe and the tagged-union matcher's allowed `Tag` values are the same set,
so aliasing them keeps the two matchers from drifting apart on what they
accept (`symbol`/`bigint` rejected once).
#### Rejected #### Rejected
@@ -114,8 +118,8 @@ use: `UnaryFn`, `PatternReturns`, `RedundantFallback`, `HandlerMap`, and the
`Fallback` and `MustBePartial`.** Each is built from its own universe `Fallback` and `MustBePartial`.** Each is built from its own universe
(`Tags`/`MapTaggedUnion` vs the primitive values); abstracting over the (`Tags`/`MapTaggedUnion` vs the primitive values); abstracting over the
F-bounded `Handled` constraint that makes the remainder work risks the F-bounded `Handled` constraint that makes the remainder work risks the
contextual typing it exists to preserve. The `PatternKey` / `PatternParam` contextual typing it exists to preserve. `Matchable` and the `PatternKey` /
projection is the one piece both universes genuinely share. `PatternParam` projection are the pieces both universes genuinely share.
## Tagged-union matcher ## Tagged-union matcher
+6
View File
@@ -8,6 +8,12 @@ import type { ValueOf } from "type-fest";
// A handler: one universe member in, one return value out. // A handler: one universe member in, one return value out.
export type UnaryFn<T, R> = (shape: T) => R; export type UnaryFn<T, R> = (shape: T) => R;
// The primitive universe a matcher can discriminate. The primitive-union matcher
// uses it directly; the tagged-union matcher uses it as the set of allowed
// discriminant (`Tag`) values. `boolean` is admitted as the pair `true | false`;
// see README § Caveats for the unsupported members.
export type Matchable = string | number | boolean | null | undefined;
// `boolean`, `null` and `undefined` cannot be property keys, so a mapped type // `boolean`, `null` and `undefined` cannot be property keys, so a mapped type
// over a universe that includes one keys each such member by its // over a universe that includes one keys each such member by its
// stringification. `PatternParam` inverts that projection, so a handler callback // stringification. `PatternParam` inverts that projection, so a handler callback
+1 -4
View File
@@ -2,6 +2,7 @@ import type { Exact } from "type-fest";
import type { import type {
HandlerMap, HandlerMap,
Matchable,
PatternKey, PatternKey,
PatternParam, PatternParam,
PatternReturns, PatternReturns,
@@ -9,10 +10,6 @@ import type {
UnaryFn, UnaryFn,
} from "./matcher-shared.ts"; } from "./matcher-shared.ts";
// The primitive universe a matcher can discriminate. `boolean` is admitted as
// the pair `true | false`; see README § Caveats for the unsupported members.
type Matchable = string | number | boolean | null | undefined;
type Handlers<T extends Matchable, R> = { type Handlers<T extends Matchable, R> = {
[K in PatternKey<T>]: UnaryFn<PatternParam<K>, R>; [K in PatternKey<T>]: UnaryFn<PatternParam<K>, R>;
}; };
+8 -8
View File
@@ -2,6 +2,7 @@ import type { Exact, UnknownRecord } from "type-fest";
import type { import type {
HandlerMap, HandlerMap,
Matchable,
PatternKey, PatternKey,
PatternParam, PatternParam,
PatternReturns, PatternReturns,
@@ -9,22 +10,21 @@ import type {
UnaryFn, UnaryFn,
} from "./matcher-shared.ts"; } from "./matcher-shared.ts";
// A tagged union is discriminated by one property whose values are the tags. // A tagged union is discriminated by one property whose values are the tags (a
// `string` and `number` tags key a handler map directly; `boolean`, `null` and // `Matchable`). `string` and `number` tags key a handler map directly;
// `undefined` are admitted too but are not property keys, so they go through the // `boolean`, `null` and `undefined` are admitted too but are not property keys,
// `PatternKey` projection. `symbol` has no literal syntax to write a handler // so they go through the `PatternKey` projection. `symbol` has no literal syntax
// under, and `bigint` is not a property key. // to write a handler under, and `bigint` is not a property key.
type Tag = string | number | boolean | null | undefined;
// The discriminant values of `T` under `K`. `Extract` keeps the finite literal // The discriminant values of `T` under `K`. `Extract` keeps the finite literal
// tags and leaves a widened `string`/`number` as itself, so an open universe // tags and leaves a widened `string`/`number` as itself, so an open universe
// keeps an open fallback. // keeps an open fallback.
type Tags<T extends object, K extends keyof T> = Extract<T[K], Tag>; type Tags<T extends object, K extends keyof T> = Extract<T[K], Matchable>;
// The keys of `T` that can act as a discriminant. `getTaggedUnionMatcher<T>()` // The keys of `T` that can act as a discriminant. `getTaggedUnionMatcher<T>()`
// accepts only these, so the factory rejects a key whose values are not tags. // accepts only these, so the factory rejects a key whose values are not tags.
type Discriminated<T extends object> = { type Discriminated<T extends object> = {
[K in keyof T]: T[K] extends Tag ? K : never; [K in keyof T]: T[K] extends Matchable ? K : never;
}[keyof T]; }[keyof T];
// The member(s) of `T` tagged `V`. `Extract` distributes over the union, so a // The member(s) of `T` tagged `V`. `Extract` distributes over the union, so a