From 44a8d6744ec71db9522b43c900fd9fbf7bbe77c5 Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 22 Sep 2021 20:36:18 +0200 Subject: [PATCH] fix 10530 --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 5 ++ .../reference/controlFlowOptionalChain.types | 4 +- .../reference/incrementOnNullAssertion.types | 10 +-- ...rowingByNonLiteralIndexedAccess.errors.txt | 28 +++++++ .../narrowingByNonLiteralIndexedAccess.js | 31 ++++++++ ...narrowingByNonLiteralIndexedAccess.symbols | 67 ++++++++++++++++ .../narrowingByNonLiteralIndexedAccess.types | 76 +++++++++++++++++++ .../noUncheckedIndexedAccess.errors.txt | 6 +- .../reference/noUncheckedIndexedAccess.types | 2 +- .../narrowingByNonLiteralIndexedAccess.ts | 20 +++++ 11 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 tests/baselines/reference/narrowingByNonLiteralIndexedAccess.errors.txt create mode 100644 tests/baselines/reference/narrowingByNonLiteralIndexedAccess.js create mode 100644 tests/baselines/reference/narrowingByNonLiteralIndexedAccess.symbols create mode 100644 tests/baselines/reference/narrowingByNonLiteralIndexedAccess.types create mode 100644 tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9087823617bca..52e87db08ac36 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -889,7 +889,7 @@ namespace ts { return isDottedName(expr) || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) || isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) - || isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) + || isElementAccessExpression(expr) && ((isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression)) || isIdentifier(expr.argumentExpression)) || isAssignmentExpression(expr) && isNarrowableReference(expr.left); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51c5001204f02..61adc0a6a3887 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -32621,6 +32621,11 @@ namespace ts { case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: + if (isPropertyAccessExpression(left) && + isElementAccessExpression(left.expression) && + isIdentifier(left.expression.expression)) { + return booleanType; + } reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); return booleanType; diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 4e335fca6d287..b144ef2954430 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -2057,11 +2057,11 @@ while (arr[i]?.tag === "left") { if (arr[i]?.tag === "right") { >arr[i]?.tag === "right" : boolean ->arr[i]?.tag : "left" | "right" +>arr[i]?.tag : "left" >arr[i] : { tag: "left" | "right"; } >arr : { tag: "left" | "right"; }[] >i : number ->tag : "left" | "right" +>tag : "left" >"right" : "right" console.log("I should ALSO be reachable"); diff --git a/tests/baselines/reference/incrementOnNullAssertion.types b/tests/baselines/reference/incrementOnNullAssertion.types index 0c7f5340b9f9a..199c4c87050f8 100644 --- a/tests/baselines/reference/incrementOnNullAssertion.types +++ b/tests/baselines/reference/incrementOnNullAssertion.types @@ -27,21 +27,21 @@ if (foo[x] === undefined) { } else { let nu = foo[x] ->nu : number | undefined ->foo[x] : number | undefined +>nu : number +>foo[x] : number >foo : Dictionary >x : "bar" let n = foo[x] ->n : number | undefined ->foo[x] : number | undefined +>n : number +>foo[x] : number >foo : Dictionary >x : "bar" foo[x]!++ >foo[x]!++ : number >foo[x]! : number ->foo[x] : number | undefined +>foo[x] : number >foo : Dictionary >x : "bar" } diff --git a/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.errors.txt b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.errors.txt new file mode 100644 index 0000000000000..6d2f8be9b4791 --- /dev/null +++ b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.errors.txt @@ -0,0 +1,28 @@ +tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts(14,1): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts(17,1): error TS2532: Object is possibly 'undefined'. + + +==== tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts (2 errors) ==== + interface IEye { + visibility: number | undefined + } + + interface IPirate { + hands: number | undefined, + eyes: IEye[] + } + + const pirates: IPirate[] = []; + + const index: number = 1; + + pirates[index].hands++; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + if (pirates[index].hands) pirates[index].hands++; + + pirates[index].eyes[index].visibility++; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + if (pirates[index].eyes[index].visibility) pirates[index].eyes[index].visibility++; + \ No newline at end of file diff --git a/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.js b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.js new file mode 100644 index 0000000000000..0df9d4c4cd672 --- /dev/null +++ b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.js @@ -0,0 +1,31 @@ +//// [narrowingByNonLiteralIndexedAccess.ts] +interface IEye { + visibility: number | undefined +} + +interface IPirate { + hands: number | undefined, + eyes: IEye[] +} + +const pirates: IPirate[] = []; + +const index: number = 1; + +pirates[index].hands++; +if (pirates[index].hands) pirates[index].hands++; + +pirates[index].eyes[index].visibility++; +if (pirates[index].eyes[index].visibility) pirates[index].eyes[index].visibility++; + + +//// [narrowingByNonLiteralIndexedAccess.js] +"use strict"; +var pirates = []; +var index = 1; +pirates[index].hands++; +if (pirates[index].hands) + pirates[index].hands++; +pirates[index].eyes[index].visibility++; +if (pirates[index].eyes[index].visibility) + pirates[index].eyes[index].visibility++; diff --git a/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.symbols b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.symbols new file mode 100644 index 0000000000000..863683a551a75 --- /dev/null +++ b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.symbols @@ -0,0 +1,67 @@ +=== tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts === +interface IEye { +>IEye : Symbol(IEye, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 0)) + + visibility: number | undefined +>visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) +} + +interface IPirate { +>IPirate : Symbol(IPirate, Decl(narrowingByNonLiteralIndexedAccess.ts, 2, 1)) + + hands: number | undefined, +>hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) + + eyes: IEye[] +>eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>IEye : Symbol(IEye, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 0)) +} + +const pirates: IPirate[] = []; +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>IPirate : Symbol(IPirate, Decl(narrowingByNonLiteralIndexedAccess.ts, 2, 1)) + +const index: number = 1; +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) + +pirates[index].hands++; +>pirates[index].hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) + +if (pirates[index].hands) pirates[index].hands++; +>pirates[index].hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) +>pirates[index].hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>hands : Symbol(IPirate.hands, Decl(narrowingByNonLiteralIndexedAccess.ts, 4, 19)) + +pirates[index].eyes[index].visibility++; +>pirates[index].eyes[index].visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) +>pirates[index].eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) + +if (pirates[index].eyes[index].visibility) pirates[index].eyes[index].visibility++; +>pirates[index].eyes[index].visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) +>pirates[index].eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) +>pirates[index].eyes[index].visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) +>pirates[index].eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>pirates : Symbol(pirates, Decl(narrowingByNonLiteralIndexedAccess.ts, 9, 5)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>eyes : Symbol(IPirate.eyes, Decl(narrowingByNonLiteralIndexedAccess.ts, 5, 30)) +>index : Symbol(index, Decl(narrowingByNonLiteralIndexedAccess.ts, 11, 5)) +>visibility : Symbol(IEye.visibility, Decl(narrowingByNonLiteralIndexedAccess.ts, 0, 16)) + diff --git a/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.types b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.types new file mode 100644 index 0000000000000..f3ec3cfef1f16 --- /dev/null +++ b/tests/baselines/reference/narrowingByNonLiteralIndexedAccess.types @@ -0,0 +1,76 @@ +=== tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts === +interface IEye { + visibility: number | undefined +>visibility : number | undefined +} + +interface IPirate { + hands: number | undefined, +>hands : number | undefined + + eyes: IEye[] +>eyes : IEye[] +} + +const pirates: IPirate[] = []; +>pirates : IPirate[] +>[] : never[] + +const index: number = 1; +>index : number +>1 : 1 + +pirates[index].hands++; +>pirates[index].hands++ : number +>pirates[index].hands : number | undefined +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>hands : number | undefined + +if (pirates[index].hands) pirates[index].hands++; +>pirates[index].hands : number | undefined +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>hands : number | undefined +>pirates[index].hands++ : number +>pirates[index].hands : number +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>hands : number + +pirates[index].eyes[index].visibility++; +>pirates[index].eyes[index].visibility++ : number +>pirates[index].eyes[index].visibility : number | undefined +>pirates[index].eyes[index] : IEye +>pirates[index].eyes : IEye[] +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>eyes : IEye[] +>index : number +>visibility : number | undefined + +if (pirates[index].eyes[index].visibility) pirates[index].eyes[index].visibility++; +>pirates[index].eyes[index].visibility : number | undefined +>pirates[index].eyes[index] : IEye +>pirates[index].eyes : IEye[] +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>eyes : IEye[] +>index : number +>visibility : number | undefined +>pirates[index].eyes[index].visibility++ : number +>pirates[index].eyes[index].visibility : number +>pirates[index].eyes[index] : IEye +>pirates[index].eyes : IEye[] +>pirates[index] : IPirate +>pirates : IPirate[] +>index : number +>eyes : IEye[] +>index : number +>visibility : number + diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt index 39a4e9aeb4442..ff6d730d3cdc9 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt +++ b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt @@ -32,8 +32,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(90,7): error TS2322 Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(98,5): error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'. Type 'undefined' is not assignable to type 'string'. -tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is not assignable to type 'string'. - Type 'undefined' is not assignable to type 'string'. +tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'undefined' is not assignable to type 'string'. ==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (31 errors) ==== @@ -201,8 +200,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS232 !!! error TS2322: Type 'undefined' is not assignable to type 'string'. const v: string = myRecord2[key]; // Should error ~ -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. }; \ No newline at end of file diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.types b/tests/baselines/reference/noUncheckedIndexedAccess.types index 9dc76ff0d887a..e472de4eb5482 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.types +++ b/tests/baselines/reference/noUncheckedIndexedAccess.types @@ -412,7 +412,7 @@ const fn3 = (key: Key) => { const v: string = myRecord2[key]; // Should error >v : string ->myRecord2[key] : string | undefined +>myRecord2[key] : undefined >myRecord2 : { [key: string]: string; a: string; b: string; } >key : Key diff --git a/tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts b/tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts new file mode 100644 index 0000000000000..0c3ac462eff37 --- /dev/null +++ b/tests/cases/compiler/narrowingByNonLiteralIndexedAccess.ts @@ -0,0 +1,20 @@ +// @strict: true + +interface IEye { + visibility: number | undefined +} + +interface IPirate { + hands: number | undefined, + eyes: IEye[] +} + +const pirates: IPirate[] = []; + +const index: number = 1; + +pirates[index].hands++; +if (pirates[index].hands) pirates[index].hands++; + +pirates[index].eyes[index].visibility++; +if (pirates[index].eyes[index].visibility) pirates[index].eyes[index].visibility++;