Skip to content

Commit 7016d45

Browse files
collin5RyanCavanaugh
authored andcommitted
Better errors for indexing gettable/settable values (#26446)
* give suggestions when index signature given * add tests for noImplicitAny indexing on Object * remove comments regarding error messages * recommend set if el is on RHS of assignment else get * add new baseline tests
1 parent 3ce3cde commit 7016d45

8 files changed

+289
-19
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10117,7 +10117,13 @@ namespace ts {
1011710117
}
1011810118
}
1011910119
else {
10120-
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType));
10120+
const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression);
10121+
if (suggestion !== undefined) {
10122+
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion);
10123+
}
10124+
else {
10125+
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType));
10126+
}
1012110127
}
1012210128
}
1012310129
}
@@ -20186,6 +20192,35 @@ namespace ts {
2018620192
return suggestion && symbolName(suggestion);
2018720193
}
2018820194

20195+
function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression): string | undefined {
20196+
// check if object type has setter or getter
20197+
const hasProp = (name: "set" | "get", argCount = 1) => {
20198+
const prop = getPropertyOfObjectType(objectType, <__String>name);
20199+
if (prop) {
20200+
const s = getSingleCallSignature(getTypeOfSymbol(prop));
20201+
if (s && getMinArgumentCount(s) === argCount && typeToString(getTypeAtPosition(s, 0)) === "string") {
20202+
return true;
20203+
}
20204+
}
20205+
return false;
20206+
};
20207+
20208+
const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get";
20209+
if (!hasProp(suggestedMethod)) {
20210+
return undefined;
20211+
}
20212+
20213+
let suggestion = tryGetPropertyAccessOrIdentifierToString(expr);
20214+
if (suggestion === undefined) {
20215+
suggestion = suggestedMethod;
20216+
}
20217+
else {
20218+
suggestion += "." + suggestedMethod;
20219+
}
20220+
20221+
return suggestion;
20222+
}
20223+
2018920224
/**
2019020225
* Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
2019120226
* Names less than length 3 only check for case-insensitive equality, not levenshtein distance.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4272,7 +4272,10 @@
42724272
"category": "Error",
42734273
"code": 7051
42744274
},
4275-
4275+
"Element implicitly has an 'any' type because type '{0}' has no index signature. Did you mean to call '{1}' ?": {
4276+
"category": "Error",
4277+
"code": 7052
4278+
},
42764279
"You cannot rename this element.": {
42774280
"category": "Error",
42784281
"code": 8000

src/compiler/utilities.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3960,6 +3960,16 @@ namespace ts {
39603960
return isPropertyAccessExpression(node) && isEntityNameExpression(node.expression);
39613961
}
39623962

3963+
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined {
3964+
if (isPropertyAccessExpression(expr)) {
3965+
return tryGetPropertyAccessOrIdentifierToString(expr.expression) + "." + expr.name;
3966+
}
3967+
if (isIdentifier(expr)) {
3968+
return unescapeLeadingUnderscores(expr.escapedText);
3969+
}
3970+
return undefined;
3971+
}
3972+
39633973
export function isPrototypeAccess(node: Node): node is PropertyAccessExpression {
39643974
return isPropertyAccessExpression(node) && node.name.escapedText === "prototype";
39653975
}
Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(1,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature.
2+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(7,1): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
3+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(8,13): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
4+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(13,13): error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature.
5+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(19,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
6+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(20,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
7+
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(21,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
28

39

4-
==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (1 errors) ====
5-
var x = {}["hello"];
10+
==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (7 errors) ====
11+
var a = {}["hello"];
612
~~~~~~~~~~~
713
!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature.
8-
var y: string = { '': 'foo' }[''];
14+
var b: string = { '': 'foo' }[''];
15+
16+
var c = {
17+
get: (key: string) => 'foobar'
18+
};
19+
c['hello'];
20+
~~~~~~~~~~
21+
!!! error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
22+
const foo = c['hello'];
23+
~~~~~~~~~~
24+
!!! error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
25+
26+
var d = {
27+
set: (key: string) => 'foobar'
28+
};
29+
const bar = d['hello'];
30+
~~~~~~~~~~
31+
!!! error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature.
32+
33+
var e = {
34+
set: (key: string) => 'foobar',
35+
get: (key: string) => 'foobar'
36+
};
37+
e['hello'] = 'modified';
38+
~~~~~~~~~~
39+
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
40+
e['hello'] += 1;
41+
~~~~~~~~~~
42+
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
43+
e['hello'] ++;
44+
~~~~~~~~~~
45+
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
46+
47+
Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
11
//// [noImplicitAnyStringIndexerOnObject.ts]
2-
var x = {}["hello"];
3-
var y: string = { '': 'foo' }[''];
2+
var a = {}["hello"];
3+
var b: string = { '': 'foo' }[''];
4+
5+
var c = {
6+
get: (key: string) => 'foobar'
7+
};
8+
c['hello'];
9+
const foo = c['hello'];
10+
11+
var d = {
12+
set: (key: string) => 'foobar'
13+
};
14+
const bar = d['hello'];
15+
16+
var e = {
17+
set: (key: string) => 'foobar',
18+
get: (key: string) => 'foobar'
19+
};
20+
e['hello'] = 'modified';
21+
e['hello'] += 1;
22+
e['hello'] ++;
23+
24+
425

526
//// [noImplicitAnyStringIndexerOnObject.js]
6-
var x = {}["hello"];
7-
var y = { '': 'foo' }[''];
27+
var a = {}["hello"];
28+
var b = { '': 'foo' }[''];
29+
var c = {
30+
get: function (key) { return 'foobar'; }
31+
};
32+
c['hello'];
33+
var foo = c['hello'];
34+
var d = {
35+
set: function (key) { return 'foobar'; }
36+
};
37+
var bar = d['hello'];
38+
var e = {
39+
set: function (key) { return 'foobar'; },
40+
get: function (key) { return 'foobar'; }
41+
};
42+
e['hello'] = 'modified';
43+
e['hello'] += 1;
44+
e['hello']++;
Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,58 @@
11
=== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts ===
2-
var x = {}["hello"];
3-
>x : Symbol(x, Decl(noImplicitAnyStringIndexerOnObject.ts, 0, 3))
2+
var a = {}["hello"];
3+
>a : Symbol(a, Decl(noImplicitAnyStringIndexerOnObject.ts, 0, 3))
44

5-
var y: string = { '': 'foo' }[''];
6-
>y : Symbol(y, Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 3))
5+
var b: string = { '': 'foo' }[''];
6+
>b : Symbol(b, Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 3))
77
>'' : Symbol('', Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 17))
88
>'' : Symbol('', Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 17))
99

10+
var c = {
11+
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
12+
13+
get: (key: string) => 'foobar'
14+
>get : Symbol(get, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 9))
15+
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 4, 8))
16+
17+
};
18+
c['hello'];
19+
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
20+
21+
const foo = c['hello'];
22+
>foo : Symbol(foo, Decl(noImplicitAnyStringIndexerOnObject.ts, 7, 5))
23+
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
24+
25+
var d = {
26+
>d : Symbol(d, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 3))
27+
28+
set: (key: string) => 'foobar'
29+
>set : Symbol(set, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 9))
30+
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 10, 8))
31+
32+
};
33+
const bar = d['hello'];
34+
>bar : Symbol(bar, Decl(noImplicitAnyStringIndexerOnObject.ts, 12, 5))
35+
>d : Symbol(d, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 3))
36+
37+
var e = {
38+
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
39+
40+
set: (key: string) => 'foobar',
41+
>set : Symbol(set, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 9))
42+
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 15, 8))
43+
44+
get: (key: string) => 'foobar'
45+
>get : Symbol(get, Decl(noImplicitAnyStringIndexerOnObject.ts, 15, 33))
46+
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 16, 8))
47+
48+
};
49+
e['hello'] = 'modified';
50+
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
51+
52+
e['hello'] += 1;
53+
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
54+
55+
e['hello'] ++;
56+
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
57+
58+
Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,92 @@
11
=== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts ===
2-
var x = {}["hello"];
3-
>x : any
2+
var a = {}["hello"];
3+
>a : any
44
>{}["hello"] : any
55
>{} : {}
66
>"hello" : "hello"
77

8-
var y: string = { '': 'foo' }[''];
9-
>y : string
8+
var b: string = { '': 'foo' }[''];
9+
>b : string
1010
>{ '': 'foo' }[''] : string
1111
>{ '': 'foo' } : { '': string; }
1212
>'' : string
1313
>'foo' : "foo"
1414
>'' : ""
1515

16+
var c = {
17+
>c : { get: (key: string) => string; }
18+
>{ get: (key: string) => 'foobar'} : { get: (key: string) => string; }
19+
20+
get: (key: string) => 'foobar'
21+
>get : (key: string) => string
22+
>(key: string) => 'foobar' : (key: string) => string
23+
>key : string
24+
>'foobar' : "foobar"
25+
26+
};
27+
c['hello'];
28+
>c['hello'] : any
29+
>c : { get: (key: string) => string; }
30+
>'hello' : "hello"
31+
32+
const foo = c['hello'];
33+
>foo : any
34+
>c['hello'] : any
35+
>c : { get: (key: string) => string; }
36+
>'hello' : "hello"
37+
38+
var d = {
39+
>d : { set: (key: string) => string; }
40+
>{ set: (key: string) => 'foobar'} : { set: (key: string) => string; }
41+
42+
set: (key: string) => 'foobar'
43+
>set : (key: string) => string
44+
>(key: string) => 'foobar' : (key: string) => string
45+
>key : string
46+
>'foobar' : "foobar"
47+
48+
};
49+
const bar = d['hello'];
50+
>bar : any
51+
>d['hello'] : any
52+
>d : { set: (key: string) => string; }
53+
>'hello' : "hello"
54+
55+
var e = {
56+
>e : { set: (key: string) => string; get: (key: string) => string; }
57+
>{ set: (key: string) => 'foobar', get: (key: string) => 'foobar'} : { set: (key: string) => string; get: (key: string) => string; }
58+
59+
set: (key: string) => 'foobar',
60+
>set : (key: string) => string
61+
>(key: string) => 'foobar' : (key: string) => string
62+
>key : string
63+
>'foobar' : "foobar"
64+
65+
get: (key: string) => 'foobar'
66+
>get : (key: string) => string
67+
>(key: string) => 'foobar' : (key: string) => string
68+
>key : string
69+
>'foobar' : "foobar"
70+
71+
};
72+
e['hello'] = 'modified';
73+
>e['hello'] = 'modified' : "modified"
74+
>e['hello'] : any
75+
>e : { set: (key: string) => string; get: (key: string) => string; }
76+
>'hello' : "hello"
77+
>'modified' : "modified"
78+
79+
e['hello'] += 1;
80+
>e['hello'] += 1 : any
81+
>e['hello'] : any
82+
>e : { set: (key: string) => string; get: (key: string) => string; }
83+
>'hello' : "hello"
84+
>1 : 1
85+
86+
e['hello'] ++;
87+
>e['hello'] ++ : number
88+
>e['hello'] : any
89+
>e : { set: (key: string) => string; get: (key: string) => string; }
90+
>'hello' : "hello"
91+
92+
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
11
// @noimplicitany: true
22

3-
var x = {}["hello"];
4-
var y: string = { '': 'foo' }[''];
3+
var a = {}["hello"];
4+
var b: string = { '': 'foo' }[''];
5+
6+
var c = {
7+
get: (key: string) => 'foobar'
8+
};
9+
c['hello'];
10+
const foo = c['hello'];
11+
12+
var d = {
13+
set: (key: string) => 'foobar'
14+
};
15+
const bar = d['hello'];
16+
17+
var e = {
18+
set: (key: string) => 'foobar',
19+
get: (key: string) => 'foobar'
20+
};
21+
e['hello'] = 'modified';
22+
e['hello'] += 1;
23+
e['hello'] ++;
24+

0 commit comments

Comments
 (0)