From 3b694e1fa2b92d70d6a41121742eb06a0aea9450 Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Wed, 6 Dec 2023 14:46:36 -0500 Subject: [PATCH 1/3] wip --- src/types/Match.ts | 4 +- tests/test.test.ts | 130 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 tests/test.test.ts diff --git a/src/types/Match.ts b/src/types/Match.ts index 77a27086..4a75539d 100644 --- a/src/types/Match.ts +++ b/src/types/Match.ts @@ -181,6 +181,8 @@ export type Match< handler: (value: i) => PickReturnValue ): PickReturnValue>; + _handleCases: handledCases; + /** * `.exhaustive()` checks that all cases are handled, and returns the result value. * @@ -231,7 +233,7 @@ export type Match< * of the returned tuple. * - For the second part though I'm not aware a cheap way of sorting a tuple. */ -type DeepExcludeAll = [a] extends [never] +export type DeepExcludeAll = [a] extends [never] ? never : tupleList extends [infer excluded, ...infer tail] ? DeepExcludeAll, tail> diff --git a/tests/test.test.ts b/tests/test.test.ts new file mode 100644 index 00000000..b8883388 --- /dev/null +++ b/tests/test.test.ts @@ -0,0 +1,130 @@ +import { P, match } from '../src'; +import { DeepExclude } from '../src/types/DeepExclude'; +import { DeepExcludeAll } from '../src/types/Match'; + +type States = 'idle' | 'loading' | 'success' | 'error' | 'partial_result'; + +const eventStatus = 'success' as States; +const dataStatus = 'loading' as States; +const backgroundStatus = 'loading' as States; +const replaySelectorsStatus = 'idle' as States; + +const input = { + eventStatus, + dataStatus, + backgroundStatus, + replaySelectorsStatus, + replaySelectorsStatus2: replaySelectorsStatus, +} as const; + +type input = typeof input; + +const expr = match(input) + .returnType< + | { status: 'idle' } + | { status: 'loading' } + | { status: 'success' } + | { status: 'error'; error: unknown } + >() + .with( + P.union( + { eventStatus: P.union('loading', 'partial_result') }, + { dataStatus: P.union('loading', 'partial_result') }, + { backgroundStatus: 'loading' } + ), + () => ({ status: 'loading' }) + ) + .with( + { + eventStatus: 'success', + dataStatus: 'success', + backgroundStatus: 'success', + replaySelectorsStatus: 'success', + }, + () => ({ status: 'success' }) + ) + .with( + { + replaySelectorsStatus: 'error', + }, + () => ({ + status: 'error', + error: new Error('Oops 0'), + }) + ) + .with( + { + eventStatus: 'error', + }, + () => ({ status: 'error', error: new Error('Oops 1') }) + ) + .with({ dataStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 2'), + })) + .with({ backgroundStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 3'), + })) + .with( + P.union( + { eventStatus: 'idle' }, + { dataStatus: 'idle' }, + { backgroundStatus: 'idle' }, + { replaySelectorsStatus: 'idle' } + ), + () => ({ status: 'idle' as const }) + ); + +const handledCases = expr._handleCases; + +handledCases; +// ^? + +const len = handledCases['length']; +// ^? + +const x0 = handledCases[0]; +// ^? + +const x1 = handledCases[1]; +// ^? +const x2 = handledCases[2]; +// ^? +const x3 = handledCases[3]; +// ^? + +const x4 = handledCases[4]; +// ^? + +const x5 = handledCases[5]; +// ^? + +const x6 = handledCases[6]; +// ^? + +type t0 = DeepExclude; +// ^? + +type t1 = DeepExclude; +// ^? + +type t2 = DeepExclude; +// ^? + +type t3 = DeepExclude; +// ^? + +type t4 = DeepExclude; +// ^? + +type t5 = DeepExclude; +// ^? + +type t6 = DeepExclude; +// ^? + +// const res = expr.exhaustive; + +type trec = DeepExcludeAll; +// ^? From 58bd6f6e8bebcf4bddfd3dd92b48b28c45f2030c Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Fri, 29 Dec 2023 23:39:30 +0100 Subject: [PATCH 2/3] support larger cardinality with .exhaustive() --- src/types/BuildMany.ts | 37 ++++------ src/types/Match.ts | 4 +- tests/large-exhaustive.test.ts | 66 +++++++++++++++++ tests/test.test.ts | 130 --------------------------------- 4 files changed, 80 insertions(+), 157 deletions(-) delete mode 100644 tests/test.test.ts diff --git a/src/types/BuildMany.ts b/src/types/BuildMany.ts index 9c39f7d0..4968c819 100644 --- a/src/types/BuildMany.ts +++ b/src/types/BuildMany.ts @@ -15,37 +15,26 @@ type BuildOne = xs extends [ ? BuildOne>, tail> : data; -type SafeGet = k extends keyof data - ? data[k] - : def; - // Update :: a -> b -> PropertyKey[] -> a -type Update< - data, - value, - path extends readonly PropertyKey[] -> = path extends readonly [infer head, ...infer tail] +type Update = path extends readonly [ + infer head, + ...infer tail +] ? data extends readonly [any, ...any] ? head extends number - ? UpdateAt< - data, - Iterator, - Update> - > + ? UpdateAt, Update> : never : data extends readonly (infer a)[] - ? Update>[] + ? Update[] : data extends Set - ? Set>> + ? Set> : data extends Map - ? Map>> - : Compute< - Omit> & { - [k in Extract]: Update< - SafeGet, - value, - Extract - >; + ? Map> + : head extends keyof data + ? Compute< + { [k in Exclude]: data[k] } & { + [k in head]: Update; } > + : data : value; diff --git a/src/types/Match.ts b/src/types/Match.ts index 4a75539d..77a27086 100644 --- a/src/types/Match.ts +++ b/src/types/Match.ts @@ -181,8 +181,6 @@ export type Match< handler: (value: i) => PickReturnValue ): PickReturnValue>; - _handleCases: handledCases; - /** * `.exhaustive()` checks that all cases are handled, and returns the result value. * @@ -233,7 +231,7 @@ export type Match< * of the returned tuple. * - For the second part though I'm not aware a cheap way of sorting a tuple. */ -export type DeepExcludeAll = [a] extends [never] +type DeepExcludeAll = [a] extends [never] ? never : tupleList extends [infer excluded, ...infer tail] ? DeepExcludeAll, tail> diff --git a/tests/large-exhaustive.test.ts b/tests/large-exhaustive.test.ts index fdb45e09..87caa292 100644 --- a/tests/large-exhaustive.test.ts +++ b/tests/large-exhaustive.test.ts @@ -151,4 +151,70 @@ describe('large exhaustive', () => { .exhaustive() ).toBe('Null'); }); + + it('Input with a large distributed cardinality', () => { + type States = 'idle' | 'loading' | 'success' | 'error' | 'partial_result'; + + const eventStatus = 'success' as States; + const dataStatus = 'loading' as States; + const backgroundStatus = 'loading' as States; + const replaySelectorsStatus = 'idle' as States; + + const input = { + eventStatus, + dataStatus, + backgroundStatus, + replaySelectorsStatus, + } as const; + + type input = typeof input; + + const res = match(input) + .returnType< + | { status: 'idle' } + | { status: 'loading' } + | { status: 'success' } + | { status: 'error'; error: unknown } + >() + .with( + { eventStatus: P.union('loading', 'partial_result') }, + { dataStatus: P.union('loading', 'partial_result') }, + { backgroundStatus: P.union('loading', 'partial_result') }, + { replaySelectorsStatus: P.union('loading', 'partial_result') }, + () => ({ status: 'loading' }) + ) + .with( + { + eventStatus: 'success', + dataStatus: 'success', + backgroundStatus: 'success', + replaySelectorsStatus: 'success', + }, + () => ({ status: 'success' }) + ) + .with( + { eventStatus: 'idle' }, + { dataStatus: 'idle' }, + { backgroundStatus: 'idle' }, + { replaySelectorsStatus: 'idle' }, + () => ({ status: 'idle' as const }) + ) + .with({ replaySelectorsStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 0'), + })) + .with({ eventStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 1'), + })) + .with({ dataStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 2'), + })) + .with({ backgroundStatus: 'error' }, () => ({ + status: 'error', + error: new Error('Oops 3'), + })) + .exhaustive(); + }); }); diff --git a/tests/test.test.ts b/tests/test.test.ts deleted file mode 100644 index b8883388..00000000 --- a/tests/test.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { P, match } from '../src'; -import { DeepExclude } from '../src/types/DeepExclude'; -import { DeepExcludeAll } from '../src/types/Match'; - -type States = 'idle' | 'loading' | 'success' | 'error' | 'partial_result'; - -const eventStatus = 'success' as States; -const dataStatus = 'loading' as States; -const backgroundStatus = 'loading' as States; -const replaySelectorsStatus = 'idle' as States; - -const input = { - eventStatus, - dataStatus, - backgroundStatus, - replaySelectorsStatus, - replaySelectorsStatus2: replaySelectorsStatus, -} as const; - -type input = typeof input; - -const expr = match(input) - .returnType< - | { status: 'idle' } - | { status: 'loading' } - | { status: 'success' } - | { status: 'error'; error: unknown } - >() - .with( - P.union( - { eventStatus: P.union('loading', 'partial_result') }, - { dataStatus: P.union('loading', 'partial_result') }, - { backgroundStatus: 'loading' } - ), - () => ({ status: 'loading' }) - ) - .with( - { - eventStatus: 'success', - dataStatus: 'success', - backgroundStatus: 'success', - replaySelectorsStatus: 'success', - }, - () => ({ status: 'success' }) - ) - .with( - { - replaySelectorsStatus: 'error', - }, - () => ({ - status: 'error', - error: new Error('Oops 0'), - }) - ) - .with( - { - eventStatus: 'error', - }, - () => ({ status: 'error', error: new Error('Oops 1') }) - ) - .with({ dataStatus: 'error' }, () => ({ - status: 'error', - error: new Error('Oops 2'), - })) - .with({ backgroundStatus: 'error' }, () => ({ - status: 'error', - error: new Error('Oops 3'), - })) - .with( - P.union( - { eventStatus: 'idle' }, - { dataStatus: 'idle' }, - { backgroundStatus: 'idle' }, - { replaySelectorsStatus: 'idle' } - ), - () => ({ status: 'idle' as const }) - ); - -const handledCases = expr._handleCases; - -handledCases; -// ^? - -const len = handledCases['length']; -// ^? - -const x0 = handledCases[0]; -// ^? - -const x1 = handledCases[1]; -// ^? -const x2 = handledCases[2]; -// ^? -const x3 = handledCases[3]; -// ^? - -const x4 = handledCases[4]; -// ^? - -const x5 = handledCases[5]; -// ^? - -const x6 = handledCases[6]; -// ^? - -type t0 = DeepExclude; -// ^? - -type t1 = DeepExclude; -// ^? - -type t2 = DeepExclude; -// ^? - -type t3 = DeepExclude; -// ^? - -type t4 = DeepExclude; -// ^? - -type t5 = DeepExclude; -// ^? - -type t6 = DeepExclude; -// ^? - -// const res = expr.exhaustive; - -type trec = DeepExcludeAll; -// ^? From f22c5a7ed24e315fcc60a9fbb3022681d4a648cf Mon Sep 17 00:00:00 2001 From: gvergnaud Date: Sat, 13 Jan 2024 16:03:31 +0100 Subject: [PATCH 3/3] deps: update typescript to 5.3.3 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f4fb06cc..4d8c401f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "prettier": "^2.8.8", "rimraf": "^5.0.1", "ts-jest": "^29.1.0", - "typescript": "^5.1.3" + "typescript": "^5.3.3" } }, "node_modules/@babel/code-frame": { @@ -8233,9 +8233,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -14502,9 +14502,9 @@ } }, "typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 30a0c85a..55ad2735 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,6 @@ "prettier": "^2.8.8", "rimraf": "^5.0.1", "ts-jest": "^29.1.0", - "typescript": "^5.1.3" + "typescript": "^5.3.3" } }