type Example = {
a: string,
b: number
}
let value: Example = {
a: 'hello',
b: 10,
}
// This IIFE is because typescript will narrow the type if assigned directly like
// const k: keyof Example = 'a'
const k = ((): keyof Example => 'a')()
value = {
...value,
// Obviously this is unsound
[k]: 5,
}
This passes even in strict mode. Playground link.
Why is typescript unsound here? I want to understand the overarching unsoundness so I can avoid using patterns like this that will not be typesafe. I would also appreciate any relevant documentation or GitHub issues.
This is a known issue with TypeScript, documented at microsoft/TypeScript#38663. If you have a computed property name whose type is a union, then TypeScript will widen that type all the way to string
(see microsoft/TypeScript#13948), and then more or less just ignores the property entirely. That is, it's even worse than possibly assigning a number
to a
, it could assign anything to a
, including false
(see this playground link).
It's not currently classified as a bug, probably because such unsoundnesses with property writes permeate the language:
// widen the type of value, this is considered "safe"
const myVal: { [k: string]: string | number } = value;
// but oops:
myVal.b = "oopsieDoodle";
Fixing such unsoundness would make things safer, but arguably a lot more annoying to use, as evidenced by the many complaints about microsoft/TypeScript#30769. TypeScript is unsound, largely in places where the TS team thinks the cure is worse than the disease. See TypeScript Language Design Non-Goal #3 and TS team comments in microsoft/TypeScript#9825.