🐛 Reject mixed literal and broad universes
`UnsupportedReason` tested `IsLiteral<PatternKey<T>> extends false`.
`IsLiteral` is `boolean` for a union that mixes a literal with a broad
type (`"a" | \`x-${number}\``), so `boolean extends false` is false and
the mix was accepted. Test `extends true` instead, which rejects it.
Found while adding property-union broad-universe coverage; the gate is
shared with the primitive-union matcher.
This commit is contained in:
1 parent
55bf6c24a7
commit
1bdf7f0315
4 files changed
+13
-5
No files matched your search
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- reject a universe that mixes a literal with a broad type (e.g.
|
||||||
|
`"a" | \`x-${number}\``), which the finite-literal gate let through
|
||||||
- narrow the tagged-union matcher's handler parameters for a union-valued or
|
- narrow the tagged-union matcher's handler parameters for a union-valued or
|
||||||
optional discriminant, and its fallback to the unhandled tags, instead of
|
optional discriminant, and its fallback to the unhandled tags, instead of
|
||||||
passing `never`
|
passing `never`
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ inversion: the handler parameter is the member(s) of `T` whose `PatternKey` is
|
|||||||
object satisfy the exhaustive overload and reaches the `dispatch` throw.
|
object satisfy the exhaustive overload and reaches the `dispatch` throw.
|
||||||
Rejecting at the boundary avoids threading an open/closed branch through
|
Rejecting at the boundary avoids threading an open/closed branch through
|
||||||
`Handlers`, `Fallback` and `MustBePartial`.
|
`Handlers`, `Fallback` and `MustBePartial`.
|
||||||
|
- **The finite-literal predicate is `IsLiteral<PatternKey<T>> extends true`.**
|
||||||
|
`IsLiteral` is `boolean` for a union that mixes a literal with a broad type
|
||||||
|
(`"a" | \`x-${number}\``), so `extends false`would treat the mix as
|
||||||
|
supported;`extends true` is the check that rejects it.
|
||||||
- **Collisions are rejected, not merged.** `Member<T, K>` would be sound (the
|
- **Collisions are rejected, not merged.** `Member<T, K>` would be sound (the
|
||||||
handler gets the union), but the API is one handler per member; rejecting
|
handler gets the union), but the API is one handler per member; rejecting
|
||||||
keeps `Member` a singleton and the remainder exact.
|
keeps `Member` a singleton and the remainder exact.
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ export type Collisions<T> = Extract<T, Stringified<T>>;
|
|||||||
// universe is a finite union of literals with no value/stringification
|
// universe is a finite union of literals with no value/stringification
|
||||||
// collision: only then can `PatternKey` be inverted unambiguously.
|
// collision: only then can `PatternKey` be inverted unambiguously.
|
||||||
export type UnsupportedReason<T extends Matchable> =
|
export type UnsupportedReason<T extends Matchable> =
|
||||||
IsLiteral<PatternKey<T>> extends false
|
IsLiteral<PatternKey<T>> extends true
|
||||||
? "broad types like string, number and template literals are not supported"
|
? [Collisions<T>] extends [never]
|
||||||
: [Collisions<T>] extends [never]
|
? never
|
||||||
? never
|
: `a value and its stringification collide. Value is "${Collisions<T> & string}"`
|
||||||
: `a value and its stringification collide. Value is "${Collisions<T> & string}"`;
|
: "broad types like string, number and template literals are not supported";
|
||||||
|
|
||||||
// The diagnostic for an unsupported universe. Extends `HandlerMap` so the
|
// The diagnostic for an unsupported universe. Extends `HandlerMap` so the
|
||||||
// implementation's `handlers: HandlerMap` stays assignable when the gate is
|
// implementation's `handlers: HandlerMap` stays assignable when the gate is
|
||||||
|
|||||||
@@ -528,6 +528,8 @@ test("getPrimitiveUnionMatcher: a broad universe is rejected", () => {
|
|||||||
getPrimitiveUnionMatcher<number>()({ 1: () => 1 });
|
getPrimitiveUnionMatcher<number>()({ 1: () => 1 });
|
||||||
// @ts-expect-error a template literal is open
|
// @ts-expect-error a template literal is open
|
||||||
getPrimitiveUnionMatcher<`a${string}`>()({ a: () => 1 });
|
getPrimitiveUnionMatcher<`a${string}`>()({ a: () => 1 });
|
||||||
|
// @ts-expect-error a literal mixed with a template literal is open
|
||||||
|
getPrimitiveUnionMatcher<"a" | `x-${number}`>()({ a: () => 1 });
|
||||||
// @ts-expect-error `string | boolean` is open because of `string`
|
// @ts-expect-error `string | boolean` is open because of `string`
|
||||||
getPrimitiveUnionMatcher<string | boolean>()({ true: () => 1 });
|
getPrimitiveUnionMatcher<string | boolean>()({ true: () => 1 });
|
||||||
// @ts-expect-error the widening factory rejects broad universes too
|
// @ts-expect-error the widening factory rejects broad universes too
|
||||||
|
|||||||
Reference in new issue
Block a user