Reputation: 2329
Let's say we have this example:
class Base<T extends Base<T>> {}
class ClassA extends Base<ClassA> {}
class ClassB extends Base<ClassB> {}
type Condition = ClassA extends ClassB ? true : false;
The base class has one generic argument which basically says, anything derivind from it should template it with it's own type.
Then we have 2 classes that derive from said base.
Lastly I've created a conditional type that checks whether the derived classes extend each other.
To my surprise typescript is telling me that they do, but I think that this condition should be false. ClassA
isn't extending ClassB
and vice versa. Only ClassA extends Base<ClassA>
should return true.
Is this an issue in typescript's conditional types or am I missing something? I came across this issue while building more complex conditional type, that as well was returning unexpected results.
Edit: The generic arguments aren't needed as well. Even this example returns wrong result:
class Base {}
class ClassA extends Base {}
class ClassB extends Base {}
type Condition = ClassA extends ClassB ? true : false;
Upvotes: 2
Views: 6847
Reputation: 249466
Typescript usses structural typing to best emulate the way Javascript duck typing works. This has the advantage of allowing us to model a lot of Javascript scenarios in a staticly type way.
The problem is that you must never forget that when typescript checks any type compaitbility (even in conditional types) it is not checking for nominal inheritance like C# or Java would, it is checking for structural subtyping.
In your example, since all classes are empty, they are structurally equivalent to {}
, which means that yes, ClassA
does extends ClassB
and ClassB
extends ClassA
since they are all structually the same type.
Add any member to the classes and this goes away, private members ensure maximum incopatibility because another class (or interface) can't just declare redeclare them to mimic subtyping.
class Base {}
class ClassA extends Base { private a: number}
class ClassB extends Base { private b: number}
type Condition = ClassA extends ClassB ? true : false; // false now
Upvotes: 10