Typescript should be able to derive the type of the array created by `Object.keys` from the key signature of the object
quinn opened this issue · 7 comments
TypeScript Version: 3.2.0-dev.201xxxxx
Search Terms:
object.keys
Code
type Keys = 'one' | 'two'
const obj:{[key in Keys]: string} = {
one: 'value for one',
two: 'value for two',
}
Object.keys(obj).forEach(key =>
console.log(obj[key])
)Expected behavior:
No error.
Actual behavior:
$ tsc --noImplicitAny test.ts
test.ts:9:15 - error TS7017: Element implicitly has an 'any' type because type '{ one: string; two: string; }' has no index signature.
9 console.log(obj[key])
~~~~~~~~
Playground Link:
Related Issues:
Duplicate of #26901 (any many others).
@ahejlsberg thanks for the additional context. If this isn't possible, could someone at least explain or link to what is the most idiomatic typescript in these situations?
@quinn If you are certain obj has no other enumerable properties you can assert that using a type assertion. Either of the following:
type Keys = 'one' | 'two';
const obj:{[key in Keys]: string} = {
one: 'value for one',
two: 'value for two',
};
Object.keys(obj).forEach(key => console.log(obj[key as Keys]));
(Object.keys(obj) as Keys[]).forEach(key => console.log(obj[key]));@ahejlsberg using as fixed it, thank you! My assumption would be that if I somehow mutated the object to have a key other than 'one' or 'two' the fact that i have a defined type for the key [key in Keys] typescript would catch that.
and I definitely feel like i'm not doing something right if i ever need to use as. I really hate to rely on being certain myself rather than letting typescript to its thing.
Emmm, same problem with for-in:
let a: ClassA;
for (let k in a) {
console.log(a[k]); // [ts] Element implicitly has an 'any' type because type 'ClassA' has no index signature. [7017]
}And must be written in this style:
let a: ClassA;
let k: keyof ClassA;
for (k in a) {
console.log(a[k]); // That's okay.
}So, why doesn't TS give k the type keyof ClassA automatically?
So let's say you write
const obj = { x: 1, y: 2 };keyof typeof obj has type "x" | "y"
It would be bad if TypeScript allowed a value "z" to appear in an expression typed as "x" | "y". But that's exactly what's at stake here:
type Point = { x: number, y: number };
function fn(x: Point) {
// Proposal: make this *not* an error
for (let k in x) printXorY(k);
}
function printXorY(key: keyof Point) {
switch(key) {
case "x":
console.log("is x");
break;
case "y":
console.log("is y");
break;
default:
throw new Error("HOW IS THIS HAPPENING");
}
}
const p3 = { x: 1, y: 2, z: 3 };
fn(p3); // oops