I've been learning TypeScript and I've encountered something strange. Take a look at this code:
interface Foo {
sayHello(): void;
}
class Bar implements Foo {
sayHello(): void {
console.log("Bar Hello!");
}
sayGoodbye(): void {
console.log("Bar Goodbye!");
}
}
class Baz implements Foo {
sayHello(): void {
console.log("Baz Hello!");
}
}
class TContainer<T extends Foo> {
public value : T;
constructor(value : T) {
this.value = value;
}
}
function run() {
// barContainer.value should only ever be of type 'Bar'
const barContainer : TContainer<Bar> = new TContainer(new Bar());
// Cast barContainer and allow it to hold anything that implements Foo
// (!!!) This should not be allowed
const fooContainer : TContainer<Foo> = barContainer;
// Set fooContainer.value (and thus barContainer.value) to a type without sayGoodbye
fooContainer.value = new Baz();
// Note that just writing barContainer.value = new Baz(); does correctly cause a compile time error.
// Compiler still thinks barContainer.value is of type Bar, so this will crash at runtime
barContainer.value.sayGoodbye();
}
TypeScript seems to allow casting generic types in an unsafe way which causes breakages at runtime. Other language's like C# or Java do not allow generic types to be converted like this, because it's unsafe.
Is there any way to get the TypeScript compiler to report this as an error? If not, why not and how can I make types like TContainer
safe?