If one type in a union type extends the other, when awaiting a promise with this union type the least specific type is inferred.
In the code below a is of type Promise\
TypeScript Version: 2.4.1
Code
type A = { a: number }
type B = { a: number, b: number }
type C = A | B
async function f(): Promise<C> {
throw ''
}
(async () => {
const a = f()
const b = await a
})()
Expected behavior:
b is of type C
Actual behavior:
b is of type A
This is presented a little strangely by the language service but I believe it is correct because
{ a: number } is equivalent to { a: number } | { a: number, b: number}
There maybe assignability, but there seems to be an issue:
type A = { a: number }
type B = { a: number, b: number }
type C = A | B
async function f(): Promise<C> {
throw ''
}
function g(): C {
throw ''
}
(async () => {
const a = f() // type Promise<C>
const b = await a // type A
const c = g() // type C
f().then((d) => { /* d type C */ })
})()
Only when await is used does the type change in a surprising way.
Union types of this form behave strangely because we don't "realize" that they're collapsible until we actually go and try to do something with them (e.g. map into a promise return type).
In an ideal world it would just not be legal to write a union type like this, but generic instantiation means there's not always a recognizable line of code associated with the declaration of such a type. For the time being, just... don't write types like that.
Just to be explicit, I think you are pointing out that:
type C = { a: number } | { a: number; b: number; };
is a _silly_ type (that ideally would be illegal) that causes issues in certain constructs and that really, it should be written as:
type C = { a: number; b?: number; };
and then there would be no issues.
Most helpful comment
There maybe assignability, but there seems to be an issue:
Only when
awaitis used does the type change in a surprising way.