From fb6bccdb14531b93e989c00e36ed278e0d365720 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Tue, 16 Nov 2021 22:14:50 -0500 Subject: [PATCH] Treat empty objects as `never` to force errors --- src/types.ts | 19 ++++++++++++------- typescript_test/test.ts | 32 ++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/types.ts b/src/types.ts index 5b20a5c90..e44504d9c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -125,20 +125,23 @@ export type MergeParameters< LongestParamsArray extends readonly any[] = LongestArray > = // After all that preparation work, we can actually do parameter extraction. - // These steps work somewhat inside out: - // 10) Finally, after all that, run a recursive expansion on the values to make the user-visible + // These steps work somewhat inside out (jump ahead to the middle): + // 11) Finally, after all that, run a shallow expansion on the values to make the user-visible // field details more readable when viewing the selector's type in a hover box. ExpandItems< - // 9) Tuples can have field names attached, and it seems to work better to remove those + // 10) Tuples can have field names attached, and it seems to work better to remove those RemoveNames<{ // 5) We know the longest params array has N args. Loop over the indices of that array. // 6) For each index, do a check to ensure that we're _only_ checking numeric indices, // not any field names for array functions like `slice()` [index in keyof LongestParamsArray]: LongestParamsArray[index] extends LongestParamsArray[number] - ? // 8) Then, intersect all of the parameters for this arg together. - IntersectAll< - // 7) Since this is a _nested_ array, extract the right sub-array for this index - LongestParamsArray[index] + ? // 9) Any object types that were intersected may have had + IgnoreInvalidIntersections< + // 8) Then, intersect all of the parameters for this arg together. + IntersectAll< + // 7) Since this is a _nested_ array, extract the right sub-array for this index + LongestParamsArray[index] + > > : never }> @@ -158,6 +161,8 @@ type EmptyObject = { [K in any]: never } +type IgnoreInvalidIntersections = T extends EmptyObject ? never : T + /** Extract the parameters from all functions as a tuple */ export type ExtractParams = { [index in keyof T]: T[index] extends T[number] ? Parameters : never diff --git a/typescript_test/test.ts b/typescript_test/test.ts index b0eeb5cf5..1916c787b 100644 --- a/typescript_test/test.ts +++ b/typescript_test/test.ts @@ -708,13 +708,13 @@ function testDynamicArrayArgument() { ) const s = createSelector( - data.map(obj => (state: {}, fld: keyof Elem) => obj[fld]), + data.map(obj => (state: StateA, fld: keyof Elem) => obj[fld]), (...vals) => vals.join(',') ) - s({}, 'val1') - s({}, 'val2') + s({ a: 42 }, 'val1') + s({ a: 42 }, 'val2') // @ts-expect-error - s({}, 'val3') + s({ a: 42 }, 'val3') } function testStructuredSelectorTypeParams() { @@ -1365,3 +1365,27 @@ function rtkIssue1750() { return null } } + +function handleNestedIncompatTypes() { + // Incompatible parameters should force errors even for nested fields. + // One-level-deep fields get stripped to empty objects, so they + // should be replaced with `never`. + // Deeper fields should get caught by TS. + // Playground: https://tsplay.dev/wg6X0W + const input1a = (_: StateA, param: { b: number }) => param.b + + const input1b = (_: StateA, param: { b: string }) => param.b + + const testSelector1 = createSelector(input1a, input1b, () => ({})) + + // @ts-expect-error + testSelector1({ a: 42 }, { b: 99 }) // should not compile + + const input2a = (_: StateA, param: { b: { c: number } }) => param.b.c + const input2b = (_: StateA, param: { b: { c: string } }) => param.b.c + + const testSelector2 = createSelector(input2a, input2b, (c1, c2) => {}) + + // @ts-expect-error + testSelector2({ a: 42 }, { b: { c: 99 } }) +}