Skip to content

Commit 9569198

Browse files
authored
fix(25770): add diagnostic message for the possible mapped type used as an index (#39973)
1 parent 6ec3629 commit 9569198

7 files changed

+251
-4
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,16 +2243,32 @@ namespace ts {
22432243
}
22442244
const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false));
22452245
if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) {
2246-
const message = isES2015OrLaterConstructorName(name)
2247-
? Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later
2248-
: Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here;
2249-
error(errorLocation, message, unescapeLeadingUnderscores(name));
2246+
const rawName = unescapeLeadingUnderscores(name);
2247+
if (isES2015OrLaterConstructorName(name)) {
2248+
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName);
2249+
}
2250+
else if (maybeMappedType(errorLocation, symbol)) {
2251+
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K");
2252+
}
2253+
else {
2254+
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName);
2255+
}
22502256
return true;
22512257
}
22522258
}
22532259
return false;
22542260
}
22552261

2262+
function maybeMappedType(node: Node, symbol: Symbol) {
2263+
const container = findAncestor(node.parent, n =>
2264+
isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined;
2265+
if (container && container.members.length === 1) {
2266+
const type = getDeclaredTypeOfSymbol(symbol);
2267+
return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true);
2268+
}
2269+
return false;
2270+
}
2271+
22562272
function isES2015OrLaterConstructorName(n: __String) {
22572273
switch (n) {
22582274
case "Promise":

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,6 +2607,10 @@
26072607
"category": "Error",
26082608
"code": 2689
26092609
},
2610+
"'{0}' only refers to a type, but is being used as a value here. Did you mean to use '{1} in {0}'?": {
2611+
"category": "Error",
2612+
"code": 2690
2613+
},
26102614
"An import path cannot end with a '{0}' extension. Consider importing '{1}' instead.": {
26112615
"category": "Error",
26122616
"code": 2691
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(3,5): error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
2+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(3,6): error TS2690: 'K' only refers to a type, but is being used as a value here. Did you mean to use 'P in K'?
3+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(13,5): error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
4+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(13,6): error TS2690: 'K2' only refers to a type, but is being used as a value here. Did you mean to use 'K in K2'?
5+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(18,5): error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
6+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(18,6): error TS2690: 'K3' only refers to a type, but is being used as a value here. Did you mean to use 'K in K3'?
7+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(23,5): error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
8+
tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts(23,6): error TS2693: 'K4' only refers to a type, but is being used as a value here.
9+
10+
11+
==== tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts (8 errors) ====
12+
type K = number | string;
13+
type T = {
14+
[K]: number; // Did you mean to use 'P in K'?
15+
~~~
16+
!!! error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
17+
~
18+
!!! error TS2690: 'K' only refers to a type, but is being used as a value here. Did you mean to use 'P in K'?
19+
}
20+
21+
const K1 = Symbol();
22+
type T1 = {
23+
[K1]: number;
24+
}
25+
26+
type K2 = "x" | "y";
27+
type T2 = {
28+
[K2]: number; // Did you mean to use 'K in K2'?
29+
~~~~
30+
!!! error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
31+
~~
32+
!!! error TS2690: 'K2' only refers to a type, but is being used as a value here. Did you mean to use 'K in K2'?
33+
}
34+
35+
type K3 = number | string;
36+
type T3 = {
37+
[K3]: number; // Did you mean to use 'K in K3'?
38+
~~~~
39+
!!! error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
40+
~~
41+
!!! error TS2690: 'K3' only refers to a type, but is being used as a value here. Did you mean to use 'K in K3'?
42+
}
43+
44+
type K4 = number | string;
45+
type T4 = {
46+
[K4]: number;
47+
~~~~
48+
!!! error TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
49+
~~
50+
!!! error TS2693: 'K4' only refers to a type, but is being used as a value here.
51+
k4: string;
52+
}
53+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [typeUsedAsTypeLiteralIndex.ts]
2+
type K = number | string;
3+
type T = {
4+
[K]: number; // Did you mean to use 'P in K'?
5+
}
6+
7+
const K1 = Symbol();
8+
type T1 = {
9+
[K1]: number;
10+
}
11+
12+
type K2 = "x" | "y";
13+
type T2 = {
14+
[K2]: number; // Did you mean to use 'K in K2'?
15+
}
16+
17+
type K3 = number | string;
18+
type T3 = {
19+
[K3]: number; // Did you mean to use 'K in K3'?
20+
}
21+
22+
type K4 = number | string;
23+
type T4 = {
24+
[K4]: number;
25+
k4: string;
26+
}
27+
28+
29+
//// [typeUsedAsTypeLiteralIndex.js]
30+
const K1 = Symbol();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts ===
2+
type K = number | string;
3+
>K : Symbol(K, Decl(typeUsedAsTypeLiteralIndex.ts, 0, 0))
4+
5+
type T = {
6+
>T : Symbol(T, Decl(typeUsedAsTypeLiteralIndex.ts, 0, 25))
7+
8+
[K]: number; // Did you mean to use 'P in K'?
9+
>[K] : Symbol([K], Decl(typeUsedAsTypeLiteralIndex.ts, 1, 10))
10+
}
11+
12+
const K1 = Symbol();
13+
>K1 : Symbol(K1, Decl(typeUsedAsTypeLiteralIndex.ts, 5, 5))
14+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
15+
16+
type T1 = {
17+
>T1 : Symbol(T1, Decl(typeUsedAsTypeLiteralIndex.ts, 5, 20))
18+
19+
[K1]: number;
20+
>[K1] : Symbol([K1], Decl(typeUsedAsTypeLiteralIndex.ts, 6, 11))
21+
>K1 : Symbol(K1, Decl(typeUsedAsTypeLiteralIndex.ts, 5, 5))
22+
}
23+
24+
type K2 = "x" | "y";
25+
>K2 : Symbol(K2, Decl(typeUsedAsTypeLiteralIndex.ts, 8, 1))
26+
27+
type T2 = {
28+
>T2 : Symbol(T2, Decl(typeUsedAsTypeLiteralIndex.ts, 10, 20))
29+
30+
[K2]: number; // Did you mean to use 'K in K2'?
31+
>[K2] : Symbol([K2], Decl(typeUsedAsTypeLiteralIndex.ts, 11, 11))
32+
}
33+
34+
type K3 = number | string;
35+
>K3 : Symbol(K3, Decl(typeUsedAsTypeLiteralIndex.ts, 13, 1))
36+
37+
type T3 = {
38+
>T3 : Symbol(T3, Decl(typeUsedAsTypeLiteralIndex.ts, 15, 26))
39+
40+
[K3]: number; // Did you mean to use 'K in K3'?
41+
>[K3] : Symbol([K3], Decl(typeUsedAsTypeLiteralIndex.ts, 16, 11))
42+
}
43+
44+
type K4 = number | string;
45+
>K4 : Symbol(K4, Decl(typeUsedAsTypeLiteralIndex.ts, 18, 1))
46+
47+
type T4 = {
48+
>T4 : Symbol(T4, Decl(typeUsedAsTypeLiteralIndex.ts, 20, 26))
49+
50+
[K4]: number;
51+
>[K4] : Symbol([K4], Decl(typeUsedAsTypeLiteralIndex.ts, 21, 11))
52+
53+
k4: string;
54+
>k4 : Symbol(k4, Decl(typeUsedAsTypeLiteralIndex.ts, 22, 17))
55+
}
56+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/compiler/typeUsedAsTypeLiteralIndex.ts ===
2+
type K = number | string;
3+
>K : K
4+
5+
type T = {
6+
>T : T
7+
8+
[K]: number; // Did you mean to use 'P in K'?
9+
>[K] : number
10+
>K : any
11+
}
12+
13+
const K1 = Symbol();
14+
>K1 : unique symbol
15+
>Symbol() : unique symbol
16+
>Symbol : SymbolConstructor
17+
18+
type T1 = {
19+
>T1 : T1
20+
21+
[K1]: number;
22+
>[K1] : number
23+
>K1 : unique symbol
24+
}
25+
26+
type K2 = "x" | "y";
27+
>K2 : K2
28+
29+
type T2 = {
30+
>T2 : T2
31+
32+
[K2]: number; // Did you mean to use 'K in K2'?
33+
>[K2] : number
34+
>K2 : any
35+
}
36+
37+
type K3 = number | string;
38+
>K3 : K
39+
40+
type T3 = {
41+
>T3 : T3
42+
43+
[K3]: number; // Did you mean to use 'K in K3'?
44+
>[K3] : number
45+
>K3 : any
46+
}
47+
48+
type K4 = number | string;
49+
>K4 : K
50+
51+
type T4 = {
52+
>T4 : T4
53+
54+
[K4]: number;
55+
>[K4] : number
56+
>K4 : any
57+
58+
k4: string;
59+
>k4 : string
60+
}
61+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// @target: esnext
2+
3+
type K = number | string;
4+
type T = {
5+
[K]: number; // Did you mean to use 'P in K'?
6+
}
7+
8+
const K1 = Symbol();
9+
type T1 = {
10+
[K1]: number;
11+
}
12+
13+
type K2 = "x" | "y";
14+
type T2 = {
15+
[K2]: number; // Did you mean to use 'K in K2'?
16+
}
17+
18+
type K3 = number | string;
19+
type T3 = {
20+
[K3]: number; // Did you mean to use 'K in K3'?
21+
}
22+
23+
type K4 = number | string;
24+
type T4 = {
25+
[K4]: number;
26+
k4: string;
27+
}

0 commit comments

Comments
 (0)