It seems that TypeScript is facing a limitation in this scenario.
During the type assignment checks between X
and Y
, the compiler sometimes skips a complete "structural check" to optimize performance. This means it doesn't fully evaluate each member of X
and compare it with those of
Y</code recursively, especially when both types are based on the same parent type.</p>
<p>Sometimes, if <code>X
and
Y
are derived from a common type like
F<T>
, the variance properties of
F<T>
(covariant, contravariant, bivariant, invariant) play a role in shortcutting the structural check process. For example, if
F<T>
is covariant in
T
, then checking
A extends B
allows the direct conclusion that
F<A> extends F<B>
.
However, there's an issue where incorrect variance markers are assigned by the compiler for conditional types such as
type F<T> = T extends U ? X : Y
. These conditional types end up being treated as bivariant in
T
, which leads to unexpected outcomes during type assignments.
In the case of Type<T>
, even though it appears to have a bivariance marker, it does not exhibit bivariance behavior in T
:
function foo<T, U extends T>(t: Type<T>, u: Type<U>) {
u = t; // works
t = u; // really?
}
This is simply a current limitation in TypeScript's handling of such scenarios.
To work around this limitation, TypeScript 4.7 has introduced optional variance annotations for type parameters. By marking types as covariant or contravariant explicitly, you can guide the compiler to make more accurate decisions:
One potential solution would be to denote Type<T>
as covariant in T
, but unfortunately, variance annotations are only supported in specific type aliases and not directly in type parameters.
However, you can apply variance annotations on wrapper types like BoxType<T>
to achieve the desired behavior:
type BoxType<out T> = {
value: Type<T>
}
By doing so, you can ensure correct assignability behaviors within nested type structures.
const x: BoxType<boolean> = { value: z };
const y: BoxType<true> = x; // this will trigger an error
You can experiment further with the provided code snippet using the playground link here.