What are the rules that govern how Typescript infers generic types in function parameters?
The generic type is not being correctly inferred in the code below - why?
//* main type ******************************************************************************************/
type FunctionChainArray_<
T extends [AnyFn, ...AnyFn[]],
Parent extends AnyFn | never = never,
Current extends AnyFn = T[0],
Rest extends [AnyFn, ...AnyFn[]] = T extends [AnyFn, ...infer R extends [AnyFn, ...AnyFn[]]]
? R
: never,
> = {
singleFirstValue: [Current]
startOfChain: [Current, ...FunctionChainArray_<Rest, Current>]
chain: [(Input: ReturnType<Parent>) => ReturnType<Current>, ...FunctionChainArray_<Rest, Current>]
last: [(Input: ReturnType<Parent>) => ReturnType<Current>]
}[[Parent] extends [never]
? [Rest] extends [never]
? 'singleFirstValue' // first value has no Rest
: 'startOfChain' // build chain
: [Rest] extends [never] // mid chain case
? 'last' // last value
: 'chain'] // build chain
type FunctionChainArray<
T extends readonly AnyFn[],
TU extends AnyFn[] = UnwrapAsConstArray<T, AnyFn[]>, // remove readonly
TP extends [AnyFn, ...AnyFn[]] = TU extends [AnyFn, ...AnyFn[]] ? TU : never,
> = IsFinite<
TU,
FunctionChainArray_<TP>,
TU extends ((input: infer I) => any)[] ? ((input: I) => I)[] : [never]
>
//* compose function ***************************************************************************************/
const compose = <T extends readonly AnyFn[]>(...fnsToAdd: FunctionChainArray<T>) => {
const composedFn = fnsToAdd.reduce(
(previousFn, currentFn) => (input) => currentFn(previousFn(input)),
)
return composedFn // as unknown as { T: T; FChain: FunctionChainArray<T> }
}
//* used function ***************************************************************************************/
const logger = <T extends unknown>(str: string, rv: T) => { console.log(str); return rv }
const chainArray = [
(a: 'a') => logger(`A:${a}`, 'Ra' as unknown as 'Ra'),
(b: 'Ra') => logger(`B:${b}`, 'Rb' as unknown as 'Rb'),
(c: 'Rb') => logger(`C:${c}`, 'Rc' as unknown as 'Rc'),
(d: 'Rc') => logger(`D:${d}`, 'd' as unknown as 'd'),
] as const
// without inference, when the type is supplied it is correctly typecheck
const works = compose<typeof chainArray>(
(a: 'a') => logger(`A:${a}`, 'Ra' as unknown as 'Ra'),
(b: 'Ra') => logger(`B:${b}`, 'Rb' as unknown as 'Rb'),
(c: 'error') => logger(`C:${c}`, 'Rc' as unknown as 'Rc'),
(d: 'Rc') => logger(`D:${d}`, 'd' as unknown as 'd'),
)
// when one infer's the type it doesn't work
const doesntWork = compose(
(a: 'a') => logger(`A:${a}`, 'Ra' as unknown as 'Ra'),
(b: 'Ra') => logger(`B:${b}`, 'Rb' as unknown as 'Rb'),
(c: 'error') => logger(`C:${c}`, 'Rc' as unknown as 'Rc'),
(d: 'Rc') => logger(`D:${d}`, 'd' as unknown as 'd'),
)