TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Assigning a value with the wrong type through dynamic attribute name (bracket notation) not reported in some cases

Open fjolliton opened this issue 3 years ago • 3 comments

Bug Report

🔎 Search Terms

Not sure which keywords apply to this.

🕗 Version & Regression Information

This behavior occurs with 4.7, 4.8 and 4.9.0-dev.20220920.

⏯ Playground Link

See here

💻 Code

type Key = 'a' | 'b';
type Values = Record<Key, { value: number }>;
const x: Values = { a: { value: 1 }, b: { value: 2 } };
const key: Key = 'a' as Key; // <= HERE: Without "as Key" it detects the type error
const y: Values = { ...x, [key]: { value: 'oops' } }; // <= OOPS: Wrong type for `value`!

A variation:

type Key = 'a' | 'b';
type Values = Record<Key, { value: number }>;
const data: Values = { a: { value: 1 }, b: { value: 2 } };
// No error reported, while it returns the wrong type.
const example = (key: Key): Values => ({ ...data, [key]: { value: 'oops' } });
example('a'); // => {"a": {value: "oops"}, "b": {value: 2}}

🙁 Actual behavior

While key in the code example is typed as "a" | "b", TypeScript doesn't detect that we are attempting to use an incorrect type for the value attribute.

🙂 Expected behavior

The type should probably be deduced as {a: {value: string}} | {b: {value: string}} leading to an error, but actually any values get accepted silently which breaks the typing contract.

fjolliton avatar Sep 20 '22 14:09 fjolliton

Strongly related to #13948

jcalz avatar Sep 20 '22 15:09 jcalz

To make it clear: the problem is that the wrong type is silently accepted, not that correct code is being rejected.

fjolliton avatar Sep 20 '22 16:09 fjolliton

The problem here is that we don't have a type to give the object literal (or rather, no means of generating the type that isn't combinatorially explosive).

If you wrote something like

type AlphabetChar = "a" | "b" | "c" /*... the rest ... */| "z";

declare function getRandomChar(): AlphabetChar;
const k1 = getRandomChar(), k2 = getRandomChar(), k3 = getRandomChar(), k4 = getRandomChar();
const obj = { [k1]: 0, [k2]: 0, [k3]: 0, [k4]: 0 };

then the only precise type for obj is the one with 26 ** 4 = 456976 constituents.

Something we might give a try for is computing a pessimistic type with optional members -- { a?: number, b?: number, c?: number ... }. This will be a little silly when there are only two possible key values, but it's at least correct.

RyanCavanaugh avatar Sep 21 '22 23:09 RyanCavanaugh

At the very least, there should be an option that reject such codes that knowingly could break the typing contract and require some sort of annotations from the developer to let it pass.

fjolliton avatar Sep 24 '22 09:09 fjolliton