Skip to content

Commit

Permalink
Treat empty objects as never to force errors
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Nov 17, 2021
1 parent 05ce9ea commit fb6bccd
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
19 changes: 12 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,23 @@ export type MergeParameters<
LongestParamsArray extends readonly any[] = LongestArray<TuplifiedArrays>
> =
// 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
}>
Expand All @@ -158,6 +161,8 @@ type EmptyObject = {
[K in any]: never
}

type IgnoreInvalidIntersections<T> = T extends EmptyObject ? never : T

/** Extract the parameters from all functions as a tuple */
export type ExtractParams<T extends readonly UnknownFunction[]> = {
[index in keyof T]: T[index] extends T[number] ? Parameters<T[index]> : never
Expand Down
32 changes: 28 additions & 4 deletions typescript_test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 } })
}

0 comments on commit fb6bccd

Please sign in to comment.