helmbert
helmbert

Reputation: 37994

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 IFoo as type parameter:

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

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

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

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

Note that I've used FooB as parameter type in the FooAHandler's handle() function. I would assume that this code would trigger a compiler error, as the IFooHandler<F> interface prescribes that the handle method should accept a parameter of type F (so, in case of a IFooHandler<FooA>, of type FooA -- and not FooB).

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

Upvotes: 1

Views: 945

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164139

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)

Upvotes: 6

Related Questions