Typescript: strictFunctionTypes has different behavior with parameter types and return types

Created on 5 Oct 2017  ·  3Comments  ·  Source: microsoft/TypeScript

cc @ahejlsberg

TypeScript Version: master

Code

declare class C {
  static a(f: (a: C) => C): void;
  static b(): (a: C) => C;
}
declare class D extends C {
  private p: void;
  static a(f: (a: D) => D): void;
  static b(): (a: D) => D;
}

Expected behavior:

no error or both a and b make an error.

Actual behavior:

$ node built/local/tsc.js index.d.ts --noEmit --strictFunctionTypes
index.d.ts(5,15): error TS2417: Class static side 'typeof D' incorrectly extends base class static side 'typeof C'.
  Types of property 'b' are incompatible.
    Type '() => (a: D) => D' is not assignable to type '() => (a: C) => C'.
      Type '(a: D) => D' is not assignable to type '(a: C) => C'.
        Types of parameters 'a' and 'a' are incompatible.
          Type 'C' is not assignable to type 'D'.
            Property 'p' is missing in type 'C'.
Bug Fixed

Most helpful comment

So, it turns out we can't tighten the rules for the original example. If we did we would break covariance for a number of core types, such as Array<T>, which simply isn't an option. The issue is that many types rely on covariance for the result position of a callback parameter of a method. For example, the reduce method in Array<T>:

interface Array<T> {
    // ...
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
    // ...
}

If we were to strictly (i.e. contravariantly) check the return position of that callback in --strictFunctionTypes mode, Array<T> would become invariant.

However... It is the case that we aren't correctly checking callback parameters of regular function types in strict function types mode. For example, we should error on both assignments below, but we only error on the second one:

declare class C {
    private c: void;
}
declare class D extends C {
    private d: void;
}
declare let f1: (f: (x: C) => C) => void;
declare let f2: (f: (x: D) => D) => void;
f1 = f2;  // Should be an error
f2 = f1;  // Error

All 3 comments

I'm not sure this difference is intended or not.

I think you're right, the following should be an error with --strictFunctionTypes:

declare class C {
    static a(f: (x: C) => C): void;
}
declare class D extends C {
    private p: void;
    static a(f: (x: D) => D): void;
}

We currently don't report an error because of overlap in functionality between strict function type checking and covariant checking for callback parameters (#15104). This causes us to always check the return type of a callback parameter bivariantly when we should actually check it contravariantly in strict function types mode.

Definitely a corner case, but one that we should get right.

So, it turns out we can't tighten the rules for the original example. If we did we would break covariance for a number of core types, such as Array<T>, which simply isn't an option. The issue is that many types rely on covariance for the result position of a callback parameter of a method. For example, the reduce method in Array<T>:

interface Array<T> {
    // ...
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
    // ...
}

If we were to strictly (i.e. contravariantly) check the return position of that callback in --strictFunctionTypes mode, Array<T> would become invariant.

However... It is the case that we aren't correctly checking callback parameters of regular function types in strict function types mode. For example, we should error on both assignments below, but we only error on the second one:

declare class C {
    private c: void;
}
declare class D extends C {
    private d: void;
}
declare let f1: (f: (x: C) => C) => void;
declare let f2: (f: (x: D) => D) => void;
f1 = f2;  // Should be an error
f2 = f1;  // Error
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Antony-Jones picture Antony-Jones  ·  3Comments

manekinekko picture manekinekko  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

siddjain picture siddjain  ·  3Comments