Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infer correct state when input selectors are a mix of explicit and rest syntax in createSelector #602

Merged
merged 3 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MergeParameters, ExtractParams } from './versionedTypes'
import type { MergeParameters } from './versionedTypes'
export type { MergeParameters } from './versionedTypes'

/*
Expand Down Expand Up @@ -71,7 +71,8 @@ export type OutputParametricSelector<
Result,
Combiner extends UnknownFunction,
Keys = {}
> = ParametricSelector<State, Props, Result> & OutputSelectorFields<Combiner, Keys>
> = ParametricSelector<State, Props, Result> &
OutputSelectorFields<Combiner, Keys>

/** An array of input selectors */
export type SelectorArray = ReadonlyArray<Selector>
Expand All @@ -89,13 +90,9 @@ export type EqualityFn = (a: any, b: any) => boolean
export type SelectorResultArray<Selectors extends SelectorArray> =
ExtractReturnType<Selectors>

/** Finds union of the first parameter from an array of functions and turns it into an intersection */
type GetIntersectedFirstParam<T extends readonly UnknownFunction[]> =
UnionToIntersection<ExtractParams<T>[number][0]>

/** Determines the combined single "State" type (first arg) from all input selectors */
export type GetStateFromSelectors<S extends SelectorArray> =
GetIntersectedFirstParam<S>
MergeParameters<S>[0]

/** Determines the combined "Params" type (all remaining args) from all input selectors */
export type GetParamsFromSelectors<
Expand All @@ -122,6 +119,27 @@ export type Head<T> = T extends [any, ...any[]] ? T[0] : never
/** All other items in an array */
export type Tail<A> = 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<S extends readonly unknown[][]> = Tail<S> extends [
unknown
]
? S
: Tail<S> extends readonly unknown[][]
? ReverseHead<Tail<S>>
: 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<S> = _ReverseTail<_ReverseTail<S>>
type _ReverseTail<S> = Tail<S> extends [unknown]
? [Head<S>]
: Tail<S> extends unknown[]
? [Head<S>, ..._ReverseTail<Tail<S>>]
: never

/** Extract only numeric keys from an array type */
export type AllArrayKeys<A extends readonly any[]> = A extends any
? {
Expand Down
2 changes: 1 addition & 1 deletion src/versionedTypes/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { MergeParameters, ExtractParams } from './ts47-mergeParameters'
export { MergeParameters } from './ts47-mergeParameters'
52 changes: 38 additions & 14 deletions src/versionedTypes/ts47-mergeParameters.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
// 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 readonly unknown[][]> = T extends [
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readonly unknown[][] requirement has to be removed from several places since ReverseHead and ReverseTail use recursive types to find the first an last tuple element. Typescript complains when a condition (like readonly unknown[][]) has to to be compared with a type that is recursive, since it can't check the result of that type.

The checks of whether a type is readonly unknown[][] are moved to conditintional types instead where it is needed.

These are internal types specifically used for this use case, so removing the requirement should not be an issue.

infer U extends unknown[]
]
type LongestTuple<T> = T extends [infer U extends unknown[]]
? U
: T extends [infer U, ...infer R extends unknown[][]]
? MostProperties<U, LongestTuple<R>>
: never

type MostProperties<T, U> = keyof U extends keyof T ? T : U

type ElementAt<T extends unknown[], N extends keyof any> = N extends keyof T
? T[N]
: unknown
type ElementAt<T, N extends keyof any> = N extends keyof T ? T[N] : unknown

type ElementsAt<T extends readonly unknown[][], N extends keyof any> = {
type ElementsAt<T, N extends keyof any> = {
[K in keyof T]: ElementAt<T[K], N>
}

Expand All @@ -27,16 +25,42 @@ type Intersect<T extends readonly unknown[]> = T extends []
? H & Intersect<T>
: T[number]

type MergeTuples<
T extends readonly unknown[][],
L extends unknown[] = LongestTuple<T>
> = {
[K in keyof L]: Intersect<ElementsAt<T, K>>
type MergeTuples<T, L extends unknown[] = LongestTuple<T>> = {
[K in keyof L]: Intersect<
ElementsAt<T, K> extends readonly unknown[] ? ElementsAt<T, K> : never
>
}

export type ExtractParams<T extends readonly UnknownFunction[]> = {
type ExtractParameters<T extends readonly UnknownFunction[]> = {
[K in keyof T]: Parameters<T[K]>
}

export type MergeParameters<T extends readonly UnknownFunction[]> =
'0' extends keyof T ? MergeTuples<ExtractParams<T>> : Parameters<T[number]>
'0' extends keyof T
? MergeTuples<MakeRestExplicit<ExtractParameters<T>>>
: Parameters<T[number]>

type HasRest<S extends readonly unknown[]> = number extends S['length']
? true
: false

type HasExplicit<S extends readonly unknown[]> = '0' extends keyof S
? true
: false

type HasCombined<S extends readonly unknown[]> = true extends HasExplicit<S> &
HasRest<S>
? true
: false

type MakeRestExplicit<T extends readonly unknown[][]> =
true extends HasCombined<T>
? [
...ReverseTail<T>,
ReverseHead<T> extends readonly unknown[]
? ReverseHead<T>[number]
: never
]
: true extends HasRest<T>
? [...T]
: T