Why is this code allowed in TS?
interface IFoo {
foo(a: string | number): void
}
class Bar implements IFoo {
foo(a: string) { }
}
I think the compiler should throw error, as param a: string
of Bar
is narrower/not substitutable to a: string | number
of IFoo
.
This is deliberate design and intended behavior due to Function Parameter Bivariance.
When comparing the types of function parameters, assignment succeeds if either the source parameter is assignable to the target parameter, or vice versa. [...]
https://www.typescriptlang.org/docs/handbook/type-compatibility.html#function-parameter-bivariance
Essentially, this means that function types are checked in both ways. The class implementation of foo
is not only assignable to the declaration in IFoo
if its type is more narrow but also if it's wider. A type error only occurs if neither the interface declaration nor the class type implementation are assignable to one another.
interface IFoo {
foo(a: string | number): void;
}
class Bar implements IFoo {
foo(a: string) {}
}
class Baz implements IFoo {
foo(a: string | number | boolean) {}
}
class Invalid implements IFoo {
foo(a: string | boolean) {}
//~~~ Property 'foo' in type 'Invalid' is not assignable...
}
You can prevent this by enabling --strictFunctionTypes
.
However, there is another important thing to notice. The strict parameter type checking only works when declaring foo
as an arrow function (foo: (a: string | number) => void;
).
During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, the setting only applies to functions written in function syntax, not to those in method syntax.
https://www.typescriptlang.org/tsconfig/#strictFunctionTypes
Now you get your expected parameter type checking:
interface IFoo {
foo: (a: string | number) => void; // <- !! function syntax
}
class Bar implements IFoo {
foo(a: string) { }
//~~~ Property 'foo' in type 'Bar' is not assignable...
}