Covariance with undefined in TypeScript Covariance with undefined in TypeScript typescript typescript

Covariance with undefined in TypeScript


This is a design decision. All method parameters behave bivariantly. This means that as far as ts is concerned, for methods (_x: number) => void is a subtype of to (_x: number | number) => void (and vice-versa). This is obviously unsound.

Initially not only method parameters behaved bivariantly but all function signature parameters. To fix this, the strictFuctionTypes flag was added in typescript 2.6. From the PR:

With this PR we introduce a --strictFunctionTypes mode in which function type parameter positions are checked contravariantly instead of bivariantly. The stricter checking applies to all function types, except those originating in method or constructor declarations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array) continue to mostly relate covariantly. The impact of strictly checking methods would be a much bigger breaking change as a large number of generic types would become invariant (even so, we may continue to explore this stricter mode).

(highlight added)

So here we get a glimpse into the decision to have methods parameters continue to relate bivariantly. It's for convenience. Without this unsoundness, most classes would be invariant. For example if Array were invariant, Array<Dog> would not be a subtype of Array<Animal>, creating all sort of pain points in pretty basic code.

While definitely not equivalent, if we use a function field instead of a method (with strictFunctionTypes turned on), we do get an error that Type '(x: number) => void' is not assignable to type '(_x: number | undefined) => void'

abstract class A {    // You can pass an undefined to any A    public foo!: (_x: number | undefined) => void;}class B extends A {    // Error here    public foo: (x: number) => void = x => {        if (x === undefined) {            throw new Error("Type error!");        }    }}function makeAnA(): A {    //And here     return new B();}function test() {    const b = makeAnA();    // b is a B, so this should not be possible    b.foo(undefined);}

Playground Link

Note: The code above gives an error only with strictFunctionTypes as without it all function parameter continue to behave bivariantly.