I’ve come across an interesting issue while working on a TypeScript project (version 2.9.2) involving unexpected polymorphic behavior. In languages like Java and C#, both classes and interfaces contribute to defining polymorphic behaviors. For example, in the following scenario, it is valid for item1
to be of type A
as well as type B
, and for item2
to be of type C
as well as type D
:
interface A { }
class B implements A { }
class C { }
class D extends C { }
However, in TypeScript, I've noticed that this may not always hold true. Here's a simplified version of my setup:
interface A {
new (str: string): Module;
someFunction(str: string): A;
}
class B implements A {
constructor(str: string) { /* ... */ }
someFunction(str: string): B { /* ... */ }
}
The compiler seems to struggle with the return type of B
's someFunction()
. Based on my understanding of polymorphism, if B
implements A
, any function returning something of type A
should also be able to return something of type
B</code. However, since interfaces cannot be instantiated and are more like agreements between classes, defining <code>A
as abstract makes more sense in terms of expected polymorphic behavior, which indeed works in practice. But for my library's design, it feels appropriate for A
to remain an interface.
The specific compiler error I encounter points to the line declaring B
's someFunction()
:
[ts]
Property 'someFunction' in type 'B' is not assignable to the same property in base type 'A'.
Type '(str: string) => B' is not assignable to type '(str: string) => A'.
Type 'B' is not assignable to type 'A'.
Types of property 'someFunction' are incompatible.
Type '(str: string) => B' is not assignable to type '(str: string) => A'.
(method) Project.B.someFunction(str: string): B
The root of the issue appears to be the constructor declaration within A
. Removing this definition resolves the problem, but I need it to outline what it means fundamentally to belong to type A
.
In light of the expected polymorphic behavior, how can I redefine my interface to achieve this? Would switching to an abstract class be more suitable? How do I enforce this polymorphic behavior effectively?