From 93e532490f83836b5a0ce113a5d8bb933122dc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 20 Jan 2023 00:39:24 +0100 Subject: [PATCH 1/2] Fixed binding element types coming from the out of tuple bounds under `noUncheckedIndexedAccess` --- src/compiler/checker.ts | 10 ++- ...ructureTupleWithVariableElement.errors.txt | 31 +++++++++ ...estructureTupleWithVariableElement.symbols | 57 +++++++++++++++++ .../destructureTupleWithVariableElement.types | 63 +++++++++++++++++++ .../destructureTupleWithVariableElement.ts | 22 +++++++ 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/destructureTupleWithVariableElement.errors.txt create mode 100644 tests/baselines/reference/destructureTupleWithVariableElement.symbols create mode 100644 tests/baselines/reference/destructureTupleWithVariableElement.types create mode 100644 tests/cases/compiler/destructureTupleWithVariableElement.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 717652d1d1770..9a0e9b85b3c73 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22767,7 +22767,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return propType; } if (everyType(type, isTupleType)) { - return mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType); + return mapType(type, t => { + const tupleType = t as TupleTypeReference; + const restType = getRestTypeOfTupleType(tupleType); + if (!restType) { + return undefinedType; + } + const possibleOutOfBounds = index >= tupleType.target.fixedLength + getEndElementCount(tupleType.target, ElementFlags.Fixed); + return compilerOptions.noUncheckedIndexedAccess && possibleOutOfBounds ? getUnionType([restType, undefinedType]) : restType; + }); } return undefined; } diff --git a/tests/baselines/reference/destructureTupleWithVariableElement.errors.txt b/tests/baselines/reference/destructureTupleWithVariableElement.errors.txt new file mode 100644 index 0000000000000..7d6e22c7f3d5e --- /dev/null +++ b/tests/baselines/reference/destructureTupleWithVariableElement.errors.txt @@ -0,0 +1,31 @@ +tests/cases/compiler/destructureTupleWithVariableElement.ts(9,1): error TS18048: 's1' is possibly 'undefined'. +tests/cases/compiler/destructureTupleWithVariableElement.ts(10,1): error TS18048: 's2' is possibly 'undefined'. +tests/cases/compiler/destructureTupleWithVariableElement.ts(18,1): error TS18048: 's5' is possibly 'undefined'. + + +==== tests/cases/compiler/destructureTupleWithVariableElement.ts (3 errors) ==== + // repro from #52302 + + type NonEmptyStringArray = [string, ...Array] + + const strings: NonEmptyStringArray = ['one', 'two'] + const [s0, s1, s2] = strings; + + s0.toUpperCase() + s1.toUpperCase() + ~~ +!!! error TS18048: 's1' is possibly 'undefined'. + s2.toUpperCase() + ~~ +!!! error TS18048: 's2' is possibly 'undefined'. + + declare const strings2: [string, ...Array, string] + + const [s3, s4, s5] = strings2; + + s3.toUpperCase() + s4.toUpperCase() + s5.toUpperCase() + ~~ +!!! error TS18048: 's5' is possibly 'undefined'. + \ No newline at end of file diff --git a/tests/baselines/reference/destructureTupleWithVariableElement.symbols b/tests/baselines/reference/destructureTupleWithVariableElement.symbols new file mode 100644 index 0000000000000..cb9582c0894c0 --- /dev/null +++ b/tests/baselines/reference/destructureTupleWithVariableElement.symbols @@ -0,0 +1,57 @@ +=== tests/cases/compiler/destructureTupleWithVariableElement.ts === +// repro from #52302 + +type NonEmptyStringArray = [string, ...Array] +>NonEmptyStringArray : Symbol(NonEmptyStringArray, Decl(destructureTupleWithVariableElement.ts, 0, 0)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +const strings: NonEmptyStringArray = ['one', 'two'] +>strings : Symbol(strings, Decl(destructureTupleWithVariableElement.ts, 4, 5)) +>NonEmptyStringArray : Symbol(NonEmptyStringArray, Decl(destructureTupleWithVariableElement.ts, 0, 0)) + +const [s0, s1, s2] = strings; +>s0 : Symbol(s0, Decl(destructureTupleWithVariableElement.ts, 5, 7)) +>s1 : Symbol(s1, Decl(destructureTupleWithVariableElement.ts, 5, 10)) +>s2 : Symbol(s2, Decl(destructureTupleWithVariableElement.ts, 5, 14)) +>strings : Symbol(strings, Decl(destructureTupleWithVariableElement.ts, 4, 5)) + +s0.toUpperCase() +>s0.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s0 : Symbol(s0, Decl(destructureTupleWithVariableElement.ts, 5, 7)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + +s1.toUpperCase() +>s1.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s1 : Symbol(s1, Decl(destructureTupleWithVariableElement.ts, 5, 10)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + +s2.toUpperCase() +>s2.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s2 : Symbol(s2, Decl(destructureTupleWithVariableElement.ts, 5, 14)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + +declare const strings2: [string, ...Array, string] +>strings2 : Symbol(strings2, Decl(destructureTupleWithVariableElement.ts, 11, 13)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +const [s3, s4, s5] = strings2; +>s3 : Symbol(s3, Decl(destructureTupleWithVariableElement.ts, 13, 7)) +>s4 : Symbol(s4, Decl(destructureTupleWithVariableElement.ts, 13, 10)) +>s5 : Symbol(s5, Decl(destructureTupleWithVariableElement.ts, 13, 14)) +>strings2 : Symbol(strings2, Decl(destructureTupleWithVariableElement.ts, 11, 13)) + +s3.toUpperCase() +>s3.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s3 : Symbol(s3, Decl(destructureTupleWithVariableElement.ts, 13, 7)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + +s4.toUpperCase() +>s4.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s4 : Symbol(s4, Decl(destructureTupleWithVariableElement.ts, 13, 10)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + +s5.toUpperCase() +>s5.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>s5 : Symbol(s5, Decl(destructureTupleWithVariableElement.ts, 13, 14)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/destructureTupleWithVariableElement.types b/tests/baselines/reference/destructureTupleWithVariableElement.types new file mode 100644 index 0000000000000..1aec5823e2349 --- /dev/null +++ b/tests/baselines/reference/destructureTupleWithVariableElement.types @@ -0,0 +1,63 @@ +=== tests/cases/compiler/destructureTupleWithVariableElement.ts === +// repro from #52302 + +type NonEmptyStringArray = [string, ...Array] +>NonEmptyStringArray : [string, ...string[]] + +const strings: NonEmptyStringArray = ['one', 'two'] +>strings : [string, ...string[]] +>['one', 'two'] : [string, string] +>'one' : "one" +>'two' : "two" + +const [s0, s1, s2] = strings; +>s0 : string +>s1 : string | undefined +>s2 : string | undefined +>strings : [string, ...string[]] + +s0.toUpperCase() +>s0.toUpperCase() : string +>s0.toUpperCase : () => string +>s0 : string +>toUpperCase : () => string + +s1.toUpperCase() +>s1.toUpperCase() : string +>s1.toUpperCase : () => string +>s1 : string | undefined +>toUpperCase : () => string + +s2.toUpperCase() +>s2.toUpperCase() : string +>s2.toUpperCase : () => string +>s2 : string | undefined +>toUpperCase : () => string + +declare const strings2: [string, ...Array, string] +>strings2 : [string, ...string[], string] + +const [s3, s4, s5] = strings2; +>s3 : string +>s4 : string | undefined +>s5 : string | undefined +>strings2 : [string, ...string[], string] + +s3.toUpperCase() +>s3.toUpperCase() : string +>s3.toUpperCase : () => string +>s3 : string +>toUpperCase : () => string + +s4.toUpperCase() +>s4.toUpperCase() : string +>s4.toUpperCase : () => string +>s4 : string +>toUpperCase : () => string + +s5.toUpperCase() +>s5.toUpperCase() : string +>s5.toUpperCase : () => string +>s5 : string | undefined +>toUpperCase : () => string + diff --git a/tests/cases/compiler/destructureTupleWithVariableElement.ts b/tests/cases/compiler/destructureTupleWithVariableElement.ts new file mode 100644 index 0000000000000..44e58afe0fdf2 --- /dev/null +++ b/tests/cases/compiler/destructureTupleWithVariableElement.ts @@ -0,0 +1,22 @@ +// @strict: true +// @noUncheckedIndexedAccess: true +// @noEmit: true + +// repro from #52302 + +type NonEmptyStringArray = [string, ...Array] + +const strings: NonEmptyStringArray = ['one', 'two'] +const [s0, s1, s2] = strings; + +s0.toUpperCase() +s1.toUpperCase() +s2.toUpperCase() + +declare const strings2: [string, ...Array, string] + +const [s3, s4, s5] = strings2; + +s3.toUpperCase() +s4.toUpperCase() +s5.toUpperCase() From 447a09884ef878efe1e27fda7a87f6da2d2fa27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 1 Feb 2023 21:46:25 +0100 Subject: [PATCH 2/2] Move the `getEndElementCount` call into a branch guarded with the `noUncheckedIndexedAccess` check --- src/compiler/checker.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9a0e9b85b3c73..0670304eb96c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22773,8 +22773,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!restType) { return undefinedType; } - const possibleOutOfBounds = index >= tupleType.target.fixedLength + getEndElementCount(tupleType.target, ElementFlags.Fixed); - return compilerOptions.noUncheckedIndexedAccess && possibleOutOfBounds ? getUnionType([restType, undefinedType]) : restType; + if (compilerOptions.noUncheckedIndexedAccess && + index >= tupleType.target.fixedLength + getEndElementCount(tupleType.target, ElementFlags.Fixed)) { + return getUnionType([restType, undefinedType]); + } + return restType; }); } return undefined;