1
type SingleOrArray<T> = T | [T]

function f<T extends 'a' | 'b'>(a: SingleOrArray<T>, b: SingleOrArray<T>) {
    return a && b
}

f('a', ['b'])

Then I got a mistake: Argument of type '"a"' is not assignable to parameter of type 'SingleOrArray<"b">', And 'f' was inferred to be:

function f<"b">(a: SingleOrArray<"b">, b: SingleOrArray<"b">): SingleOrArray<"b">"

I really can't explain why, Need Help!

2
  • T will be inferred from one parameter as a and will be checked against the other parameter. This will work under strictNullChecks: function f<T extends [SingleOrArray<'a' | 'b'>, SingleOrArray<'a' | 'b'>] >(...a: T) { return a[0] && a[1] } let d = f('a', ['b']) Commented Mar 8, 2019 at 13:30
  • Hi, Titian Cernicova-Dragomir, Thanks for your reply. But I still confuse about it:After inferred as 'a', how was it been denied? And after it was inferred as 'b', why it was accepted? Commented Mar 8, 2019 at 14:05

2 Answers 2

2

If this is not representative of what you try to do then, this will not help much, but use of generics is pointless in the function of the example. Just use the type directly, maybe alias it first:

type AOrB = 'a' | 'b';
function f(a: SingleOrArray<AOrB>, b: SingleOrArray<AOrB>) {
    return a && b
}

This way the type will be independent in both arguments. You could work around this in your example by introducing another type parameter, then use one for the first argument and one for the second.

function f<
    T1 extends 'a' | 'b',
    T2 extends 'a' | 'b'
>(a: SingleOrArray<T1>, b: SingleOrArray<T2>) {
    return a && b
}
Sign up to request clarification or add additional context in comments.

8 Comments

So the problem is that T has to be the exact same in both arguments?
@JuanMendes: At least that appears to be what is happening during type resolution, looking at the reported error. Not sure why it would behave that way though.
Hi, H.B., thanks for your reply, sorry for my codes looks strange, in fact it's a only an abstract of another function, which is meaningful. The point is I can't explain why this mistake happens, In my option, 'T' was inferred as 'a' and 'b' separately, and then been joined up as 'a' | 'b'. Can you help to explain what's happening here? Thanks.
@FreemanChen: Well, T is being resolved to one part of the union, and since T is being used for both arguments, you will get a type conflict if the resolved type is not compatible with both arguments. As noted, i do not know why the type collapses like this.
@H.B., Thanks, I guess so too, especially after Titian' comments. But I still can't define how the conflict works. E.g.: Code like this works: type ArrayOrObject<T> = [T] | {x:T} function f<T extends 'a' | 'b'>(...args: ArrayOrObject<T>[]) {} f(['a'], {x:'b'})
|
0

after diving into source codes for a whole day, I think I got the answer:

In fact <T> was inferred as 'a' and 'b' from T and [T] separately, which were called candidates types. These two candidates types have different priority: T in union type definition has a special priority value 1 (View https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts, line 4398, the NakedTypeVariable enum value), while [T] has a common priority value 0, which means higher in priority orders.

Then the higher priority value 0 will overwrite 1 immediately, before comparing the type details. That's why the type 'a' was erased. If they share the same priority value, then they will be merged(see below).

The simplest way to fix is removing <T extends 'a' | 'b'>. without any assertion, T in [T] will be inferred as a wider type string after destructuring a virtual expression ['b'].

I also found how the compiler process when multiple candidate types existed: If candidate types share a basic (like string, number) covariant type, then join them with union symbol |. Otherwise will get the leftmost type for which no type to the right is a super type.

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.