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
x
is compatible withy
ify
has 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