How to dynamically infer a the values of a generic array as a union in TypeScript?

Viewed 591

Given the following declaration:

declare function useStrings<T extends string[]>(
  arr: T,
  fn: (item: T extends Array<infer U> ? U : never) => void
): void;

I expect the item parameter in fn to be a union type of the values of T.

However, when invoked like this, item is typed as a string:

useStrings(["a", "b", "c"], (item) => {});
//                           ^^^^ string

The following example gives the desired result, but is of course not a desirable solution:

useStrings(["a", "b", "c"] as ("a" | "b" | "c")[], (item) => {});
//                                                  ^^^^ "a" | "b" | "c"

How to get item inferred as a union without resorting to hacky assertions such as in the last example?

2 Answers

expr as const will lower it down into the narrowest possible tuple type, so you should use that and extend your function to work with readonly arrays:

declare function useStrings<T extends readonly string[]>(
  arr: T,
  fn: (item: T extends readonly (infer U)[] ? U : never) => void
): void;

useStrings(["a", "b", "c"] as const, (item) => {});

Playground link

I don't use infers and my solutions work. Try it yourself!

declare function useStrings<T extends string>(
    arr: T[],
    fn: (item: T) => void
): void;

Probably don't use infer

Related