I've been writing TypeScript for a while and am confused about what an index signature means.
For example, this code is legal:
function fn(obj: { [x: string]: number }) {
let n: number = obj.something;
}
But this code, which does basically the same thing, isn't:
function fn(obj: { [x: string]: number }) {
let p: { something: number } = obj;
}
Is this a bug? What's the intended meaning of this?
You are right to be confused. Index signatures mean a few things, and they mean slightly different things depending where and how you ask.
First, index signatures imply that all declared properties in the type must have a compatible type.
This is a definitional aspect of index signatures -- that they describe an object with different property keys, but a consistent type across all those keys. This rule prevents an incorrect type from being observed when a property is accessed through an indirection:
Second, index signatures allow writing to any index with a compatible type.
This is a key use case for index signatures: You have some set of keys and you want to store a value associated with the key.
Third, index signatures imply the existence of any property you specifically ask for:
Because
x["p"]
andx.p
have identical behavior in JavaScript, TypeScript treats them equivalently:This is consistent with how TypeScript views arrays, which is that array access is assumed to be in-bounds. It's also ergonomic for index signatures because, very commonly, you have a known set of keys available and don't need to do any additional checking:
However, index signatures don't imply that any arbitrary, unspecific property will be present when it comes to relating a type with an index signature to a type with declared properties:
This sort of assignment is very unlikely to be correct in practice!
This is a distinguishing feature between
{ [k: string]: any }
andany
itself - you can read and write properties of any kind on an object with an index signature, but it can't be used in place of any type whatsoever likeany
can.Each of these behaviors is individually very justifiable, but taken as a whole, some inconsistencies are observable.
For example, these two functions are identical in terms of their runtime behavior, but TypeScript only considers one of them to have been called incorrectly:
Overall, when you write an index signature on a type, you're saying: