Skip to content

Commit 57713e7

Browse files
committed
Rewrite MergeParameters to work with TS 4.9
Per microsoft/TypeScript#50831 , the existing implementation of `MergeParameters` broke with TS 49 due to a change in how "optional" and "undefined" arguments get interpreted Anders Hjelsberg himself supplied a new implementation that's actually much simpler, but only works with TS 4.7 and greater. Normally this would require shipping two entirely different sets of types, as we already do for TS <4.1. However, there's a trick we used with RTK where we: - Create a folder and put two different files with different impls of the same type inside - Add an index file and re-export one of those - Add a file named `package.dist.json` containing `typesVersions` that points to both of those `.d.ts` files - Copy that `package.dist.json` over to `dist/some/package.json` during the actual build/publish step That way during dev TS just imports the type as normal, but the built version sees that `some/package.json`, sees `typesVersions`, finds the right `.d.ts` file, and imports the right implementation for itself.
1 parent 5b83d04 commit 57713e7

File tree

10 files changed

+291
-189
lines changed

10 files changed

+291
-189
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"@typescript-eslint/no-explicit-any": "off",
4646
"@typescript-eslint/no-unused-vars": "off",
4747
"@typescript-eslint/no-non-null-assertion": "off",
48-
"@typescript-eslint/no-shadow": ["error"],
48+
"@typescript-eslint/no-shadow": ["off"],
4949
"@typescript-eslint/no-use-before-define": ["error"],
5050
"@typescript-eslint/ban-types": "off",
5151
"prefer-rest-params": "off",

.github/workflows/build-and-test-types.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,21 @@ jobs:
6262
- name: Install deps
6363
run: yarn install
6464

65-
- name: Install TypeScript ${{ matrix.ts }}
66-
run: yarn add typescript@${{ matrix.ts }}
67-
65+
# Build with the actual TS version in the repo
6866
- name: Pack
6967
run: yarn build && yarn pack
7068

7169
- name: Install build artifact
7270
run: yarn add ./package.tgz
7371

72+
# Then install the specific version to test against
73+
- name: Install TypeScript ${{ matrix.ts }}
74+
run: yarn add --dev typescript@${{ matrix.ts }}
75+
76+
# Remove config line that points "reselect" to the `src` folder,
77+
# so that the typetest will use the installed version instead
78+
- run: sed -i -e /@remap-prod-remove-line/d ./typescript_test/tsconfig.json
79+
7480
- name: Test types
7581
run: |
7682
./node_modules/.bin/tsc --version

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
},
2727
"scripts": {
2828
"build:commonjs": "cross-env BABEL_ENV=commonjs babel src/*.ts --ignore src/types.ts --extensions .ts --out-dir lib ",
29-
"build:es": "babel src/*.ts --ignore src/types.ts --extensions .ts --out-dir es",
29+
"build:es": "babel src/*.ts --ignore src/types.ts --extensions .ts --out-dir es && cp src/versionedTypes/package.dist.json es/versionedTypes/package.json",
3030
"build:umd": "cross-env NODE_ENV=development rollup -c -o dist/reselect.js",
3131
"build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/reselect.min.js",
3232
"build:types": "tsc",

src/types.ts

Lines changed: 17 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { MergeParameters } from './versionedTypes'
2+
export type { MergeParameters } from './versionedTypes'
3+
14
/*
25
*
36
* Reselect Data Types
@@ -94,56 +97,6 @@ export type GetParamsFromSelectors<
9497
RemainingItems extends readonly unknown[] = Tail<MergeParameters<S>>
9598
> = RemainingItems
9699

97-
/** Given a set of input selectors, extracts the intersected parameters to determine
98-
* what values can actually be passed to all of the input selectors at once
99-
* WARNING: "you are not expected to understand this" :)
100-
*/
101-
export type MergeParameters<
102-
// The actual array of input selectors
103-
T extends readonly UnknownFunction[],
104-
// Given those selectors, we do several transformations on the types in sequence:
105-
// 1) Extract "the type of parameters" for each input selector, so that we now have
106-
// a tuple of all those parameters
107-
ParamsArrays extends readonly any[][] = ExtractParams<T>,
108-
// 2) Transpose the parameter tuples.
109-
// Originally, we have nested arrays with "all params from input", "from input 2", etc:
110-
// `[ [i1a, i1b, i1c], [i2a, i2b, i2c], [i3a, i3b, i3c] ],
111-
// In order to intersect the params at each index, we need to transpose them so that
112-
// we have "all the first args", "all the second args", and so on:
113-
// `[ [i1a, i2a, i3a], [i1b, i2b, i3b], [i1c, i2c, i3c] ]
114-
// Unfortunately, this step also turns the arrays into a union, and weirder, it is
115-
// a union of all possible combinations for all input functions, so there's duplicates.
116-
TransposedArrays = Transpose<ParamsArrays>,
117-
// 3) Turn the union of arrays back into a nested tuple. Order does not matter here.
118-
TuplifiedArrays extends any[] = TuplifyUnion<TransposedArrays>,
119-
// 4) Find the longest params array out of the ones we have.
120-
// Note that this is actually the _nested_ data we wanted out of the transpose step,
121-
// so it has all the right pieces we need.
122-
LongestParamsArray extends readonly any[] = LongestArray<TuplifiedArrays>
123-
> =
124-
// After all that preparation work, we can actually do parameter extraction.
125-
// These steps work somewhat inside out (jump ahead to the middle):
126-
// 11) Finally, after all that, run a shallow expansion on the values to make the user-visible
127-
// field details more readable when viewing the selector's type in a hover box.
128-
ExpandItems<
129-
// 10) Tuples can have field names attached, and it seems to work better to remove those
130-
RemoveNames<{
131-
// 5) We know the longest params array has N args. Loop over the indices of that array.
132-
// 6) For each index, do a check to ensure that we're _only_ checking numeric indices,
133-
// not any field names for array functions like `slice()`
134-
[index in keyof LongestParamsArray]: LongestParamsArray[index] extends LongestParamsArray[number]
135-
? // 9) Any object types that were intersected may have had
136-
IgnoreInvalidIntersections<
137-
// 8) Then, intersect all of the parameters for this arg together.
138-
IntersectAll<
139-
// 7) Since this is a _nested_ array, extract the right sub-array for this index
140-
LongestParamsArray[index]
141-
>
142-
>
143-
: never
144-
}>
145-
>
146-
147100
/*
148101
*
149102
* Reselect Internal Utility Types
@@ -153,28 +106,11 @@ export type MergeParameters<
153106
/** Any function with arguments */
154107
export type UnknownFunction = (...args: any[]) => any
155108

156-
/** An object with no fields */
157-
type EmptyObject = {
158-
[K in any]: never
159-
}
160-
161-
type IgnoreInvalidIntersections<T> = T extends EmptyObject ? never : T
162-
163-
/** Extract the parameters from all functions as a tuple */
164-
export type ExtractParams<T extends readonly UnknownFunction[]> = {
165-
[index in keyof T]: T[index] extends T[number] ? Parameters<T[index]> : never
166-
}
167-
168109
/** Extract the return type from all functions as a tuple */
169110
export type ExtractReturnType<T extends readonly UnknownFunction[]> = {
170111
[index in keyof T]: T[index] extends T[number] ? ReturnType<T[index]> : never
171112
}
172113

173-
/** Recursively expand all fields in an object for easier reading */
174-
export type ExpandItems<T extends readonly unknown[]> = {
175-
[index in keyof T]: T[index] extends T[number] ? Expand<T[index]> : never
176-
}
177-
178114
/** First item in an array */
179115
export type Head<T> = T extends [any, ...any[]] ? T[0] : never
180116
/** All other items in an array */
@@ -191,58 +127,6 @@ export type List<A = any> = ReadonlyArray<A>
191127

192128
export type Has<U, U1> = [U1] extends [U] ? 1 : 0
193129

194-
/** Select the longer of two arrays */
195-
export type Longest<L extends List, L1 extends List> = L extends unknown
196-
? L1 extends unknown
197-
? { 0: L1; 1: L }[Has<keyof L, keyof L1>]
198-
: never
199-
: never
200-
201-
/** Recurse over a nested array to locate the longest one.
202-
* Acts like a type-level `reduce()`
203-
*/
204-
export type LongestArray<S extends readonly any[][]> =
205-
// If this isn't a tuple, all indices are the same, we can't tell a difference
206-
IsTuple<S> extends '0'
207-
? // so just return the type of the first item
208-
S[0]
209-
: // If there's two nested arrays remaining, compare them
210-
S extends [any[], any[]]
211-
? Longest<S[0], S[1]>
212-
: // If there's more than two, extract their types, treat the remainder as a smaller array
213-
S extends [any[], any[], ...infer Rest]
214-
? // then compare those two, recurse through the smaller array, and compare vs its result
215-
Longest<
216-
Longest<S[0], S[1]>,
217-
Rest extends any[][] ? LongestArray<Rest> : []
218-
>
219-
: // If there's one item left, return it
220-
S extends [any[]]
221-
? S[0]
222-
: never
223-
224-
/** Recursive type for intersecting together all items in a tuple, to determine
225-
* the final parameter type at a given argument index in the generated selector. */
226-
export type IntersectAll<T extends any[]> = IsTuple<T> extends '0'
227-
? T[0]
228-
: _IntersectAll<T>
229-
230-
type IfJustNullish<T, True, False> = [T] extends [undefined | null]
231-
? True
232-
: False
233-
234-
/** Intersect a pair of types together, for use in parameter type calculation.
235-
* This is made much more complex because we need to correctly handle cases
236-
* where a function has fewer parameters and the type is `undefined`, as well as
237-
* optional params or params that have `null` or `undefined` as part of a union.
238-
*
239-
* If the next type by itself is `null` or `undefined`, we exclude it and return
240-
* the other type. Otherwise, intersect them together.
241-
*/
242-
type _IntersectAll<T, R = unknown> = T extends [infer First, ...infer Rest]
243-
? _IntersectAll<Rest, IfJustNullish<First, R, R & First>>
244-
: R
245-
246130
/*
247131
*
248132
* External/Copied Utility Types
@@ -253,32 +137,22 @@ type _IntersectAll<T, R = unknown> = T extends [infer First, ...infer Rest]
253137
* Source: https://github.com/sindresorhus/type-fest/blob/main/source/union-to-intersection.d.ts
254138
* Reference: https://github.com/microsoft/TypeScript/issues/29594
255139
*/
256-
export type UnionToIntersection<Union> = (
140+
export type UnionToIntersection<Union> =
257141
// `extends unknown` is always going to be the case and is used to convert the
258142
// `Union` into a [distributive conditional
259143
// type](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
260-
Union extends unknown
261-
? // The union type is used as the only argument to a function since the union
262-
// of function arguments is an intersection.
263-
(distributedUnion: Union) => void
264-
: // This won't happen.
265-
never
266-
// Infer the `Intersection` type since TypeScript represents the positional
267-
// arguments of unions of functions as an intersection of the union.
268-
) extends (mergedIntersection: infer Intersection) => void
269-
? Intersection
270-
: never
271-
272-
/**
273-
* Removes field names from a tuple
274-
* Source: https://stackoverflow.com/a/63571175/62937
275-
*/
276-
type RemoveNames<T extends readonly any[]> = [any, ...T] extends [
277-
any,
278-
...infer U
279-
]
280-
? U
281-
: never
144+
(
145+
Union extends unknown
146+
? // The union type is used as the only argument to a function since the union
147+
// of function arguments is an intersection.
148+
(distributedUnion: Union) => void
149+
: // This won't happen.
150+
never
151+
) extends // Infer the `Intersection` type since TypeScript represents the positional
152+
// arguments of unions of functions as an intersection of the union.
153+
(mergedIntersection: infer Intersection) => void
154+
? Intersection
155+
: never
282156

283157
/**
284158
* Assorted util types for type-level conditional logic
@@ -313,7 +187,7 @@ type LastOf<T> = UnionToIntersection<
313187
: never
314188

315189
// TS4.1+
316-
type TuplifyUnion<
190+
export type TuplifyUnion<
317191
T,
318192
L = LastOf<T>,
319193
N = [T] extends [never] ? true : false
@@ -331,21 +205,6 @@ export type ObjValueTuple<
331205
? ObjValueTuple<T, KT, [...R, T[K & keyof T]]>
332206
: R
333207

334-
/**
335-
* Transposes nested arrays
336-
* Source: https://stackoverflow.com/a/66303933/62937
337-
*/
338-
type Transpose<T> = T[Extract<
339-
keyof T,
340-
T extends readonly any[] ? number : unknown
341-
>] extends infer V
342-
? {
343-
[K in keyof V]: {
344-
[L in keyof T]: K extends keyof T[L] ? T[L][K] : undefined
345-
}
346-
}
347-
: never
348-
349208
/** Utility type to infer the type of "all params of a function except the first", so we can determine what arguments a memoize function accepts */
350209
export type DropFirst<T extends unknown[]> = T extends [unknown, ...infer U]
351210
? U

src/versionedTypes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { MergeParameters } from './ts47-mergeParameters'

src/versionedTypes/package.dist.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"typesVersions": {
3+
">=4.7": {
4+
"index": [
5+
"./ts47-mergeParameters.d.ts"
6+
]
7+
},
8+
"<4.7": {
9+
"index": [
10+
"./ts46-mergeParameters.d.ts"
11+
]
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)