✨ Reject a fallback for an exhaustive map
A fallback paired with a handler map that already covers `T` was still accepted, even though its remainder is empty. Fold the guard into `Handled`'s own (F-bounded) constraint so it is checked after inference; a conditional in the fallback parameter is evaluated while `Handled` is still its constraint and rejects every partial map whose handler callbacks need contextual typing.
This commit is contained in:
1 parent
7e7f716424
commit
f27966e803
5 files changed
+65
-14
No files matched your search
+14
-10
@@ -31,8 +31,9 @@ Each factory is two overloads whose order is load-bearing:
|
||||
|
||||
1. `Handlers<T, R>` — the exhaustive form, and the contextual type of the
|
||||
handler-map popup;
|
||||
2. `Handled extends Exact<Partial<Handlers<T, R>>, Handled>` plus
|
||||
`Fallback<T, Handled, R>` — a partial handler map plus the fallback.
|
||||
2. `Handled extends Exact<Partial<Handlers<T, R>>, Handled>` intersected with
|
||||
`MustBePartial<T, Handled>`, plus `Fallback<T, Handled, R>` — a partial
|
||||
handler map plus the fallback, rejected when the map already covers `T`.
|
||||
|
||||
#### Why
|
||||
|
||||
@@ -41,6 +42,14 @@ Each factory is two overloads whose order is load-bearing:
|
||||
handler map can only see all of `T`, never `Exclude<T, keyof Handled>`. A later
|
||||
argument is contextually typed from inference on an earlier one, so the split
|
||||
is what makes the remainder expressible.
|
||||
- **The redundant-fallback guard is an F-bounded constraint.** A map that
|
||||
already covers `T` plus a fallback is rejected by folding
|
||||
`MustBePartial<T, Handled>` into `Handled`'s own constraint. The guard is
|
||||
checked _after_ `Handled` is inferred, so the contextual pass that types the
|
||||
handler callbacks survives. The obvious conditional
|
||||
`Exclude<T, keyof Handled> extends never ? …` in the fallback's parameter
|
||||
type is evaluated while `Handled` is still its constraint and rejects every
|
||||
partial map whose callbacks are context-sensitive.
|
||||
- **Overload order keeps both messages.** #1 supplies the contextual type
|
||||
(`a, b, c`); #2 accepts a partial map once a fallback is present, so its popup
|
||||
is optional (`a?, b?, c?`). A gap without a fallback is reported against #1.
|
||||
@@ -57,9 +66,9 @@ Each factory is two overloads whose order is load-bearing:
|
||||
- **Single-object `_`** (the former shape). `_` sees only all of `T`; the
|
||||
remainder is not expressible there, and an exhaustive map plus `_` was
|
||||
accepted.
|
||||
- **Curried handlers-first** — `(handlers)(fallback)`. `Handled` is fixed before
|
||||
the second call, so a redundant-fallback guard would work. Rejected: two calls
|
||||
for the common case.
|
||||
- **Curried handlers-first** — `(handlers)(fallback)`. Rejected: two calls for
|
||||
the common case. It is not needed for the redundant-fallback guard, which the
|
||||
F-bounded constraint already provides (see Why).
|
||||
- **`this` / HKT self-reference.** `this` is post-construction (method bodies,
|
||||
return positions); a parameter's contextual type is pre-construction.
|
||||
`keyof this` in an interface method is the interface, not the literal.
|
||||
@@ -76,11 +85,6 @@ Each factory is two overloads whose order is load-bearing:
|
||||
|
||||
#### Known issue
|
||||
|
||||
- A redundant fallback is accepted: when the handler map already covers `T`, the
|
||||
fallback is still allowed. The guard would be
|
||||
`Exclude<T, keyof Handled> extends never ? never : unknown`, but the
|
||||
conditional is evaluated before `Handled` is inferred; currying is the only
|
||||
encoding that fixes it (see Rejected).
|
||||
- `PatternReturns` must be
|
||||
`ReturnType<Extract<ValueOf<P>, (...args: never[]) => unknown>>` so it survives
|
||||
the closed, partly-optional `P` constraints.
|
||||
|
||||
Reference in new issue
Block a user