🐛 Gate the universe to finite, literal keys
An open universe (`string`, `number`, a template literal) makes the handler map index-like, so a partial object satisfied the exhaustive overload and the runtime `dispatch` throw was reachable. A universe containing a member and its stringification (`"true" | true`, `1 | "1"`) collapsed two members onto one key. - reject broad universes and value/stringification collisions at the factory, with a diagnostic naming the reason - invert `PatternKey` against the universe (`Member<T, K>`) instead of `PatternParam<K>`, so a standalone `"true"` is typed `"true"` - intersect `UniverseGate<T>` into the handler and fallback parameters, preserving `R` inference and the popup
This commit is contained in:
1 parent
1723419e69
commit
3b5269f9e1
6 files changed
+953
-95
No files matched your search
+11
-10
@@ -3,25 +3,26 @@ import type { Exact } from "type-fest";
|
||||
import type {
|
||||
HandlerMap,
|
||||
Matchable,
|
||||
Member,
|
||||
PatternKey,
|
||||
PatternParam,
|
||||
PatternReturns,
|
||||
RedundantFallback,
|
||||
UnaryFn,
|
||||
UniverseGate,
|
||||
} from "./matcher-shared.ts";
|
||||
|
||||
type Handlers<T extends Matchable, R> = {
|
||||
[K in PatternKey<T>]: UnaryFn<PatternParam<K>, R>;
|
||||
[K in PatternKey<T>]: UnaryFn<Member<T, K>, R>;
|
||||
};
|
||||
|
||||
// The fallback is a *second argument*, not a property of the handler map,
|
||||
// because its parameter is the remainder `Exclude<T, keyof Handled>` and TypeScript
|
||||
// because its parameter is the remainder `Exclude<T, Member<T, keyof Handled>>` and TypeScript
|
||||
// fixes a property's contextual type before it infers its sibling keys. A later
|
||||
// argument, by contrast, is contextually typed from inference on an earlier
|
||||
// one, so the split is what makes the remainder expressible at all.
|
||||
// See development/library.md.
|
||||
type Fallback<T extends Matchable, Handled, R> = UnaryFn<
|
||||
Exclude<T, PatternParam<keyof Handled>>,
|
||||
Exclude<T, Member<T, keyof Handled>>,
|
||||
R
|
||||
>;
|
||||
|
||||
@@ -44,14 +45,14 @@ type MustBePartial<T extends Matchable, Handled> =
|
||||
// #1 Handlers (first) -> the exhaustive form and the autocomplete popup
|
||||
// #2 Fallback (last) -> accepts a partial handler map plus a fallback
|
||||
interface PrimitiveUnionMatcherStrict<T extends Matchable> {
|
||||
<R>(handlers: Handlers<T, R>): UnaryFn<T, R>;
|
||||
<R>(handlers: Handlers<T, R> & UniverseGate<T>): UnaryFn<T, R>;
|
||||
<
|
||||
R,
|
||||
Handled extends Exact<Partial<Handlers<T, R>>, Handled> &
|
||||
MustBePartial<T, Handled>,
|
||||
>(
|
||||
handlers: Handled & Partial<Handlers<T, R>>,
|
||||
fallback: Fallback<T, Handled, R>,
|
||||
handlers: Handled & Partial<Handlers<T, R>> & UniverseGate<T>,
|
||||
fallback: Fallback<T, Handled, R> & UniverseGate<T>,
|
||||
): UnaryFn<T, R>;
|
||||
}
|
||||
|
||||
@@ -60,15 +61,15 @@ interface PrimitiveUnionMatcherStrict<T extends Matchable> {
|
||||
// contextual/autocomplete type.
|
||||
interface PrimitiveUnionMatcherWidening<T extends Matchable> {
|
||||
<P extends Exact<Handlers<T, unknown>, P>>(
|
||||
handlers: P,
|
||||
handlers: P & UniverseGate<T>,
|
||||
): UnaryFn<T, PatternReturns<P>>;
|
||||
<
|
||||
R,
|
||||
Handled extends Exact<Partial<Handlers<T, unknown>>, Handled> &
|
||||
MustBePartial<T, Handled>,
|
||||
>(
|
||||
handlers: Handled,
|
||||
fallback: Fallback<T, Handled, R>,
|
||||
handlers: Handled & UniverseGate<T>,
|
||||
fallback: Fallback<T, Handled, R> & UniverseGate<T>,
|
||||
): UnaryFn<T, PatternReturns<Handled> | R>;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user