Skip to content

Commit 04abc00

Browse files
committed
support type inference of object non-literal keys
1 parent b5bab16 commit 04abc00

6 files changed

+433
-0
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24434,6 +24434,11 @@ namespace ts {
2443424434
return propertyType;
2443524435
}
2443624436
}
24437+
else if (element.name?.kind === SyntaxKind.ComputedPropertyName) {
24438+
const indexType = getTypeOfExpression(element.name.expression);
24439+
return getIndexedAccessTypeOrUndefined(type, indexType);
24440+
}
24441+
2443724442
return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) ||
2443824443
getIndexTypeOfContextualType(type, IndexKind.String);
2443924444
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
tests/cases/compiler/contextualTypeInObjectProperty.ts(19,17): error TS7006: Parameter 'keyC' implicitly has an 'any' type.
2+
tests/cases/compiler/contextualTypeInObjectProperty.ts(24,3): error TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
3+
tests/cases/compiler/contextualTypeInObjectProperty.ts(24,11): error TS7006: Parameter 'keyC' implicitly has an 'any' type.
4+
5+
6+
==== tests/cases/compiler/contextualTypeInObjectProperty.ts (3 errors) ====
7+
type Shape = {
8+
"a"?: (a: "a") => "a";
9+
"b"?: (b: "b") => "b";
10+
"c"?: (c: "c") => "c";
11+
};
12+
13+
const getC = () => "c" as const;
14+
15+
export const obj: Shape = {
16+
["a"]: keyA => keyA,
17+
["b" as "b"]: keyB => keyB,
18+
[getC()]: keyC => keyC,
19+
};
20+
21+
22+
const getUnion = () => "b" as "b" | "c";
23+
24+
export const unionType: Shape = {
25+
[getUnion()]: keyC => keyC,
26+
~~~~
27+
!!! error TS7006: Parameter 'keyC' implicitly has an 'any' type.
28+
};
29+
30+
31+
export const func: Shape = {
32+
[getC]: keyC => keyC,
33+
~~~~~~
34+
!!! error TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
35+
~~~~
36+
!!! error TS7006: Parameter 'keyC' implicitly has an 'any' type.
37+
};
38+
39+
const generic: {
40+
c: <T>(arg: T) => T;
41+
} = {
42+
[getC()]: keyC => keyC,
43+
};
44+
45+
const thisType = {
46+
[getC()]: function() {
47+
this.c();
48+
}
49+
};
50+
51+
52+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
53+
f({ data: 0 }, {
54+
[(() => 'data' as const)()](value, key) {
55+
56+
},
57+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [contextualTypeInObjectProperty.ts]
2+
type Shape = {
3+
"a"?: (a: "a") => "a";
4+
"b"?: (b: "b") => "b";
5+
"c"?: (c: "c") => "c";
6+
};
7+
8+
const getC = () => "c" as const;
9+
10+
export const obj: Shape = {
11+
["a"]: keyA => keyA,
12+
["b" as "b"]: keyB => keyB,
13+
[getC()]: keyC => keyC,
14+
};
15+
16+
17+
const getUnion = () => "b" as "b" | "c";
18+
19+
export const unionType: Shape = {
20+
[getUnion()]: keyC => keyC,
21+
};
22+
23+
24+
export const func: Shape = {
25+
[getC]: keyC => keyC,
26+
};
27+
28+
const generic: {
29+
c: <T>(arg: T) => T;
30+
} = {
31+
[getC()]: keyC => keyC,
32+
};
33+
34+
const thisType = {
35+
[getC()]: function() {
36+
this.c();
37+
}
38+
};
39+
40+
41+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
42+
f({ data: 0 }, {
43+
[(() => 'data' as const)()](value, key) {
44+
45+
},
46+
});
47+
48+
//// [contextualTypeInObjectProperty.js]
49+
"use strict";
50+
var _a, _b, _c, _d, _e, _f;
51+
exports.__esModule = true;
52+
exports.func = exports.unionType = exports.obj = void 0;
53+
var getC = function () { return "c"; };
54+
exports.obj = (_a = {},
55+
_a["a"] = function (keyA) { return keyA; },
56+
_a["b"] = function (keyB) { return keyB; },
57+
_a[getC()] = function (keyC) { return keyC; },
58+
_a);
59+
var getUnion = function () { return "b"; };
60+
exports.unionType = (_b = {},
61+
_b[getUnion()] = function (keyC) { return keyC; },
62+
_b);
63+
exports.func = (_c = {},
64+
_c[getC] = function (keyC) { return keyC; },
65+
_c);
66+
var generic = (_d = {},
67+
_d[getC()] = function (keyC) { return keyC; },
68+
_d);
69+
var thisType = (_e = {},
70+
_e[getC()] = function () {
71+
this.c();
72+
},
73+
_e);
74+
f({ data: 0 }, (_f = {},
75+
_f[(function () { return 'data'; })()] = function (value, key) {
76+
},
77+
_f));
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
=== tests/cases/compiler/contextualTypeInObjectProperty.ts ===
2+
type Shape = {
3+
>Shape : Symbol(Shape, Decl(contextualTypeInObjectProperty.ts, 0, 0))
4+
5+
"a"?: (a: "a") => "a";
6+
>"a" : Symbol("a", Decl(contextualTypeInObjectProperty.ts, 0, 14))
7+
>a : Symbol(a, Decl(contextualTypeInObjectProperty.ts, 1, 11))
8+
9+
"b"?: (b: "b") => "b";
10+
>"b" : Symbol("b", Decl(contextualTypeInObjectProperty.ts, 1, 26))
11+
>b : Symbol(b, Decl(contextualTypeInObjectProperty.ts, 2, 11))
12+
13+
"c"?: (c: "c") => "c";
14+
>"c" : Symbol("c", Decl(contextualTypeInObjectProperty.ts, 2, 26))
15+
>c : Symbol(c, Decl(contextualTypeInObjectProperty.ts, 3, 11))
16+
17+
};
18+
19+
const getC = () => "c" as const;
20+
>getC : Symbol(getC, Decl(contextualTypeInObjectProperty.ts, 6, 5))
21+
22+
export const obj: Shape = {
23+
>obj : Symbol(obj, Decl(contextualTypeInObjectProperty.ts, 8, 12))
24+
>Shape : Symbol(Shape, Decl(contextualTypeInObjectProperty.ts, 0, 0))
25+
26+
["a"]: keyA => keyA,
27+
>["a"] : Symbol(["a"], Decl(contextualTypeInObjectProperty.ts, 8, 27))
28+
>"a" : Symbol(["a"], Decl(contextualTypeInObjectProperty.ts, 8, 27))
29+
>keyA : Symbol(keyA, Decl(contextualTypeInObjectProperty.ts, 9, 8))
30+
>keyA : Symbol(keyA, Decl(contextualTypeInObjectProperty.ts, 9, 8))
31+
32+
["b" as "b"]: keyB => keyB,
33+
>["b" as "b"] : Symbol(["b" as "b"], Decl(contextualTypeInObjectProperty.ts, 9, 22))
34+
>keyB : Symbol(keyB, Decl(contextualTypeInObjectProperty.ts, 10, 15))
35+
>keyB : Symbol(keyB, Decl(contextualTypeInObjectProperty.ts, 10, 15))
36+
37+
[getC()]: keyC => keyC,
38+
>[getC()] : Symbol([getC()], Decl(contextualTypeInObjectProperty.ts, 10, 29))
39+
>getC : Symbol(getC, Decl(contextualTypeInObjectProperty.ts, 6, 5))
40+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 11, 11))
41+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 11, 11))
42+
43+
};
44+
45+
46+
const getUnion = () => "b" as "b" | "c";
47+
>getUnion : Symbol(getUnion, Decl(contextualTypeInObjectProperty.ts, 15, 5))
48+
49+
export const unionType: Shape = {
50+
>unionType : Symbol(unionType, Decl(contextualTypeInObjectProperty.ts, 17, 12))
51+
>Shape : Symbol(Shape, Decl(contextualTypeInObjectProperty.ts, 0, 0))
52+
53+
[getUnion()]: keyC => keyC,
54+
>[getUnion()] : Symbol([getUnion()], Decl(contextualTypeInObjectProperty.ts, 17, 33))
55+
>getUnion : Symbol(getUnion, Decl(contextualTypeInObjectProperty.ts, 15, 5))
56+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 18, 15))
57+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 18, 15))
58+
59+
};
60+
61+
62+
export const func: Shape = {
63+
>func : Symbol(func, Decl(contextualTypeInObjectProperty.ts, 22, 12))
64+
>Shape : Symbol(Shape, Decl(contextualTypeInObjectProperty.ts, 0, 0))
65+
66+
[getC]: keyC => keyC,
67+
>[getC] : Symbol([getC], Decl(contextualTypeInObjectProperty.ts, 22, 28))
68+
>getC : Symbol(getC, Decl(contextualTypeInObjectProperty.ts, 6, 5))
69+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 23, 9))
70+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 23, 9))
71+
72+
};
73+
74+
const generic: {
75+
>generic : Symbol(generic, Decl(contextualTypeInObjectProperty.ts, 26, 5))
76+
77+
c: <T>(arg: T) => T;
78+
>c : Symbol(c, Decl(contextualTypeInObjectProperty.ts, 26, 16))
79+
>T : Symbol(T, Decl(contextualTypeInObjectProperty.ts, 27, 6))
80+
>arg : Symbol(arg, Decl(contextualTypeInObjectProperty.ts, 27, 9))
81+
>T : Symbol(T, Decl(contextualTypeInObjectProperty.ts, 27, 6))
82+
>T : Symbol(T, Decl(contextualTypeInObjectProperty.ts, 27, 6))
83+
84+
} = {
85+
[getC()]: keyC => keyC,
86+
>[getC()] : Symbol([getC()], Decl(contextualTypeInObjectProperty.ts, 28, 5))
87+
>getC : Symbol(getC, Decl(contextualTypeInObjectProperty.ts, 6, 5))
88+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 29, 11))
89+
>keyC : Symbol(keyC, Decl(contextualTypeInObjectProperty.ts, 29, 11))
90+
91+
};
92+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
=== tests/cases/compiler/contextualTypeInObjectProperty.ts ===
2+
type Shape = {
3+
>Shape : Shape
4+
5+
"a"?: (a: "a") => "a";
6+
>"a" : (a: "a") => "a"
7+
>a : "a"
8+
9+
"b"?: (b: "b") => "b";
10+
>"b" : (b: "b") => "b"
11+
>b : "b"
12+
13+
"c"?: (c: "c") => "c";
14+
>"c" : (c: "c") => "c"
15+
>c : "c"
16+
17+
};
18+
19+
const getC = () => "c" as const;
20+
>getC : () => "c"
21+
>() => "c" as const : () => "c"
22+
>"c" as const : "c"
23+
>"c" : "c"
24+
25+
export const obj: Shape = {
26+
>obj : Shape
27+
>{ ["a"]: keyA => keyA, ["b" as "b"]: keyB => keyB, [getC()]: keyC => keyC,} : { a: (keyA: "a") => "a"; b: (keyB: "b") => "b"; c: (keyC: "c") => "c"; }
28+
29+
["a"]: keyA => keyA,
30+
>["a"] : (keyA: "a") => "a"
31+
>"a" : "a"
32+
>keyA => keyA : (keyA: "a") => "a"
33+
>keyA : "a"
34+
>keyA : "a"
35+
36+
["b" as "b"]: keyB => keyB,
37+
>["b" as "b"] : (keyB: "b") => "b"
38+
>"b" as "b" : "b"
39+
>"b" : "b"
40+
>keyB => keyB : (keyB: "b") => "b"
41+
>keyB : "b"
42+
>keyB : "b"
43+
44+
[getC()]: keyC => keyC,
45+
>[getC()] : (keyC: "c") => "c"
46+
>getC() : "c"
47+
>getC : () => "c"
48+
>keyC => keyC : (keyC: "c") => "c"
49+
>keyC : "c"
50+
>keyC : "c"
51+
52+
};
53+
54+
55+
const getUnion = () => "b" as "b" | "c";
56+
>getUnion : () => "b" | "c"
57+
>() => "b" as "b" | "c" : () => "b" | "c"
58+
>"b" as "b" | "c" : "b" | "c"
59+
>"b" : "b"
60+
61+
export const unionType: Shape = {
62+
>unionType : Shape
63+
>{ [getUnion()]: keyC => keyC,} : { [x: string]: (keyC: any) => any; }
64+
65+
[getUnion()]: keyC => keyC,
66+
>[getUnion()] : (keyC: any) => any
67+
>getUnion() : "b" | "c"
68+
>getUnion : () => "b" | "c"
69+
>keyC => keyC : (keyC: any) => any
70+
>keyC : any
71+
>keyC : any
72+
73+
};
74+
75+
76+
export const func: Shape = {
77+
>func : Shape
78+
>{ [getC]: keyC => keyC,} : {}
79+
80+
[getC]: keyC => keyC,
81+
>[getC] : (keyC: any) => any
82+
>getC : () => "c"
83+
>keyC => keyC : (keyC: any) => any
84+
>keyC : any
85+
>keyC : any
86+
87+
};
88+
89+
const generic: {
90+
>generic : { c: <T>(arg: T) => T; }
91+
92+
c: <T>(arg: T) => T;
93+
>c : <T>(arg: T) => T
94+
>arg : T
95+
96+
} = {
97+
>{ [getC()]: keyC => keyC,} : { c: <T>(keyC: T) => T; }
98+
99+
[getC()]: keyC => keyC,
100+
>[getC()] : <T>(keyC: T) => T
101+
>getC() : "c"
102+
>getC : () => "c"
103+
>keyC => keyC : <T>(keyC: T) => T
104+
>keyC : T
105+
>keyC : T
106+
107+
};
108+
109+
const thisType = {
110+
>thisType : { c: () => void; }
111+
>{ [getC()]: function() { this.c(); }} : { c: () => void; }
112+
113+
[getC()]: function() {
114+
>[getC()] : () => void
115+
>getC() : "c"
116+
>getC : () => "c"
117+
>function() { this.c(); } : () => void
118+
119+
this.c();
120+
>this.c() : void
121+
>this.c : () => void
122+
>this : { c: () => void; }
123+
>c : () => void
124+
}
125+
};
126+
127+
128+
declare function f<T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }): void;
129+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
130+
>data : T
131+
>handlers : { [P in keyof T]: (value: T[P], prop: P) => void; }
132+
>value : T[P]
133+
>prop : P
134+
135+
f({ data: 0 }, {
136+
>f({ data: 0 }, { [(() => 'data' as const)()](value, key) { },}) : void
137+
>f : <T extends object>(data: T, handlers: { [P in keyof T]: (value: T[P], prop: P) => void; }) => void
138+
>{ data: 0 } : { data: number; }
139+
>data : number
140+
>0 : 0
141+
>{ [(() => 'data' as const)()](value, key) { },} : { data(value: number, key: "data"): void; }
142+
143+
[(() => 'data' as const)()](value, key) {
144+
>[(() => 'data' as const)()] : (value: number, key: "data") => void
145+
>(() => 'data' as const)() : "data"
146+
>(() => 'data' as const) : () => "data"
147+
>() => 'data' as const : () => "data"
148+
>'data' as const : "data"
149+
>'data' : "data"
150+
>value : number
151+
>key : "data"
152+
153+
},
154+
});

0 commit comments

Comments
 (0)