Description
I'm aware that this is likely to be a design limitation; I can't find an existing issue about this specific problem so I'm filing this one for reference.
TypeScript Version: 3.8.0-dev.20191220
Search Terms: variance, composition
Code
interface Fn<A, B> {
(a: A): B;
then<C>(next: Fn<B, C>): Fn<A, C>;
}
declare const fn: Fn<string, number>;
const contravariantInA: Fn<"a", number> = fn; // error!
// Type 'Fn<string, number>' is not assignable to type 'Fn<"a", number>'.
// Type 'string' is not assignable to type '"a"'.
const covariantInB: Fn<string, unknown> = fn; // okay
Expected behavior: Fn<A, B>
should be contravariant in A
.
Actual behavior: Fn<A, B>
is invariant in A
.
I came upon this while investigating a Stack Overflow question in which someone had built a composable type like Fn<A, B>
and had to use a workaround to allow the then()
method to accept something with a wider input.
@jack-williams suggests that the issue happens because the checker defaults to covariance for A
when checking it (in typeArgumentsRelatedTo()
)... and combined with its contravariant use in the call signature, this results in invariance.
A full structural check might not have this problem but I imagine it might be hard to guarantee that such a check would terminate? (e.g., Fn<A, B>
depends on Fn<B, C>
which depends on Fn<C, D>
which depends on Fn<D, E>
etc?)
If there's no plausible way for the compiler to correctly infer the variance here, perhaps this is just another request for variance annotations as per #1394?
Not sure. Anyway, thanks!
Playground Link: Provided
Related Issues:
#1394: let us mark the parameters as covariant/contravariant/invariant ourselves
#32674: a different variance issue
#33872: a different variance bug