Skip to content

Commit 4320104

Browse files
authored
Do not include undefined in indexed access on tuple types within range of total fixed elements (#54558)
1 parent 21bb216 commit 4320104

11 files changed

+156
-34
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16277,6 +16277,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1627716277
return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1;
1627816278
}
1627916279

16280+
function getTotalFixedElementCount(type: TupleType) {
16281+
return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed);
16282+
}
16283+
1628016284
function getElementTypes(type: TupleTypeReference): readonly Type[] {
1628116285
const typeArguments = getTypeArguments(type);
1628216286
const arity = getTypeReferenceArity(type);
@@ -17402,10 +17406,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1740217406
}
1740317407
if (index >= 0) {
1740417408
errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType));
17405-
return mapType(objectType, t => {
17406-
const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType;
17407-
return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, missingType]) : restType;
17408-
});
17409+
return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : undefined);
1740917410
}
1741017411
}
1741117412
}
@@ -17745,8 +17746,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1774517746
// preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved
1774617747
// eagerly using the constraint type of 'this' at the given location.
1774717748
if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ?
17748-
isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) :
17749-
isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || isGenericReducibleType(objectType))) {
17749+
isGenericTupleType(objectType) && !indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target)) :
17750+
isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType))) {
1775017751
if (objectType.flags & TypeFlags.AnyOrUnknown) {
1775117752
return objectType;
1775217753
}
@@ -23325,18 +23326,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2332523326
return propType;
2332623327
}
2332723328
if (everyType(type, isTupleType)) {
23328-
return mapType(type, t => {
23329-
const tupleType = t as TupleTypeReference;
23330-
const restType = getRestTypeOfTupleType(tupleType);
23331-
if (!restType) {
23332-
return undefinedType;
23333-
}
23334-
if (compilerOptions.noUncheckedIndexedAccess &&
23335-
index >= tupleType.target.fixedLength + getEndElementCount(tupleType.target, ElementFlags.Fixed)) {
23336-
return getUnionType([restType, undefinedType]);
23337-
}
23338-
return restType;
23339-
});
23329+
return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined);
2334023330
}
2334123331
return undefined;
2334223332
}
@@ -23454,6 +23444,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2345423444
return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength);
2345523445
}
2345623446

23447+
function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) {
23448+
return mapType(type, t => {
23449+
const tupleType = t as TupleTypeReference;
23450+
const restType = getRestTypeOfTupleType(tupleType);
23451+
if (!restType) {
23452+
return undefinedType;
23453+
}
23454+
if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) {
23455+
return getUnionType([restType, undefinedOrMissingType]);
23456+
}
23457+
return restType;
23458+
});
23459+
}
23460+
2345723461
function getRestArrayTypeOfTupleType(type: TupleTypeReference) {
2345823462
const restType = getRestTypeOfTupleType(type);
2345923463
return restType && createArrayType(restType);

tests/baselines/reference/destructureTupleWithVariableElement.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ declare const strings2: [string, ...Array<string>, string]
4141

4242
const [s3, s4, s5] = strings2;
4343
>s3 : string
44-
>s4 : string | undefined
44+
>s4 : string
4545
>s5 : string | undefined
4646
>strings2 : [string, ...string[], string]
4747

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
indexedAccessWithVariableElement.ts(7,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
2+
Type 'undefined' is not assignable to type 'number'.
3+
indexedAccessWithVariableElement.ts(13,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
4+
Type 'undefined' is not assignable to type 'number'.
5+
6+
7+
==== indexedAccessWithVariableElement.ts (2 errors) ====
8+
// repro from https://github.com/microsoft/TypeScript/issues/54420
9+
10+
declare const array1: [...number[], number]
11+
const el1: number = array1[0]
12+
13+
declare const array2: [...number[], number]
14+
const el2: number = array2[1]
15+
~~~
16+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'.
17+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
18+
19+
declare const array3: [number, ...number[], number]
20+
const el3: number = array3[1]
21+
22+
declare const array4: [number, ...number[], number]
23+
const el4: number = array4[2]
24+
~~~
25+
!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'.
26+
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
27+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/compiler/indexedAccessWithVariableElement.ts] ////
2+
3+
=== indexedAccessWithVariableElement.ts ===
4+
// repro from https://github.com/microsoft/TypeScript/issues/54420
5+
6+
declare const array1: [...number[], number]
7+
>array1 : Symbol(array1, Decl(indexedAccessWithVariableElement.ts, 2, 13))
8+
9+
const el1: number = array1[0]
10+
>el1 : Symbol(el1, Decl(indexedAccessWithVariableElement.ts, 3, 5))
11+
>array1 : Symbol(array1, Decl(indexedAccessWithVariableElement.ts, 2, 13))
12+
13+
declare const array2: [...number[], number]
14+
>array2 : Symbol(array2, Decl(indexedAccessWithVariableElement.ts, 5, 13))
15+
16+
const el2: number = array2[1]
17+
>el2 : Symbol(el2, Decl(indexedAccessWithVariableElement.ts, 6, 5))
18+
>array2 : Symbol(array2, Decl(indexedAccessWithVariableElement.ts, 5, 13))
19+
20+
declare const array3: [number, ...number[], number]
21+
>array3 : Symbol(array3, Decl(indexedAccessWithVariableElement.ts, 8, 13))
22+
23+
const el3: number = array3[1]
24+
>el3 : Symbol(el3, Decl(indexedAccessWithVariableElement.ts, 9, 5))
25+
>array3 : Symbol(array3, Decl(indexedAccessWithVariableElement.ts, 8, 13))
26+
27+
declare const array4: [number, ...number[], number]
28+
>array4 : Symbol(array4, Decl(indexedAccessWithVariableElement.ts, 11, 13))
29+
30+
const el4: number = array4[2]
31+
>el4 : Symbol(el4, Decl(indexedAccessWithVariableElement.ts, 12, 5))
32+
>array4 : Symbol(array4, Decl(indexedAccessWithVariableElement.ts, 11, 13))
33+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [tests/cases/compiler/indexedAccessWithVariableElement.ts] ////
2+
3+
=== indexedAccessWithVariableElement.ts ===
4+
// repro from https://github.com/microsoft/TypeScript/issues/54420
5+
6+
declare const array1: [...number[], number]
7+
>array1 : [...number[], number]
8+
9+
const el1: number = array1[0]
10+
>el1 : number
11+
>array1[0] : number
12+
>array1 : [...number[], number]
13+
>0 : 0
14+
15+
declare const array2: [...number[], number]
16+
>array2 : [...number[], number]
17+
18+
const el2: number = array2[1]
19+
>el2 : number
20+
>array2[1] : number | undefined
21+
>array2 : [...number[], number]
22+
>1 : 1
23+
24+
declare const array3: [number, ...number[], number]
25+
>array3 : [number, ...number[], number]
26+
27+
const el3: number = array3[1]
28+
>el3 : number
29+
>array3[1] : number
30+
>array3 : [number, ...number[], number]
31+
>1 : 1
32+
33+
declare const array4: [number, ...number[], number]
34+
>array4 : [number, ...number[], number]
35+
36+
const el4: number = array4[2]
37+
>el4 : number
38+
>array4[2] : number | undefined
39+
>array4 : [number, ...number[], number]
40+
>2 : 2
41+

tests/baselines/reference/variadicTuples1.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab
144144

145145
function f1<T extends unknown[]>(t: [string, ...T, number], n: number) {
146146
const a = t[0]; // string
147-
const b = t[1]; // [string, ...T, number][1]
147+
const b = t[1]; // number | T[number]
148148
const c = t[2]; // [string, ...T, number][2]
149149
const d = t[n]; // [string, ...T, number][number]
150150
}
@@ -160,7 +160,7 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab
160160
function f3<T extends unknown[]>(t: [string, ...T, number]) {
161161
let [...ax] = t; // [string, ...T, number]
162162
let [b1, ...bx] = t; // string, [...T, number]
163-
let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[]
163+
let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[]
164164
}
165165

166166
// Mapped types applied to variadic tuple types

tests/baselines/reference/variadicTuples1.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function f0<T extends unknown[]>(t: [string, ...T], n: number) {
8888

8989
function f1<T extends unknown[]>(t: [string, ...T, number], n: number) {
9090
const a = t[0]; // string
91-
const b = t[1]; // [string, ...T, number][1]
91+
const b = t[1]; // number | T[number]
9292
const c = t[2]; // [string, ...T, number][2]
9393
const d = t[n]; // [string, ...T, number][number]
9494
}
@@ -104,7 +104,7 @@ function f2<T extends unknown[]>(t: [string, ...T]) {
104104
function f3<T extends unknown[]>(t: [string, ...T, number]) {
105105
let [...ax] = t; // [string, ...T, number]
106106
let [b1, ...bx] = t; // string, [...T, number]
107-
let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[]
107+
let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[]
108108
}
109109

110110
// Mapped types applied to variadic tuple types
@@ -478,7 +478,7 @@ function f0(t, n) {
478478
}
479479
function f1(t, n) {
480480
var a = t[0]; // string
481-
var b = t[1]; // [string, ...T, number][1]
481+
var b = t[1]; // number | T[number]
482482
var c = t[2]; // [string, ...T, number][2]
483483
var d = t[n]; // [string, ...T, number][number]
484484
}
@@ -491,7 +491,7 @@ function f2(t) {
491491
function f3(t) {
492492
var ax = t.slice(0); // [string, ...T, number]
493493
var b1 = t[0], bx = t.slice(1); // string, [...T, number]
494-
var c1 = t[0], c2 = t[1], cx = t.slice(2); // string, [string, ...T, number][1], (number | T[number])[]
494+
var c1 = t[0], c2 = t[1], cx = t.slice(2); // string, number | T[number], (number | T[number])[]
495495
}
496496
var tm1 = fm1([['abc'], [42], [true], ['def']]); // [boolean, string]
497497
function gx1(u, v) {

tests/baselines/reference/variadicTuples1.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function f1<T extends unknown[]>(t: [string, ...T, number], n: number) {
285285
>t : Symbol(t, Decl(variadicTuples1.ts, 85, 33))
286286
>0 : Symbol(0)
287287

288-
const b = t[1]; // [string, ...T, number][1]
288+
const b = t[1]; // number | T[number]
289289
>b : Symbol(b, Decl(variadicTuples1.ts, 87, 9))
290290
>t : Symbol(t, Decl(variadicTuples1.ts, 85, 33))
291291

@@ -338,7 +338,7 @@ function f3<T extends unknown[]>(t: [string, ...T, number]) {
338338
>bx : Symbol(bx, Decl(variadicTuples1.ts, 102, 12))
339339
>t : Symbol(t, Decl(variadicTuples1.ts, 100, 33))
340340

341-
let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[]
341+
let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[]
342342
>c1 : Symbol(c1, Decl(variadicTuples1.ts, 103, 9))
343343
>c2 : Symbol(c2, Decl(variadicTuples1.ts, 103, 12))
344344
>cx : Symbol(cx, Decl(variadicTuples1.ts, 103, 16))

tests/baselines/reference/variadicTuples1.types

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,9 @@ function f1<T extends unknown[]>(t: [string, ...T, number], n: number) {
344344
>t : [string, ...T, number]
345345
>0 : 0
346346

347-
const b = t[1]; // [string, ...T, number][1]
348-
>b : [string, ...T, number][1]
349-
>t[1] : [string, ...T, number][1]
347+
const b = t[1]; // number | T[number]
348+
>b : number | T[number]
349+
>t[1] : number | T[number]
350350
>t : [string, ...T, number]
351351
>1 : 1
352352

@@ -398,9 +398,9 @@ function f3<T extends unknown[]>(t: [string, ...T, number]) {
398398
>bx : [...T, number]
399399
>t : [string, ...T, number]
400400

401-
let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[]
401+
let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[]
402402
>c1 : string
403-
>c2 : [string, ...T, number][1]
403+
>c2 : number | T[number]
404404
>cx : (number | T[number])[]
405405
>t : [string, ...T, number]
406406
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @strict: true
2+
// @noUncheckedIndexedAccess: true
3+
// @noEmit: true
4+
5+
// repro from https://github.com/microsoft/TypeScript/issues/54420
6+
7+
declare const array1: [...number[], number]
8+
const el1: number = array1[0]
9+
10+
declare const array2: [...number[], number]
11+
const el2: number = array2[1]
12+
13+
declare const array3: [number, ...number[], number]
14+
const el3: number = array3[1]
15+
16+
declare const array4: [number, ...number[], number]
17+
const el4: number = array4[2]

0 commit comments

Comments
 (0)