简体   繁体   中英

Union type for generic param

The proxy return conditional randomly type.

const numbersArray = [1,2,3,4];
const stringsArray = ['1','2','3','4'];


function func<T>(array: T[]): T[][] {
  return [[array[0], array[1]], [array[2], array[3]]];
}

const proxy = () => Math.random() < 0.5 ? numbersArray : stringsArray;

const resulNumbers = func(numbersArray);

const resultStrings = func(stringsArray);

const resultUnion = func(proxy()); // error

error

const proxy: () => number[] | string[]

Argument of type 'number[] | string[]' is not assignable to parameter of type 'number[]'.
  Type 'string[]' is not assignable to type 'number[]'.
    Type 'string' is not assignable to type 'number'.(2345)

link to playground

The right way to resolve this issue, any ideas?

This function:

const proxy = () => Math.random() < 0.5 ? numbersArray : stringsArray;

has a return type of:

number[] | string[]

This type says that the you either have an array of all numbers, or an array of all strings, but never a mix of both.

So what is the type of the members of that type? You might think it's number | string number | string , but that's not quite true. If you take string | number string | number and add the array notation to the end, you get:

(number | string)[]

This type says that it's an array of strings or numbers, and each member could be either. You can now mix both. This is not the same thing. This means that the member type cannot be inferred, because that member type can't be used to construct the original array type.

Now lets look at the generic function:

function func<T>(array: T[]): T[][] {
  return [[array[0], array[1]], [array[2], array[3]]];
}

This function tries to find the member type T for the array T[] . But as demonstrated above, there is no good member type for string[] | number[] string[] | number[] . So typescript fails to infer T and throws an error.

So to make your function work, the member type T needs to be inferable. You can do that by casting the return type of proxy to a compatible array type where only the members of the array are a union.

const proxy: () => (string | number)[] =
  () => Math.random() < 0.5 ? numbersArray : stringsArray;

Now T can become string | number string | number and everything works like you expect.

const resultUnion = func(proxy()); // type: (string | number)[][]

Playground

The error happens because how the type argument T resolves for that call to func . The return type of proxy is

number[] | string[]

and the type for T gets inferred based on how number[] | string[]number[] | string[] fits in the following signature:

function func<T>(x: T[]): T[][]

So x must be an array of T s, which may be any type as far as func is concerned. In the case of number[] | string[]number[] | string[] the type for T is inferred to be number , because number[] is the first type in the union that matches T[] for x . Unfortunately, string[] is not taken into account.

You might want T to be number | string number | string for this call, and you can do that by explicitly specifying the types while calling func :

const xs: (number | string)[][] = func<number | string>(proxy())

The answer to the question this duplicates explains why TypeScript doesn't always infer unions for generic type parameters; it's because it's often indicative of an error when callers specify multiple things for a single generic type parameter, and if the compiler always inferred a union in such situations, it would allow a lot of probably-wrong code. See microsoft/TypeScript#19656 for "official" discussion about this.

Anyway in your case I would probably change the func() signature to be like this:

function func<T extends any[]>(array: T): T[number][][] {
  return [[array[0], array[1]], [array[2], array[3]]];
}

in which case T is actually the type of the array passed in and not of its members, which is T[number] . That should work for you, I think.

Hope that helps; good luck!

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM