diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9003ebc18da..6b23bce3b9f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16277,6 +16277,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; } + function getTotalFixedElementCount(type: TupleType) { + return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed); + } + function getElementTypes(type: TupleTypeReference): readonly Type[] { const typeArguments = getTypeArguments(type); const arity = getTypeReferenceArity(type); @@ -17402,10 +17406,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (index >= 0) { errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); - return mapType(objectType, t => { - const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, missingType]) : restType; - }); + return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : undefined); } } } @@ -17745,8 +17746,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved // eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? - isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : - isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)) || isGenericReducibleType(objectType))) { + isGenericTupleType(objectType) && !indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target)) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType))) { if (objectType.flags & TypeFlags.AnyOrUnknown) { return objectType; } @@ -23325,18 +23326,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return propType; } if (everyType(type, isTupleType)) { - return mapType(type, t => { - const tupleType = t as TupleTypeReference; - const restType = getRestTypeOfTupleType(tupleType); - if (!restType) { - return undefinedType; - } - if (compilerOptions.noUncheckedIndexedAccess && - index >= tupleType.target.fixedLength + getEndElementCount(tupleType.target, ElementFlags.Fixed)) { - return getUnionType([restType, undefinedType]); - } - return restType; - }); + return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined); } return undefined; } @@ -23454,6 +23444,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); } + function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) { + return mapType(type, t => { + const tupleType = t as TupleTypeReference; + const restType = getRestTypeOfTupleType(tupleType); + if (!restType) { + return undefinedType; + } + if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) { + return getUnionType([restType, undefinedOrMissingType]); + } + return restType; + }); + } + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { const restType = getRestTypeOfTupleType(type); return restType && createArrayType(restType); diff --git a/tests/baselines/reference/destructureTupleWithVariableElement.types b/tests/baselines/reference/destructureTupleWithVariableElement.types index cd78b8be400..9bf1bd02558 100644 --- a/tests/baselines/reference/destructureTupleWithVariableElement.types +++ b/tests/baselines/reference/destructureTupleWithVariableElement.types @@ -41,7 +41,7 @@ declare const strings2: [string, ...Array, string] const [s3, s4, s5] = strings2; >s3 : string ->s4 : string | undefined +>s4 : string >s5 : string | undefined >strings2 : [string, ...string[], string] diff --git a/tests/baselines/reference/indexedAccessWithVariableElement.errors.txt b/tests/baselines/reference/indexedAccessWithVariableElement.errors.txt new file mode 100644 index 00000000000..00403598d81 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithVariableElement.errors.txt @@ -0,0 +1,27 @@ +indexedAccessWithVariableElement.ts(7,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. +indexedAccessWithVariableElement.ts(13,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'. + Type 'undefined' is not assignable to type 'number'. + + +==== indexedAccessWithVariableElement.ts (2 errors) ==== + // repro from https://github.com/microsoft/TypeScript/issues/54420 + + declare const array1: [...number[], number] + const el1: number = array1[0] + + declare const array2: [...number[], number] + const el2: number = array2[1] + ~~~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'. +!!! error TS2322: Type 'undefined' is not assignable to type 'number'. + + declare const array3: [number, ...number[], number] + const el3: number = array3[1] + + declare const array4: [number, ...number[], number] + const el4: number = array4[2] + ~~~ +!!! error TS2322: Type 'number | undefined' is not assignable to type 'number'. +!!! error TS2322: Type 'undefined' is not assignable to type 'number'. + \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessWithVariableElement.symbols b/tests/baselines/reference/indexedAccessWithVariableElement.symbols new file mode 100644 index 00000000000..9cbf52a5d29 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithVariableElement.symbols @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/indexedAccessWithVariableElement.ts] //// + +=== indexedAccessWithVariableElement.ts === +// repro from https://github.com/microsoft/TypeScript/issues/54420 + +declare const array1: [...number[], number] +>array1 : Symbol(array1, Decl(indexedAccessWithVariableElement.ts, 2, 13)) + +const el1: number = array1[0] +>el1 : Symbol(el1, Decl(indexedAccessWithVariableElement.ts, 3, 5)) +>array1 : Symbol(array1, Decl(indexedAccessWithVariableElement.ts, 2, 13)) + +declare const array2: [...number[], number] +>array2 : Symbol(array2, Decl(indexedAccessWithVariableElement.ts, 5, 13)) + +const el2: number = array2[1] +>el2 : Symbol(el2, Decl(indexedAccessWithVariableElement.ts, 6, 5)) +>array2 : Symbol(array2, Decl(indexedAccessWithVariableElement.ts, 5, 13)) + +declare const array3: [number, ...number[], number] +>array3 : Symbol(array3, Decl(indexedAccessWithVariableElement.ts, 8, 13)) + +const el3: number = array3[1] +>el3 : Symbol(el3, Decl(indexedAccessWithVariableElement.ts, 9, 5)) +>array3 : Symbol(array3, Decl(indexedAccessWithVariableElement.ts, 8, 13)) + +declare const array4: [number, ...number[], number] +>array4 : Symbol(array4, Decl(indexedAccessWithVariableElement.ts, 11, 13)) + +const el4: number = array4[2] +>el4 : Symbol(el4, Decl(indexedAccessWithVariableElement.ts, 12, 5)) +>array4 : Symbol(array4, Decl(indexedAccessWithVariableElement.ts, 11, 13)) + diff --git a/tests/baselines/reference/indexedAccessWithVariableElement.types b/tests/baselines/reference/indexedAccessWithVariableElement.types new file mode 100644 index 00000000000..9b9e0b12161 --- /dev/null +++ b/tests/baselines/reference/indexedAccessWithVariableElement.types @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/indexedAccessWithVariableElement.ts] //// + +=== indexedAccessWithVariableElement.ts === +// repro from https://github.com/microsoft/TypeScript/issues/54420 + +declare const array1: [...number[], number] +>array1 : [...number[], number] + +const el1: number = array1[0] +>el1 : number +>array1[0] : number +>array1 : [...number[], number] +>0 : 0 + +declare const array2: [...number[], number] +>array2 : [...number[], number] + +const el2: number = array2[1] +>el2 : number +>array2[1] : number | undefined +>array2 : [...number[], number] +>1 : 1 + +declare const array3: [number, ...number[], number] +>array3 : [number, ...number[], number] + +const el3: number = array3[1] +>el3 : number +>array3[1] : number +>array3 : [number, ...number[], number] +>1 : 1 + +declare const array4: [number, ...number[], number] +>array4 : [number, ...number[], number] + +const el4: number = array4[2] +>el4 : number +>array4[2] : number | undefined +>array4 : [number, ...number[], number] +>2 : 2 + diff --git a/tests/baselines/reference/variadicTuples1.errors.txt b/tests/baselines/reference/variadicTuples1.errors.txt index 96f22697f89..0e1af58d56a 100644 --- a/tests/baselines/reference/variadicTuples1.errors.txt +++ b/tests/baselines/reference/variadicTuples1.errors.txt @@ -144,7 +144,7 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab function f1(t: [string, ...T, number], n: number) { const a = t[0]; // string - const b = t[1]; // [string, ...T, number][1] + const b = t[1]; // number | T[number] const c = t[2]; // [string, ...T, number][2] const d = t[n]; // [string, ...T, number][number] } @@ -160,7 +160,7 @@ variadicTuples1.ts(411,7): error TS2322: Type '[boolean, false]' is not assignab function f3(t: [string, ...T, number]) { let [...ax] = t; // [string, ...T, number] let [b1, ...bx] = t; // string, [...T, number] - let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[] + let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[] } // Mapped types applied to variadic tuple types diff --git a/tests/baselines/reference/variadicTuples1.js b/tests/baselines/reference/variadicTuples1.js index d3f69ceae7b..2c966b7dda2 100644 --- a/tests/baselines/reference/variadicTuples1.js +++ b/tests/baselines/reference/variadicTuples1.js @@ -88,7 +88,7 @@ function f0(t: [string, ...T], n: number) { function f1(t: [string, ...T, number], n: number) { const a = t[0]; // string - const b = t[1]; // [string, ...T, number][1] + const b = t[1]; // number | T[number] const c = t[2]; // [string, ...T, number][2] const d = t[n]; // [string, ...T, number][number] } @@ -104,7 +104,7 @@ function f2(t: [string, ...T]) { function f3(t: [string, ...T, number]) { let [...ax] = t; // [string, ...T, number] let [b1, ...bx] = t; // string, [...T, number] - let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[] + let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[] } // Mapped types applied to variadic tuple types @@ -478,7 +478,7 @@ function f0(t, n) { } function f1(t, n) { var a = t[0]; // string - var b = t[1]; // [string, ...T, number][1] + var b = t[1]; // number | T[number] var c = t[2]; // [string, ...T, number][2] var d = t[n]; // [string, ...T, number][number] } @@ -491,7 +491,7 @@ function f2(t) { function f3(t) { var ax = t.slice(0); // [string, ...T, number] var b1 = t[0], bx = t.slice(1); // string, [...T, number] - var c1 = t[0], c2 = t[1], cx = t.slice(2); // string, [string, ...T, number][1], (number | T[number])[] + var c1 = t[0], c2 = t[1], cx = t.slice(2); // string, number | T[number], (number | T[number])[] } var tm1 = fm1([['abc'], [42], [true], ['def']]); // [boolean, string] function gx1(u, v) { diff --git a/tests/baselines/reference/variadicTuples1.symbols b/tests/baselines/reference/variadicTuples1.symbols index 3350c01911b..2d24c7cffd0 100644 --- a/tests/baselines/reference/variadicTuples1.symbols +++ b/tests/baselines/reference/variadicTuples1.symbols @@ -285,7 +285,7 @@ function f1(t: [string, ...T, number], n: number) { >t : Symbol(t, Decl(variadicTuples1.ts, 85, 33)) >0 : Symbol(0) - const b = t[1]; // [string, ...T, number][1] + const b = t[1]; // number | T[number] >b : Symbol(b, Decl(variadicTuples1.ts, 87, 9)) >t : Symbol(t, Decl(variadicTuples1.ts, 85, 33)) @@ -338,7 +338,7 @@ function f3(t: [string, ...T, number]) { >bx : Symbol(bx, Decl(variadicTuples1.ts, 102, 12)) >t : Symbol(t, Decl(variadicTuples1.ts, 100, 33)) - let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[] + let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[] >c1 : Symbol(c1, Decl(variadicTuples1.ts, 103, 9)) >c2 : Symbol(c2, Decl(variadicTuples1.ts, 103, 12)) >cx : Symbol(cx, Decl(variadicTuples1.ts, 103, 16)) diff --git a/tests/baselines/reference/variadicTuples1.types b/tests/baselines/reference/variadicTuples1.types index 842c1ac7b94..f41a817d57e 100644 --- a/tests/baselines/reference/variadicTuples1.types +++ b/tests/baselines/reference/variadicTuples1.types @@ -344,9 +344,9 @@ function f1(t: [string, ...T, number], n: number) { >t : [string, ...T, number] >0 : 0 - const b = t[1]; // [string, ...T, number][1] ->b : [string, ...T, number][1] ->t[1] : [string, ...T, number][1] + const b = t[1]; // number | T[number] +>b : number | T[number] +>t[1] : number | T[number] >t : [string, ...T, number] >1 : 1 @@ -398,9 +398,9 @@ function f3(t: [string, ...T, number]) { >bx : [...T, number] >t : [string, ...T, number] - let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[] + let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[] >c1 : string ->c2 : [string, ...T, number][1] +>c2 : number | T[number] >cx : (number | T[number])[] >t : [string, ...T, number] } diff --git a/tests/cases/compiler/indexedAccessWithVariableElement.ts b/tests/cases/compiler/indexedAccessWithVariableElement.ts new file mode 100644 index 00000000000..da844a1c57d --- /dev/null +++ b/tests/cases/compiler/indexedAccessWithVariableElement.ts @@ -0,0 +1,17 @@ +// @strict: true +// @noUncheckedIndexedAccess: true +// @noEmit: true + +// repro from https://github.com/microsoft/TypeScript/issues/54420 + +declare const array1: [...number[], number] +const el1: number = array1[0] + +declare const array2: [...number[], number] +const el2: number = array2[1] + +declare const array3: [number, ...number[], number] +const el3: number = array3[1] + +declare const array4: [number, ...number[], number] +const el4: number = array4[2] diff --git a/tests/cases/conformance/types/tuple/variadicTuples1.ts b/tests/cases/conformance/types/tuple/variadicTuples1.ts index e851e44d568..573b95b1da9 100644 --- a/tests/cases/conformance/types/tuple/variadicTuples1.ts +++ b/tests/cases/conformance/types/tuple/variadicTuples1.ts @@ -88,7 +88,7 @@ function f0(t: [string, ...T], n: number) { function f1(t: [string, ...T, number], n: number) { const a = t[0]; // string - const b = t[1]; // [string, ...T, number][1] + const b = t[1]; // number | T[number] const c = t[2]; // [string, ...T, number][2] const d = t[n]; // [string, ...T, number][number] } @@ -104,7 +104,7 @@ function f2(t: [string, ...T]) { function f3(t: [string, ...T, number]) { let [...ax] = t; // [string, ...T, number] let [b1, ...bx] = t; // string, [...T, number] - let [c1, c2, ...cx] = t; // string, [string, ...T, number][1], (number | T[number])[] + let [c1, c2, ...cx] = t; // string, number | T[number], (number | T[number])[] } // Mapped types applied to variadic tuple types