Reputation: 37994
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
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'.
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
Upvotes: 6