Sneaking a string past the typechecker
Published onBelow is a bit of Typescript code that adds a string to an array of numbers, and it does this without resulting in a type checking error! The fact that this was possible surprised me and was made possible by ✨type variance✨.
function nothing_wrong_here(x: (number | string)[]) {
x.push("peanut butter");
}
const xs: number[] = [];
nothing_wrong_here(xs);
console.log(`My favorite number is ${xs[0]}`);In Typescript, a number is a subtype of (number|string) and number[] is a subtype of (number|string)[], because arrays are covariant. If A is a subtype of B and a type T<A>is a subtype of T<B> then T is covariant. Basically, the subtyping relation carries over to the wrapping type.
I don't think I have ever been bitten by this curiosity so this is probably not a big problem in practice, but this is definitely a footgun in my book.
Apparently arrays are also covariant in Java and C#, but to make sure no values of the wrong type are stored in the array each value is checked at runtime to be the correct type.