Reputation: 409
The following code compiles fine
interface W {
x: string;
}
interface M {
x: string;
}
type D<N extends M> = N | W;
declare function a<N extends M, Q extends D<N> = D<N>>(): Q | Q[] | undefined;
declare function b<N extends M, Q extends D<N> = D<N>>(p?: Q | Q[]): void;
const x = a();
b(x);
Things get interesting when I put function b
inside a class:
export class C<N extends M, Q extends D<N>> {
f(p?: Q | Q[]): void {}
g() {
const y = a();
this.f(a());
this.f(y);
}
}
Here, C.f
should be equivalent to function b
. However, I get a type error on the line this.f(y)
. Interestingly, the line this.f(a())
compiles fine.
Argument of type 'W | M | (W | M)[] | undefined' is not assignable to parameter of type 'Q | Q[] | undefined'.
Type 'W' is not assignable to type 'Q | Q[] | undefined'.
Type 'W' is missing the following properties from type 'Q[]': length, pop, push, concat, and 28 more.
I don't understand this error, because type Q
should be equivalent to W | N
so how can W
not be assignable to Q
?
Upvotes: 1
Views: 53
Reputation: 51034
The difference is caused by having type parameters on a function, vs. type parameters on a class. To simplify the answer, I'll remove the Q[]
option on a
, b
and f
since the same behaviour is exhibited without it.
In the assignment x = a()
, Typescript needs to infer a type for the variable x
. This requires choosing concrete types for the type variables N
and Q
; in the absence of other constraints, their upper bounds are chosen, so x
gets the type M | W | undefined
. The important point here is that the compiler is free to choose the upper bound D<M>
as the concrete type for Q
.
Then, in the call b(x)
, Typescript is equally free to choose N = M
and Q = D<M>
, so there is no type error.
In the class, the assignment y = a()
is exactly as above, so y
has the type M | W | undefined
. However, then in the call to f(y)
, Typescript is not free to choose N = M
and Q = D<M>
, because these N
and Q
are type parameters of the class. It is not sound here to infer N = M
and Q = D<M>
, because someone:
class M2 implements M { ... }
and create a C<M2, D<M2>>
object.type D2<N extends M> = D<N> & { ... }
and create a C<M, D2<M>>
object.So in the call f(y)
, it is not safe to assign y
of type M | W | undefined
to f
's parameter of type Q
, because e.g. when Q = D<M2>
, or Q = D2<M>
, or something else, then the variable y
is not Q
or a subtype of Q
.
Upvotes: 1