diff --git a/src/types.ts b/src/types.ts index c1ca68641..9a117f8b3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,7 +71,8 @@ export type OutputParametricSelector< Result, Combiner extends UnknownFunction, Keys = {} -> = ParametricSelector & OutputSelectorFields +> = ParametricSelector & + OutputSelectorFields /** An array of input selectors */ export type SelectorArray = ReadonlyArray @@ -118,6 +119,27 @@ export type Head = T extends [any, ...any[]] ? T[0] : never /** All other items in an array */ export type Tail = A extends [any, ...infer Rest] ? Rest : never +/** Last item in an array. Recursion also enables this to work with rest syntax - where the type of rest is extracted */ +export type ReverseHead = Tail extends [ + unknown +] + ? S + : Tail extends readonly unknown[][] + ? ReverseHead> + : never + +/** All elements in array except last + * + * Recursion makes this work also when rest syntax has been used + * Runs _ReverseTail twice, because first pass turns last element into "never", and second pass removes it. + **/ +export type ReverseTail = _ReverseTail<_ReverseTail> +type _ReverseTail = Tail extends [unknown] + ? [Head] + : Tail extends unknown[] + ? [Head, ..._ReverseTail>] + : never + /** Extract only numeric keys from an array type */ export type AllArrayKeys = A extends any ? { diff --git a/src/versionedTypes/ts47-mergeParameters.ts b/src/versionedTypes/ts47-mergeParameters.ts index 51e19dfa4..7ede1163b 100644 --- a/src/versionedTypes/ts47-mergeParameters.ts +++ b/src/versionedTypes/ts47-mergeParameters.ts @@ -1,11 +1,11 @@ // This entire implementation courtesy of Anders Hjelsberg: // https://github.com/microsoft/TypeScript/pull/50831#issuecomment-1253830522 +import { ReverseHead, ReverseTail } from '../types' + type UnknownFunction = (...args: any[]) => any -type LongestTuple = T extends [ - infer U extends unknown[] -] +type LongestTuple = T extends [infer U extends unknown[]] ? U : T extends [infer U, ...infer R extends unknown[][]] ? MostProperties> @@ -13,11 +13,9 @@ type LongestTuple = T extends [ type MostProperties = keyof U extends keyof T ? T : U -type ElementAt = N extends keyof T - ? T[N] - : unknown +type ElementAt = N extends keyof T ? T[N] : unknown -type ElementsAt = { +type ElementsAt = { [K in keyof T]: ElementAt } @@ -27,11 +25,10 @@ type Intersect = T extends [] ? H & Intersect : T[number] -type MergeTuples< - T extends readonly unknown[][], - L extends unknown[] = LongestTuple -> = { - [K in keyof L]: Intersect> +type MergeTuples> = { + [K in keyof L]: Intersect< + ElementsAt extends readonly unknown[] ? ElementsAt : never + > } type ExtractParameters = { @@ -40,5 +37,30 @@ type ExtractParameters = { export type MergeParameters = '0' extends keyof T - ? MergeTuples> + ? MergeTuples>> : Parameters + +type HasRest = number extends S['length'] + ? true + : false + +type HasExplicit = '0' extends keyof S + ? true + : false + +type HasCombined = true extends HasExplicit & + HasRest + ? true + : false + +type MakeRestExplicit = + true extends HasCombined + ? [ + ...ReverseTail, + ReverseHead extends readonly unknown[] + ? ReverseHead[number] + : never + ] + : true extends HasRest + ? [...T] + : T diff --git a/typescript_test/test.ts b/typescript_test/test.ts index 0b44df121..a121ea3ab 100644 --- a/typescript_test/test.ts +++ b/typescript_test/test.ts @@ -1665,3 +1665,37 @@ function issue555() { const selectorResult2 = someSelector2(state, undefined) const selectorResult3 = someSelector3(state, null) } + +function issue601() { + interface IState { + foo: number + bar: string + } + + const selectFoo = (state: IState) => state.foo + // Includes params in selector to be sure that this does not ruin inference + const selectBar = (state: IState, params: { a: number }) => state.bar + + const selectors = [selectFoo, selectBar] + + const selectFooBar = createSelector( + [selectBar, selectFoo, ...selectors], + (foo, bar, ...rest) => { + rest.push('a') + rest.push(1) + // @ts-expect-error + rest.push(true) + // @ts-expect-error + const nFoo: number = foo + // @ts-expect-error + const nBar: string = bar + return 'foobar' + } + ) + + selectFooBar({ foo: 3, bar: 'bar' }, { a: 3 }) + // @ts-expect-error + const res: number = selectFooBar({ foo: 3, bar: 'bar' }) + // @ts-expect-error + selectFooBar({ foo: 3, bar: 'bar' }) +}