I have the following code:
class Foo<T> {
x?: T extends string ? string | Foo<T> : Foo<T>
}
function bar<T>(): Foo<T> {
const x: Foo<T> = { }
return { x }
}
Why does the compiler yield the following error for return { x }
?
Type 'Foo<T>' is not assignable to type 'T extends string ? string | Foo<T> : Foo<T>'. ts(2322)
Foo<T>
should satisfy both string | Foo<T>
and Foo<T>
, so it should also satisfy the type of Foo.x
regardless of whether T extends string
or not, right?
CodePudding user response:
I agree that, no matter what T
is, the type Foo<T>
should be assignable to T extends string ? string | Foo<T> : Foo<T>
. This is actually a little tricky to verify, since if T
is a union type (e.g., number | Date
), then Foo<T>
will not be a union type (e.g., Foo<number | Date>
), but T extends string ? string | Foo<T> : Foo<T>
will be a union type (e.g., Foo<number> | Foo<Date>
). That's because T extends string ? ... : ...
is a distributive conditional type which splits unions into their members before evaluation, and reunites into a new union afterward. So the fact that the conditional type is always assignable to Foo<T>
independently of T
depends specifically on the definition of Foo<T>
, and the fact that it is covariant in T
(see this Q/A for a discussion of variance).
But the compiler does not see this. Why?
Well, generally speaking, the evaluation of conditional types that depend on generic type parameters is deferred by the compiler. There was some work done in microsoft/TypeScript#46429 to allow a type to be assignable to a conditional type if it was assignable to both the true and false branches of that type, but this only works for non-distributive types that don't use the infer
keyword. Since you have a distributive type, it doesn't work here.
So, evaluation of T extends string ? string | Foo<T> : Foo<T>
is deferred until such time as T
is specified. Inside the body of bar()
, T
is unspecified, so the conditional type is not evaluated there. It is essentially opaque to the compiler, and it won't be able to verify that you can assign a value to it, unless that value is also of the identical conditional type. And Foo<T>
is not. So the compiler complains.