These three functions seem to do the same thing, but the last one is an error. Why is this the case?
interface StringMap {
[key: string]: string;
}
function a(): StringMap {
return { a: "1" }; // OK
}
function b(): StringMap {
var result: StringMap = { a: "1" };
return result; // OK
}
function c(): StringMap {
var result = { a: "1" };
return result; // Error - result lacks index signature, why?
}
Starting at whatever release comes after TypeScript 1.8 (or right now if you're using the bleeding-edge compiler), you will no longer see this error when the originating expression for a type is an object literal.
See https://github.com/Microsoft/TypeScript/pull/7029
Index signatures and object literals behave specially in TypeScript. From spec section 4.5, Object Literals:
When an object literal is contextually typed by a type that includes a string index signature of type T, the resulting type of the object literal includes a string index signature with the widened form of the best common type of T and the types of the properties declared in the object literal.
What does this all mean?
Contextual typing occurs when the context of an expression gives a hint about what its type might be. For example, in this initialization:
var x: number = y;
The expression y
gets a contextual type of number
because it's initializing a value of that type. In this case, nothing special happens, but in other cases more interesting things will occur.
One of the most useful cases is functions:
// Error: string does not contain a function called 'ToUpper'
var x: (n: string) => void = (s) => console.log(s.ToUpper());
How did the compiler know that s
was a string? If you wrote that function expression by itself, s
would be of type any
and there wouldn't be any error issued. But because the function was contextually typed by the type of x
, the parameter s
acquired the type string
. Very useful!
An index signature specifies the type when an object is indexed by a string or a number. Naturally, these signatures are part of type checking:
var x: { [n: string]: Car; };
var y: { [n: string]: Animal; };
x = y; // Error: Cars are not Animals, this is invalid
The lack of an index signature is also important:
var x: { [n: string]: Car; };
var y: { name: Car; };
x = y; // Error: y doesn't have an index signature that returns a Car
Hopefully it's obvious that the above two snippets ought to cause errors. Which leads us to...
The problem with assuming that objects don't have index signatures is that you then have no way to initialize an object with an index signature:
var c: Car;
// Error, or not?
var x: { [n: string]: Car } = { 'mine': c };
The solution is that when an object literal is contextually typed by a type with an index signature, that index signature is added to the type of the object literal if it matches. For example:
var c: Car;
var a: Animal;
// OK
var x: { [n: string]: Car } = { 'mine': c };
// Not OK: Animal is not Car
var y: { [n: string]: Car } = { 'mine': a };
Let's look at the original functions in the question:
function a(): StringMap {
return { a: "1" }; // OK
}
OK, because expressions in return
statements are contextually typed by the return type of the function. The object literal {a: "1"}
has a string value for its sole property, so the index signature can be successfully applied.
function b(): StringMap {
var result: StringMap = { a: "1" };
return result; // OK
}
OK, because the initializers of explicitly-typed variables are contextually typed by the type of the variable. As before, the index signature is added to the type of the object literal.
function c(): StringMap {
var result = { a: "1" };
return result; // Error - result lacks index signature, why?
}
Not OK, because result
's type does not have an index signature.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With