helmbert helmbert - 1 month ago 5
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
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

Answer

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)

Comments