TypeScript Version: 2.7.0-dev.201xxxxx
Code
This is a (reasonably) minimal example of the code I'm compiling. My actual function is filtering entries before reconstructing the object.
export function foo<V>(object: { [key: string]: V }): { [key: string]: V } {
const entries = Object.entries(object);
const rv = entries.reduce((obj: { [key: string]: V }, [key, value]) => {
obj[key] = value;
return obj;
}, {});
return rv;
}
Note that the above code requires some options to use Object.entries and so you can reproduce the same error with the following code snippet if desired.
const entries: [string, V][] = []; //
const rv = entries.reduce((obj: { [key: string]: V }, [key, value]) => {
obj[key] = value;
return obj;
}, {});
Expected behavior:
In TypeScript 2.4.2 the constant rv is correctly inferred to be of type { [key: string]: V } but it does not seem to be in TypeScript 2.5.3.
Actual behavior:
rv is inferred to be of type {}.
It doesn't seem like the definition of reduce has changed between versions. VSCode is jumping to this signature of reduce.
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
Obviously I can specify the return value of my function foo as I have and everything works, but previously I did not need to as it was correctly inferred.
I think the problem is that {} is used for inference before the callback is. According to its declaration, reduce takes initialValue: U and returns a U, so there is no "need" to look at the callback to complete inference. If you write {} as { [key: string]: V } the example will work without needing a type annotation for obj.
Thanks for the quick reply, that was my best guess too. Specifying the type on {} works. Time for me to write a lint rule to find all the places I haven't done so.
Similar problem with reducing objects to array. Even specifying type on initialValue doesn't communicate to TS that I'm trying to produce an array.
const arrInitialValue: Item[] = [];
const _arroItems = arroResponses.reduce((acc, vResponse) => {
let arrBills: Item[];
let arrItems: Item[];
const vItems: any = vResponse;
if (Array.isArray(vItems)) {
arrItems = Array.isArray(acc) && acc.concat(vItems);
} else {
arrBills = vItems.body;
arrItems = arrBills.map(oRawBill: Item => {
return oRawBill;
});
}
return arrItems;
}, arrInitialValue);
this.arrSomethingElse = _arroItems.map(foo);
The last line throws "[ts] Property 'map' does not exist on type '{}'."
@Vandivier That's not a complete example -- most of the identifiers in that code sample have no definition. When I add declare const arroResponses: string[];, then _arroItems is number[] and the error you mentioned doesn't appear.
@andy-ms I explicitly said my problem was "reducing objects to array"
arroResponses is an array of objects. In fact, it is an array of HTTP responses with different shapes. Clearly string[] is an inappropriate comparison. Why would I call vItems.body on a string?
You can see return arrItems and let arrItems: Item[];. Also, const arrInitialValue: Item[] = [];
Clearly the return type should be Item[]. What other information should I provide? The surrounding method is largely irrelevant.
@Vandivier Thanks, created an issue at #25454.
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
Most helpful comment
I think the problem is that
{}is used for inference before the callback is. According to its declaration,reducetakesinitialValue: Uand returns aU, so there is no "need" to look at the callback to complete inference. If you write{} as { [key: string]: V }the example will work without needing a type annotation forobj.