2

I'm using a library (Constate) which contains types that I'd like to improve upon. It maps an array of functions to a different array of functions. Both arrays of functions share the same return types.

I.e. with objects, I can do:

const obj = {
  a: arg => 123,
  b: arg => 'str',
};

type T = { [k in keyof typeof obj]: () => ReturnType<typeof obj[k]> }
/*
T is {
  a: () => number;
  b: () => string;
}
*/

With arrays, this doesn't work:

const arr = [
  arg => 123,
  arg => 'str',
] as const;

type t2 = { [k in keyof typeof arr]: () => ReturnType<typeof arr[k]> }
/*
Type 'readonly [(_: any) => number, (_: any) => string][k]' does not satisfy the constraint '(...args: any) => any'.
  Type '((_: any) => number) | ((_: any) => string) | 2 | (() => string) | (() => string) | { (...items: ConcatArray<((_: any) => number) | ((_: any) => string)>[]): (((_: any) => number) | ((_: any) => string))[]; (...items: (((_: any) => number) | ... 1 more ... | ConcatArray<...>)[]): (((_: any) => number) | ((_: any) =>...' is not assignable to type '(...args: any) => any'.
    Type '2' is not assignable to type '(...args: any) => any'.
*/

I'm assuming Type '2' in the error comes from .length.

Is there a way to get only the array indices and use it with an index signature?

3 Answers 3

3

TypeScript will map arrays/tuples to arrays/tuples as of TypeScript 3.1, but this only works when doing a homomorphic mapping of the form {[K in keyof T]: ...} where T is a type parameter, as mentioned in this comment in the issue @basarat linked, microsoft/TypeScript#27995. For now one way around this is to introduce indirection to turn your concrete type typeof arr into a type parameter:

type T2 = typeof arr extends infer A ? { [K in keyof A]: () => RT<A[K]> } : never;

Also note (as mentioned in microsoft/TypeScript#27351) that the compiler refuses to understand that you're mapping over just the numeric indices, and so it will not believe that A[K] will always be a function... and so ReturnType<A[K]> will give an error. My workaround here is to define my own ReturnType (called RT) which does not care if its argument is a function:

type RT<T> = T extends (...args: any) => infer R ? R : never;

Given those, the type T2 will evaluate to:

// type T2 = readonly [() => number, () => string]

which is presumably what you're trying to achieve here.

Hope that helps; good luck!

Playground link

Sign up to request clarification or add additional context in comments.

1 Comment

That's really cool! Could you explain what typeof arr extends infer A is doing? I read the docs on infer a few times. In this case, is infer A basically getting the subset of A that doesn't include stuff in Array.prototype?
3

short answer

To get the indices of a tuple, use:

type IdxOf<T> = { [idx in keyof T]: idx }[any]

instead of

keyof

because...

const arr = [(_arg: any) => 123, (_arg: any) => 'str'] as const

// the issue is that `keyof typeof arr` is not just  "0" | "1"
type keyofArr = keyof typeof arr
// keyofArr is "0" | "1" | "length" | "toString" | ...

// IdxOf<T> fixes this:
type IdxOfArr = IdxOf<typeof arr>
// IdxOfArr is "0" | "1"

type t2 = { [k in IdxOf<typeof arr>]: () => ReturnType<typeof arr[k]> }
// t2 is { 0: () => number; 1: () => string; }

Comments

1

The issues is that using mapped types over tuples lists over methods as well (More details on this issue).

Quick Fix

List the keys manually if you must:

const obj = {
  a: () => 123,
  b: () => 'str',
};

type TObj = { [Key in keyof typeof obj]: () => ReturnType<typeof obj[Key]> }
/*
TOjb is {
  a: () => number;
  b: () => string;
}
*/

const arr = [
  () => 123,
  () => 'str',
] as const;

// type Keys = keyof typeof arr; // Instead of 
type Keys = '0' | '1'; // Use names 

type TArr = { [Key in Keys]: () => ReturnType<typeof arr[Key]> }
/*
  All good
*/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.