30

Can someone please explain, why in this code the assignment to the constant of type InterfaceA works, but the assignment to the constant of type InterfaceB throws an error:

interface InterfaceA {
  doSomething (data: object): boolean;
}

interface InterfaceB {
  doSomething: (data: object) => boolean;
}

function doIt (data: { type: string; }): boolean {
    return true;
}

const A: InterfaceA = {
    doSomething: doIt
};
const B: InterfaceB = {
    doSomething: doIt
};

For an online demo, see: http://www.typescriptlang.org/play/index.html?ssl=19&ssc=1&pln=1&pc=1#code/JYOwLgpgTgZghgYwgAgJLmvJBBZBvAWAChlkATAewGUKBbCMAC1AHNkAKMuMOALmQoAjAFYQEYAJT9BFCgBsIcEAG5iAX2LFQkWIhTodWCACF8xUpRr0mrfp258BIsZOQBeAHzIZ8xSvWaRDAAriDiwBQg5BSoYBxcPPx4yGAAngAOEPwAzmBQrMrIalLesgpKZkQAkFAMwVBRecEQqkQaRMQIkbnI2PwGmHq4bpVVlnQMzCAs-JSx6q1dID3G-Ri6SKYjhNXj1lMz0fNtrUA

To me, both interfaces are defining the same, only the notation is different.

If this is not a bug in TypeScript, and there is a real reason, then let's come to my second question: I need to specify, that "doSomething" is optional and can either be a function, or a RegExp:

interface InterfaceB {
  doSomething?: ((data: object) => boolean) | RegExp;
}`

How could I achieve this, with the notation of InterfaceA?

  • Have you see this thread? https://stackoverflow.com/questions/56055658/what-is-the-difference-between-class-method-vs-property-function-vs-property-arr – Dai Mar 12 '20 at 12:01
  • 1
    I don't see how this thread answers my question. It's regarding different method definitions in classes. Different notations generate different code there - and while looking at the generated code, everything is transparent and clear. My question is regarding interfaces, and here you can't look into generated code, because they don't generate code. –  Mar 12 '20 at 14:28

1 Answers1

46

1.) There is a difference between method and function property declaration:

interface InterfaceA {
  doSomething(data: object): boolean; // method declaration
}

interface InterfaceB {
  doSomething: (data: object) => boolean; // function as property declaration
}

2.) TypeScript 2.6 introduces a compiler flag for stronger-typed, sound function types:

Under --strictFunctionTypes 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. (my emphasis)

So in general that is a good thing. In your example, InterfaceB has following contract: "Every function that can deal with a general object is compatible". But you want to assign a function doIt, that expects specific objects of type { type: string; } as input. A client that uses InterfaceB thinks, it is enough to pass object, but the implementation doIt wants something more concrete, so you rightfully get that error.

And why doesn't the error happen with InterfaceA ?

In contrast, methods like doIt in InterfaceA are excluded from --strictFunctionTypes for practical reasons. The developers decided the type system to be not too pendantic with built-in methods of Array etc. to have a reasonable balance between correctness and productivity.

So, in favor of stronger types, I would prefer the following type, which works for your case (sample):

interface InterfaceB {
  doSomething: ((data: { type: string; }) => boolean) | RegExp;
}
ford04
  • 66,267
  • 20
  • 199
  • 171