Skip to content

Commit 949fffb

Browse files
authored
feat(47983): Negative tuple index access should not be allowed (microsoft#49901)
* feat(47983): disallow negative integers for indexing tuple * change error message * add additional tests
1 parent b7355e3 commit 949fffb

File tree

7 files changed

+110
-41
lines changed

7 files changed

+110
-41
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15584,22 +15584,29 @@ namespace ts {
1558415584
getFlowTypeOfReference(accessExpression, propType) :
1558515585
propType;
1558615586
}
15587-
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) {
15587+
if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) {
15588+
const index = +propName;
1558815589
if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) {
1558915590
const indexNode = getIndexNodeForAccessExpression(accessNode);
1559015591
if (isTupleType(objectType)) {
15592+
if (index < 0) {
15593+
error(indexNode, Diagnostics.A_tuple_type_cannot_be_indexed_with_a_negative_value);
15594+
return undefinedType;
15595+
}
1559115596
error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2,
1559215597
typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName));
1559315598
}
1559415599
else {
1559515600
error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
1559615601
}
1559715602
}
15598-
errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType));
15599-
return mapType(objectType, t => {
15600-
const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType;
15601-
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType;
15602-
});
15603+
if (index >= 0) {
15604+
errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType));
15605+
return mapType(objectType, t => {
15606+
const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType;
15607+
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType;
15608+
});
15609+
}
1560315610
}
1560415611
}
1560515612
if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,6 +2360,10 @@
23602360
"category": "Error",
23612361
"code": 2513
23622362
},
2363+
"A tuple type cannot be indexed with a negative value.": {
2364+
"category": "Error",
2365+
"code": 2514
2366+
},
23632367
"Non-abstract class '{0}' does not implement inherited abstract member '{1}' from class '{2}'.": {
23642368
"category": "Error",
23652369
"code": 2515

tests/baselines/reference/indexerWithTuple.errors.txt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
tests/cases/conformance/types/tuple/indexerWithTuple.ts(11,25): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
22
tests/cases/conformance/types/tuple/indexerWithTuple.ts(17,27): error TS2493: Tuple type '[number, [string, number]]' of length '2' has no element at index '2'.
3-
tests/cases/conformance/types/tuple/indexerWithTuple.ts(20,30): error TS2493: Tuple type '[number, string | number]' of length '2' has no element at index '2'.
4-
tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2493: Tuple type '[boolean, string | number]' of length '2' has no element at index '2'.
3+
tests/cases/conformance/types/tuple/indexerWithTuple.ts(18,25): error TS2514: A tuple type cannot be indexed with a negative value.
4+
tests/cases/conformance/types/tuple/indexerWithTuple.ts(22,30): error TS2493: Tuple type '[number, string | number]' of length '2' has no element at index '2'.
5+
tests/cases/conformance/types/tuple/indexerWithTuple.ts(30,30): error TS2493: Tuple type '[boolean, string | number]' of length '2' has no element at index '2'.
6+
tests/cases/conformance/types/tuple/indexerWithTuple.ts(38,28): error TS2514: A tuple type cannot be indexed with a negative value.
57

68

7-
==== tests/cases/conformance/types/tuple/indexerWithTuple.ts (4 errors) ====
8-
var strNumTuple: [string, number] = ["foo", 10];
9+
==== tests/cases/conformance/types/tuple/indexerWithTuple.ts (6 errors) ====
10+
var strNumTuple: [string, number] = ["foo", 10];
911
var numTupleTuple: [number, [string, number]] = [10, ["bar", 20]];
10-
var unionTuple1: [number, string| number] = [10, "foo"];
11-
var unionTuple2: [boolean, string| number] = [true, "foo"];
12+
var unionTuple1: [number, string| number] = [10, "foo"];
13+
var unionTuple2: [boolean, string| number] = [true, "foo"];
1214

1315
// no error
1416
var idx0 = 0;
@@ -26,6 +28,10 @@ tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2493: Tu
2628
var ele17 = numTupleTuple[2]; // number | [string, number]
2729
~
2830
!!! error TS2493: Tuple type '[number, [string, number]]' of length '2' has no element at index '2'.
31+
var ele19 = strNumTuple[-1] // undefined
32+
~~
33+
!!! error TS2514: A tuple type cannot be indexed with a negative value.
34+
2935
var eleUnion10 = unionTuple1[0]; // number
3036
var eleUnion11 = unionTuple1[1]; // string | number
3137
var eleUnion12 = unionTuple1[2]; // string | number
@@ -44,4 +50,11 @@ tests/cases/conformance/types/tuple/indexerWithTuple.ts(28,30): error TS2493: Tu
4450
var eleUnion23 = unionTuple2[idx0]; // string | number | boolean
4551
var eleUnion24 = unionTuple2[idx1]; // string | number | boolean
4652
var eleUnion25 = unionTuple2["0"]; // boolean
47-
var eleUnion26 = unionTuple2["1"]; // string | number
53+
var eleUnion26 = unionTuple2["1"]; // string | number
54+
55+
type t1 = [string, number][0]; // string
56+
type t2 = [string, number][1]; // number
57+
type t3 = [string, number][-1]; // undefined
58+
~~
59+
!!! error TS2514: A tuple type cannot be indexed with a negative value.
60+

tests/baselines/reference/indexerWithTuple.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//// [indexerWithTuple.ts]
2-
var strNumTuple: [string, number] = ["foo", 10];
2+
var strNumTuple: [string, number] = ["foo", 10];
33
var numTupleTuple: [number, [string, number]] = [10, ["bar", 20]];
4-
var unionTuple1: [number, string| number] = [10, "foo"];
5-
var unionTuple2: [boolean, string| number] = [true, "foo"];
4+
var unionTuple1: [number, string| number] = [10, "foo"];
5+
var unionTuple2: [boolean, string| number] = [true, "foo"];
66

77
// no error
88
var idx0 = 0;
@@ -16,6 +16,8 @@ var ele15 = strNumTuple["0"]; // string
1616
var ele16 = strNumTuple["1"]; // number
1717
var strNumTuple1 = numTupleTuple[1]; //[string, number];
1818
var ele17 = numTupleTuple[2]; // number | [string, number]
19+
var ele19 = strNumTuple[-1] // undefined
20+
1921
var eleUnion10 = unionTuple1[0]; // number
2022
var eleUnion11 = unionTuple1[1]; // string | number
2123
var eleUnion12 = unionTuple1[2]; // string | number
@@ -30,7 +32,12 @@ var eleUnion22 = unionTuple2[2]; // string | number | boolean
3032
var eleUnion23 = unionTuple2[idx0]; // string | number | boolean
3133
var eleUnion24 = unionTuple2[idx1]; // string | number | boolean
3234
var eleUnion25 = unionTuple2["0"]; // boolean
33-
var eleUnion26 = unionTuple2["1"]; // string | number
35+
var eleUnion26 = unionTuple2["1"]; // string | number
36+
37+
type t1 = [string, number][0]; // string
38+
type t2 = [string, number][1]; // number
39+
type t3 = [string, number][-1]; // undefined
40+
3441

3542
//// [indexerWithTuple.js]
3643
var strNumTuple = ["foo", 10];
@@ -49,6 +56,7 @@ var ele15 = strNumTuple["0"]; // string
4956
var ele16 = strNumTuple["1"]; // number
5057
var strNumTuple1 = numTupleTuple[1]; //[string, number];
5158
var ele17 = numTupleTuple[2]; // number | [string, number]
59+
var ele19 = strNumTuple[-1]; // undefined
5260
var eleUnion10 = unionTuple1[0]; // number
5361
var eleUnion11 = unionTuple1[1]; // string | number
5462
var eleUnion12 = unionTuple1[2]; // string | number

tests/baselines/reference/indexerWithTuple.symbols

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
=== tests/cases/conformance/types/tuple/indexerWithTuple.ts ===
2-
var strNumTuple: [string, number] = ["foo", 10];
2+
var strNumTuple: [string, number] = ["foo", 10];
33
>strNumTuple : Symbol(strNumTuple, Decl(indexerWithTuple.ts, 0, 3))
44

55
var numTupleTuple: [number, [string, number]] = [10, ["bar", 20]];
66
>numTupleTuple : Symbol(numTupleTuple, Decl(indexerWithTuple.ts, 1, 3))
77

8-
var unionTuple1: [number, string| number] = [10, "foo"];
8+
var unionTuple1: [number, string| number] = [10, "foo"];
99
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
1010

11-
var unionTuple2: [boolean, string| number] = [true, "foo"];
11+
var unionTuple2: [boolean, string| number] = [true, "foo"];
1212
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
1313

1414
// no error
@@ -61,71 +61,84 @@ var ele17 = numTupleTuple[2]; // number | [string, number]
6161
>ele17 : Symbol(ele17, Decl(indexerWithTuple.ts, 16, 3))
6262
>numTupleTuple : Symbol(numTupleTuple, Decl(indexerWithTuple.ts, 1, 3))
6363

64+
var ele19 = strNumTuple[-1] // undefined
65+
>ele19 : Symbol(ele19, Decl(indexerWithTuple.ts, 17, 3))
66+
>strNumTuple : Symbol(strNumTuple, Decl(indexerWithTuple.ts, 0, 3))
67+
6468
var eleUnion10 = unionTuple1[0]; // number
65-
>eleUnion10 : Symbol(eleUnion10, Decl(indexerWithTuple.ts, 17, 3))
69+
>eleUnion10 : Symbol(eleUnion10, Decl(indexerWithTuple.ts, 19, 3))
6670
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
6771
>0 : Symbol(0)
6872

6973
var eleUnion11 = unionTuple1[1]; // string | number
70-
>eleUnion11 : Symbol(eleUnion11, Decl(indexerWithTuple.ts, 18, 3))
74+
>eleUnion11 : Symbol(eleUnion11, Decl(indexerWithTuple.ts, 20, 3))
7175
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
7276
>1 : Symbol(1)
7377

7478
var eleUnion12 = unionTuple1[2]; // string | number
75-
>eleUnion12 : Symbol(eleUnion12, Decl(indexerWithTuple.ts, 19, 3))
79+
>eleUnion12 : Symbol(eleUnion12, Decl(indexerWithTuple.ts, 21, 3))
7680
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
7781

7882
var eleUnion13 = unionTuple1[idx0]; // string | number
79-
>eleUnion13 : Symbol(eleUnion13, Decl(indexerWithTuple.ts, 20, 3))
83+
>eleUnion13 : Symbol(eleUnion13, Decl(indexerWithTuple.ts, 22, 3))
8084
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
8185
>idx0 : Symbol(idx0, Decl(indexerWithTuple.ts, 6, 3))
8286

8387
var eleUnion14 = unionTuple1[idx1]; // string | number
84-
>eleUnion14 : Symbol(eleUnion14, Decl(indexerWithTuple.ts, 21, 3))
88+
>eleUnion14 : Symbol(eleUnion14, Decl(indexerWithTuple.ts, 23, 3))
8589
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
8690
>idx1 : Symbol(idx1, Decl(indexerWithTuple.ts, 7, 3))
8791

8892
var eleUnion15 = unionTuple1["0"]; // number
89-
>eleUnion15 : Symbol(eleUnion15, Decl(indexerWithTuple.ts, 22, 3))
93+
>eleUnion15 : Symbol(eleUnion15, Decl(indexerWithTuple.ts, 24, 3))
9094
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
9195
>"0" : Symbol(0)
9296

9397
var eleUnion16 = unionTuple1["1"]; // string | number
94-
>eleUnion16 : Symbol(eleUnion16, Decl(indexerWithTuple.ts, 23, 3))
98+
>eleUnion16 : Symbol(eleUnion16, Decl(indexerWithTuple.ts, 25, 3))
9599
>unionTuple1 : Symbol(unionTuple1, Decl(indexerWithTuple.ts, 2, 3))
96100
>"1" : Symbol(1)
97101

98102
var eleUnion20 = unionTuple2[0]; // boolean
99-
>eleUnion20 : Symbol(eleUnion20, Decl(indexerWithTuple.ts, 25, 3))
103+
>eleUnion20 : Symbol(eleUnion20, Decl(indexerWithTuple.ts, 27, 3))
100104
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
101105
>0 : Symbol(0)
102106

103107
var eleUnion21 = unionTuple2[1]; // string | number
104-
>eleUnion21 : Symbol(eleUnion21, Decl(indexerWithTuple.ts, 26, 3))
108+
>eleUnion21 : Symbol(eleUnion21, Decl(indexerWithTuple.ts, 28, 3))
105109
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
106110
>1 : Symbol(1)
107111

108112
var eleUnion22 = unionTuple2[2]; // string | number | boolean
109-
>eleUnion22 : Symbol(eleUnion22, Decl(indexerWithTuple.ts, 27, 3))
113+
>eleUnion22 : Symbol(eleUnion22, Decl(indexerWithTuple.ts, 29, 3))
110114
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
111115

112116
var eleUnion23 = unionTuple2[idx0]; // string | number | boolean
113-
>eleUnion23 : Symbol(eleUnion23, Decl(indexerWithTuple.ts, 28, 3))
117+
>eleUnion23 : Symbol(eleUnion23, Decl(indexerWithTuple.ts, 30, 3))
114118
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
115119
>idx0 : Symbol(idx0, Decl(indexerWithTuple.ts, 6, 3))
116120

117121
var eleUnion24 = unionTuple2[idx1]; // string | number | boolean
118-
>eleUnion24 : Symbol(eleUnion24, Decl(indexerWithTuple.ts, 29, 3))
122+
>eleUnion24 : Symbol(eleUnion24, Decl(indexerWithTuple.ts, 31, 3))
119123
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
120124
>idx1 : Symbol(idx1, Decl(indexerWithTuple.ts, 7, 3))
121125

122126
var eleUnion25 = unionTuple2["0"]; // boolean
123-
>eleUnion25 : Symbol(eleUnion25, Decl(indexerWithTuple.ts, 30, 3))
127+
>eleUnion25 : Symbol(eleUnion25, Decl(indexerWithTuple.ts, 32, 3))
124128
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
125129
>"0" : Symbol(0)
126130

127131
var eleUnion26 = unionTuple2["1"]; // string | number
128-
>eleUnion26 : Symbol(eleUnion26, Decl(indexerWithTuple.ts, 31, 3))
132+
>eleUnion26 : Symbol(eleUnion26, Decl(indexerWithTuple.ts, 33, 3))
129133
>unionTuple2 : Symbol(unionTuple2, Decl(indexerWithTuple.ts, 3, 3))
130134
>"1" : Symbol(1)
131135

136+
type t1 = [string, number][0]; // string
137+
>t1 : Symbol(t1, Decl(indexerWithTuple.ts, 33, 34))
138+
139+
type t2 = [string, number][1]; // number
140+
>t2 : Symbol(t2, Decl(indexerWithTuple.ts, 35, 30))
141+
142+
type t3 = [string, number][-1]; // undefined
143+
>t3 : Symbol(t3, Decl(indexerWithTuple.ts, 36, 30))
144+

tests/baselines/reference/indexerWithTuple.types

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/conformance/types/tuple/indexerWithTuple.ts ===
2-
var strNumTuple: [string, number] = ["foo", 10];
2+
var strNumTuple: [string, number] = ["foo", 10];
33
>strNumTuple : [string, number]
44
>["foo", 10] : [string, number]
55
>"foo" : "foo"
@@ -13,13 +13,13 @@ var numTupleTuple: [number, [string, number]] = [10, ["bar", 20]];
1313
>"bar" : "bar"
1414
>20 : 20
1515

16-
var unionTuple1: [number, string| number] = [10, "foo"];
16+
var unionTuple1: [number, string| number] = [10, "foo"];
1717
>unionTuple1 : [number, string | number]
1818
>[10, "foo"] : [number, string]
1919
>10 : 10
2020
>"foo" : "foo"
2121

22-
var unionTuple2: [boolean, string| number] = [true, "foo"];
22+
var unionTuple2: [boolean, string| number] = [true, "foo"];
2323
>unionTuple2 : [boolean, string | number]
2424
>[true, "foo"] : [true, string]
2525
>true : true
@@ -88,6 +88,13 @@ var ele17 = numTupleTuple[2]; // number | [string, number]
8888
>numTupleTuple : [number, [string, number]]
8989
>2 : 2
9090

91+
var ele19 = strNumTuple[-1] // undefined
92+
>ele19 : undefined
93+
>strNumTuple[-1] : undefined
94+
>strNumTuple : [string, number]
95+
>-1 : -1
96+
>1 : 1
97+
9198
var eleUnion10 = unionTuple1[0]; // number
9299
>eleUnion10 : number
93100
>unionTuple1[0] : number
@@ -172,3 +179,14 @@ var eleUnion26 = unionTuple2["1"]; // string | number
172179
>unionTuple2 : [boolean, string | number]
173180
>"1" : "1"
174181

182+
type t1 = [string, number][0]; // string
183+
>t1 : string
184+
185+
type t2 = [string, number][1]; // number
186+
>t2 : number
187+
188+
type t3 = [string, number][-1]; // undefined
189+
>t3 : undefined
190+
>-1 : -1
191+
>1 : 1
192+

tests/cases/conformance/types/tuple/indexerWithTuple.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
var strNumTuple: [string, number] = ["foo", 10];
1+
var strNumTuple: [string, number] = ["foo", 10];
22
var numTupleTuple: [number, [string, number]] = [10, ["bar", 20]];
3-
var unionTuple1: [number, string| number] = [10, "foo"];
4-
var unionTuple2: [boolean, string| number] = [true, "foo"];
3+
var unionTuple1: [number, string| number] = [10, "foo"];
4+
var unionTuple2: [boolean, string| number] = [true, "foo"];
55

66
// no error
77
var idx0 = 0;
@@ -15,6 +15,8 @@ var ele15 = strNumTuple["0"]; // string
1515
var ele16 = strNumTuple["1"]; // number
1616
var strNumTuple1 = numTupleTuple[1]; //[string, number];
1717
var ele17 = numTupleTuple[2]; // number | [string, number]
18+
var ele19 = strNumTuple[-1] // undefined
19+
1820
var eleUnion10 = unionTuple1[0]; // number
1921
var eleUnion11 = unionTuple1[1]; // string | number
2022
var eleUnion12 = unionTuple1[2]; // string | number
@@ -29,4 +31,8 @@ var eleUnion22 = unionTuple2[2]; // string | number | boolean
2931
var eleUnion23 = unionTuple2[idx0]; // string | number | boolean
3032
var eleUnion24 = unionTuple2[idx1]; // string | number | boolean
3133
var eleUnion25 = unionTuple2["0"]; // boolean
32-
var eleUnion26 = unionTuple2["1"]; // string | number
34+
var eleUnion26 = unionTuple2["1"]; // string | number
35+
36+
type t1 = [string, number][0]; // string
37+
type t2 = [string, number][1]; // number
38+
type t3 = [string, number][-1]; // undefined

0 commit comments

Comments
 (0)