helmbert helmbert - 4 months ago 16
TypeScript Question

TypeScript compiler not enforcing type parameter in implementation of generic interface?

Consider a TypeScript interface with two implementations:

interface IFoo {}

class FooA implements IFoo {}
class FooB implements IFoo {}

Next, consider a generic interface that accepts an implementation of
as type parameter:

interface IFooHandler<F extends IFoo> {
handle(foo: F): string

Now, let's implement the
interface with a specific implementation of
as a type parameter:

class FooAHandler implements IFooHandler<FooA> {
handle(foo: FooB): string {
return "Hello, Foo A!";

This compiles perfectly, using
in version 2.0.3. So here's my question: Why does this compile?

Note that I've used
as parameter type in the
function. I would assume that this code would trigger a compiler error, as the
interface prescribes that the
method should accept a parameter of type
(so, in case of a
, of type
-- and not

I could not find anything on this behaviour in the TypeScript documentation. Is this behaviour intentional, and if so, what's the reasoning behind it? Or am I just using this feature the wrong way?

Just for comparison, implementing the exact same example in Java (omitting the code, as it's quite similar) yields the (expected) compile error:

FooAHandler.java:1: error FooAHandler is not abstract and does not override abstract method handle(FooA) in IFooHandler


This happens because the compiler doesn't compare the types by name, it checks their structures, and since IFoo, FooA and FooB are all empty then they are all equal.

Even when doing:

interface IFoo {
    a: string;

class FooA implements IFoo {
    a: string;

class FooB implements IFoo {
    a: string;

The compiler still won't complain, but once we differentiate between FooA and FooB:

class FooA implements IFoo {
    a: string;
    b: string;

class FooB implements IFoo {
    a: string;
    c: string;

We get the compiler error:

Class 'FooAHandler' incorrectly implements interface 'IFooHandler<FooA>'.  
  Types of property 'handle' are incompatible.  
    Type '(foo: FooB) => string' is not assignable to type '(foo: FooA) => string'.  
      Types of parameters 'foo' and 'foo' are incompatible.  
        Type 'FooA' is not assignable to type 'FooB'.  
          Property 'c' is missing in type 'FooA'.  

(code in playground)

A side note:
Empty objects (like all of your interfaces) will always accepts everything:

interface IFoo { }

function fn(foo: IFoo) {}

fn(3); // ok
fn("string"); // ok
fn({ key: "value" }); // ok

(code in playground)