You've got extends backwards, unfortunately. Roughly speaking, when B extends A it means that B is a subtype of A. That means you should be able to assign a value of type B to a variable of type A. You can also think of this in terms of substitutability: if B extends A it means that you can substitute a value of type B for a value of type A without anyone noticing. If you ask for a A and I give you an B and something goes wrong, then B extends A is not true.
Given your definition of A, the following function should be fine... and the compiler produces no warning:
function foo(a: A) {
const unicodeShapes = {
triangle: "△",
rectangle: "▭"
}
return (unicodeShapes[a.polygon].repeat(10));
}
const a = new A();
a.polygon = "triangle";
console.log(foo(a)); // "△△△△△△△△△△"
But now think about what would happen if your B definition were accepted as valid:
const b = new B();
b.polygon = "circle";
console.log(foo(b)); // okay for the compiler, but
// RUNTIME ERROR! unicodeShapes[a.polygon] is undefined
If B extends A, then foo(b) should be acceptable... foo wants an A, and B extends A implies that b, a value of type B, is substitutable for a value of type A. But of course there's a big problem doing this at runtime.
So you cannot "extend" classes this way without leading to type safety issues.
What can you do instead? Given the simple example code, you could just flip which class is the parent and which is the child:
class B {
polygon: "triangle" | "rectangle" | "circle" = "triangle"
name: string = ""
doSomething: () => void = () => { }
}
class A extends B {
polygon: "triangle" | "rectangle" = "triangle"
}
But if you really want to use something like class B extends A, so that b instanceof A should be true, without requiring that B be a true subtype of A, you can do something like this with the Omit utility type:
const _A: new () => Omit<A, "polygon"> = A;
class B extends _A {
polygon: "triangle" | "rectangle" | "circle" = "triangle"
}
Here we have created _A and told the compiler that it constructs instances of A without a known polygon property, and assigned the A constructor to it. We have widened the type of A, which is a type-safe operation (at least when you read properties only).
Then we make B extend _A instead of A. This is more or less the same as your code at runtime, but the compiler is now happy with it because it does not see B as trying to be a subtype of A. You cannot use a B wherever you'd use an A:
console.log(foo(b)); // compiler error!
// -----------> ~
// Argument of type 'B' is not assignable to parameter of type 'A'.
Yay! This might have some observable side effects in TypeScript, because the compiler expects subclasses to form subtypes, so be careful with this sort of thing.
Playground link to code