Type inference for object with all attributes of the same type.

Topics: General, Language Specification
Aug 28, 2013 at 10:37 AM
I'm trying to define a function that operates on an object where all the attributes are of the same type:
interface O<T> {
    [index: string]: T;
}

declare function f1<T>(o: O<T>): O<T>;
declare function f2<T>(o: O<T>): T;

var o = {a: 'x', b: 'y'};
var o1 = f1(o);
var o2 = f2(o);
The problem is that the compiler detects the type of o1 as O<any> and o2 as any while I expected it to detect it as O<string> and string respectively. The generated type file:
interface O<T> {
    [index: string]: T;
}
declare var o: {
    a: string;
    b: string;
};
declare var o1: O<any>;
declare var o2;
Also if I change o to {a: 'x', b: 10} the compiler doesn't give any errors.

How can I get the compiler to correctly detect the correct return type?
Aug 28, 2013 at 10:49 AM
Some more information. It looks like it works better for arrays. For:
interface A<T> {
    [index: number] : T;
    length: number;
}

declare function f1<T>(a: A<T>): A<T>;
declare function f2<T>(a: A<T>): T;

var a = ['x', 'y'];
var a1 = f1(a);
var a2 = f2(a);
The generated type file:
interface A<T> {
    [index: number]: T;
    length: number;
}
declare var a: string[];
declare var a1: A<string>;
declare var a2: string;
However if I change the a to ['x', 10] there is no compiler error.
Developer
Aug 28, 2013 at 9:37 PM
This particular area isn't fully implemented in the compiler yet, but eventually the following will work:
interface O<T> {
    [index: string]: T;
}

declare function f1<T>(o: O<T>): O<T>;
declare function f2<T>(o: O<T>): T;

var o1 = f1({a: 'x', b: 'y'});  // O<string>
var o2 = f2({a: 'x', b: 'y'});  // string
However, your original example will continue to infer O<any> and any. The reason is that we only know about the statically declared properties of 'o', but not other properties that might be present in the object. Consider this:
var o = {a: 'x', b: 'y'};
o = {a: 'a', b: 'b', c: 123};
var o1 = f1(o);
var o2 = f2(o);
The only thing we can safely infer here is O<any> and any because we don't know what other properties 'o' might have. However, if you restrict 'o' to a type that contains a string index signature then we can know for sure:
var o: O<string> = {a: 'x', b: 'y'};
o = {a: 'a', b: 'b', c: 123};  // Error
var o1 = f1(o);  // O<string>
var o2 = f2(o);  // string
Hope this helps.
Aug 29, 2013 at 9:35 AM
Edited Aug 29, 2013 at 9:50 AM
Thanks for the detailed response. I also understand why it works for the array case. When I defined all properties of o as string I could still add other properties the are not of type string but when I defined all items of a as string it inferred the type as string[] and it considers it an error to add items of another type:
var o = {a: 'x', b: 'y'}; // type is {a: string, b: string}
o['c'] = 10;  // ok to add property with type number

var a = ['x', 'y']; // type is string[]
a[2] = 10; // type error can't convert number to string