Imagine you have the following code:
class Superclass<T>{
combine(object: Superclass<T>) {
console.log("combined!");
return object;
}
}
class Generic1 {
x = 12;
// some class...
}
class Generic2 {
y = "Hello!";
// some class...
}
new Superclass<Generic1>().combine(new Superclass<Generic2>());
Playground link
As you will surely notice, there should be an error here. Superclass<Generic1> can only be combined with Superclass<Generic1>, but in no case with Superclass<Generic2>. Visual studio code also displays this:

But there is no error after compiling! Why? Am I misunderstanding generics?
TypeScript lets it go through because the T in Superclass<T> does not affect its shape, therefore Superclass<A> and Superclass<B> have the same shape, and are therefore considered "compatible" by TypeScript.
Remember that TypeScript has a structural type system where types are considered equivalent by their members, even if their actual and original definitions are completely separate (think of convergent evolution). As for why it's surprising: Remember that TypeScript does not have a nominative type-system, where types with identical members, structure and layout, right down to cross-platform binary representations, are considered strictly different, as long as they have different type-names (this is how Java, C#, and many other languages' type systems work).
So while Superclass<T> is not using T and it doesn't affect its shape, so tsc considers T superfluous and ignores it:
So this...
class Superclass<T> {
combine(object: Superclass<T>): Superclass<T> { ... }
}
...is provably equivalent to:
class Superclass {
combine(object: Superclass): Superclass { ... }
}
However, as soon as Superclass<T> becomes dependent upon T, such as by adding a field typed as T, then it throws an error:
class Superclass<T>{
public value: T;
constructor(value: T) {
this.value = value;
}
combine(object: Superclass<T>) {
console.log("combined: " + object.value.toString());
return object;
}
}
class Generic1 {
x = Math.random();
}
class Generic2 {
y = Math.random();
}
var x = new Superclass<Generic1>( new Generic1() ).combine(new Superclass<Generic2>( new Generic2() ) );
Error:
Argument of type 'Superclass<Generic2>' is not assignable to parameter of type 'Superclass<Generic1>'.
Property 'x' is missing in type 'Generic2' but required in type 'Generic1'.
As for why this is the case - this is (I think) explained in TypeScript's documentation in the section about Type Compatibility:
The basic rule for TypeScript’s structural type system is that
xis compatible withyifyhas at least the same members asx
In this case, when Superclass<T> is closed as either Superclass<Generic1> or as Superclass<Generic2> both types have exactly the same members, and TypeScript considers them compatible wither each other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With