From 58a36936fd1f9b545b2ee75cb47f865337508957 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sat, 3 Feb 2024 00:40:57 +0800 Subject: [PATCH 1/2] Narrow object property when key is a variable --- src/compiler/checker.ts | 21 ++++- .../mappedTypeGenericIndexedAccess.types | 8 +- .../noUncheckedIndexedAccess.errors.txt | 6 +- .../reference/noUncheckedIndexedAccess.types | 2 +- ...eGuardNarrowsIndexedAccessOfAnyProperty.js | 57 +++++++++++ ...dNarrowsIndexedAccessOfAnyProperty.symbols | 87 +++++++++++++++++ ...ardNarrowsIndexedAccessOfAnyProperty.types | 94 +++++++++++++++++++ ...eGuardNarrowsIndexedAccessOfAnyProperty.ts | 25 +++++ 8 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js create mode 100644 tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols create mode 100644 tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types create mode 100644 tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b3d4256fecfc..7577bdadf21f7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26474,6 +26474,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); return key && key + "." + propName; } + else if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression) && isConstantReference(node.argumentExpression)) { + const propertySymbol = getResolvedSymbol(node.argumentExpression); + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && key + "." + getSymbolId(propertySymbol); + } break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: @@ -26519,8 +26524,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.ElementAccessExpression: const sourcePropertyName = getAccessedPropertyName(source as AccessExpression); const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; - return sourcePropertyName !== undefined && targetPropertyName !== undefined && targetPropertyName === sourcePropertyName && - isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + if (sourcePropertyName !== undefined || targetPropertyName !== undefined) { + return sourcePropertyName !== undefined && targetPropertyName !== undefined && targetPropertyName === sourcePropertyName && + isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + } + else { + if ( + isElementAccessExpression(source) && isElementAccessExpression(target) + && isConstantReference(source.argumentExpression) && isConstantReference(target.argumentExpression) + ) { + return isMatchingReference(source.expression, target.expression) && + isMatchingReference(source.argumentExpression, target.argumentExpression); + } + return false; + } case SyntaxKind.QualifiedName: return isAccessExpression(target) && (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && diff --git a/tests/baselines/reference/mappedTypeGenericIndexedAccess.types b/tests/baselines/reference/mappedTypeGenericIndexedAccess.types index 75dd61b90a26e..cb4107f6d38a3 100644 --- a/tests/baselines/reference/mappedTypeGenericIndexedAccess.types +++ b/tests/baselines/reference/mappedTypeGenericIndexedAccess.types @@ -60,14 +60,14 @@ class Test { >[] : never[] } this.entries[name]?.push(entry); ->this.entries[name]?.push(entry) : number | undefined ->this.entries[name]?.push : ((...items: Types[T][]) => number) | undefined ->this.entries[name] : Types[T][] | undefined +>this.entries[name]?.push(entry) : number +>this.entries[name]?.push : (...items: Types[T][]) => number +>this.entries[name] : Types[T][] >this.entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } >this : this >entries : { first?: { a1: true; }[] | undefined; second?: { a2: true; }[] | undefined; third?: { a3: true; }[] | undefined; } >name : T ->push : ((...items: Types[T][]) => number) | undefined +>push : (...items: Types[T][]) => number >entry : Types[T] } } diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt index 5e3da68cf77f1..1199c0dbb17d1 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt +++ b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt @@ -33,8 +33,7 @@ noUncheckedIndexedAccess.ts(90,7): error TS2322: Type 'string | undefined' is no Type 'undefined' is not assignable to type 'string'. 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'. -noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is not assignable to type 'string'. - Type 'undefined' is not assignable to type 'string'. +noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'undefined' is not assignable to type 'string'. ==== noUncheckedIndexedAccess.ts (31 errors) ==== @@ -203,8 +202,7 @@ noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is n !!! 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 54fad592dfc87..c6fffb01a5f50 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/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js new file mode 100644 index 0000000000000..2445765fd07be --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js @@ -0,0 +1,57 @@ +//// [tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts] //// + +//// [typeGuardNarrowsIndexedAccessOfAnyProperty.ts] +namespace Problem1 { + declare const obj: { [key: string]: string | undefined }; + declare let key: "a"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem2 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem3 { + declare const obj: { a?: string, b?: string }; + declare const key: "a" | "b"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem4 { + function f(obj: { [P in K]?: string }, k: K) { + const key: K = k; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } +} + +//// [typeGuardNarrowsIndexedAccessOfAnyProperty.js] +"use strict"; +var Problem1; +(function (Problem1) { + if (obj[key]) { + obj[key].toUpperCase(); + } // should Ok +})(Problem1 || (Problem1 = {})); +var Problem2; +(function (Problem2) { + if (obj[key]) { + obj[key].toUpperCase(); + } // should Ok +})(Problem2 || (Problem2 = {})); +var Problem3; +(function (Problem3) { + if (obj[key]) { + obj[key].toUpperCase(); + } // should Ok +})(Problem3 || (Problem3 = {})); +var Problem4; +(function (Problem4) { + function f(obj, k) { + var key = k; + if (obj[key]) { + obj[key].toUpperCase(); + } // should Ok + } +})(Problem4 || (Problem4 = {})); diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols new file mode 100644 index 0000000000000..4146b0b29bf75 --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols @@ -0,0 +1,87 @@ +//// [tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts] //// + +=== typeGuardNarrowsIndexedAccessOfAnyProperty.ts === +namespace Problem1 { +>Problem1 : Symbol(Problem1, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 0, 0)) + + declare const obj: { [key: string]: string | undefined }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 1, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 1, 24)) + + declare let key: "a"; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 2, 13)) + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 1, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 2, 13)) +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 1, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 2, 13)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +} + +namespace Problem2 { +>Problem2 : Symbol(Problem2, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 4, 1)) + + declare const obj: { [key: string]: string | undefined }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 7, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 7, 24)) + + declare const key: string; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 8, 15)) + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 7, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 8, 15)) +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 7, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 8, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +} + +namespace Problem3 { +>Problem3 : Symbol(Problem3, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 10, 1)) + + declare const obj: { a?: string, b?: string }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 13, 15)) +>a : Symbol(a, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 13, 22)) +>b : Symbol(b, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 13, 34)) + + declare const key: "a" | "b"; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 14, 15)) + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 13, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 14, 15)) +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 13, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 14, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +} + +namespace Problem4 { +>Problem4 : Symbol(Problem4, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 16, 1)) + + function f(obj: { [P in K]?: string }, k: K) { +>f : Symbol(f, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 18, 20)) +>K : Symbol(K, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 13)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 31)) +>P : Symbol(P, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 39)) +>K : Symbol(K, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 13)) +>k : Symbol(k, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 58)) +>K : Symbol(K, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 13)) + + const key: K = k; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 20, 9)) +>K : Symbol(K, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 13)) +>k : Symbol(k, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 58)) + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 31)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 20, 9)) +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 19, 31)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 20, 9)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types new file mode 100644 index 0000000000000..b809dc5ab9905 --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types @@ -0,0 +1,94 @@ +//// [tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts] //// + +=== typeGuardNarrowsIndexedAccessOfAnyProperty.ts === +namespace Problem1 { +>Problem1 : typeof Problem1 + + declare const obj: { [key: string]: string | undefined }; +>obj : { [key: string]: string | undefined; } +>key : string + + declare let key: "a"; +>key : "a" + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : "a" +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { [key: string]: string | undefined; } +>key : "a" +>toUpperCase : () => string +} + +namespace Problem2 { +>Problem2 : typeof Problem2 + + declare const obj: { [key: string]: string | undefined }; +>obj : { [key: string]: string | undefined; } +>key : string + + declare const key: string; +>key : string + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { [key: string]: string | undefined; } +>key : string +>toUpperCase : () => string +} + +namespace Problem3 { +>Problem3 : typeof Problem3 + + declare const obj: { a?: string, b?: string }; +>obj : { a?: string | undefined; b?: string | undefined; } +>a : string | undefined +>b : string | undefined + + declare const key: "a" | "b"; +>key : "a" | "b" + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj[key] : string | undefined +>obj : { a?: string | undefined; b?: string | undefined; } +>key : "a" | "b" +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { a?: string | undefined; b?: string | undefined; } +>key : "a" | "b" +>toUpperCase : () => string +} + +namespace Problem4 { +>Problem4 : typeof Problem4 + + function f(obj: { [P in K]?: string }, k: K) { +>f : (obj: { [P in K]?: string | undefined; }, k: K) => void +>obj : { [P in K]?: string | undefined; } +>k : K + + const key: K = k; +>key : K +>k : K + + if (obj[key]) { obj[key].toUpperCase() } // should Ok +>obj[key] : { [P in K]?: string | undefined; }[K] +>obj : { [P in K]?: string | undefined; } +>key : K +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { [P in K]?: string | undefined; } +>key : K +>toUpperCase : () => string + } +} diff --git a/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts b/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts new file mode 100644 index 0000000000000..63e1b3e8b2342 --- /dev/null +++ b/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts @@ -0,0 +1,25 @@ +// @strict: true +namespace Problem1 { + declare const obj: { [key: string]: string | undefined }; + declare let key: "a"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem2 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem3 { + declare const obj: { a?: string, b?: string }; + declare const key: "a" | "b"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok +} + +namespace Problem4 { + function f(obj: { [P in K]?: string }, k: K) { + const key: K = k; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } +} \ No newline at end of file From cf23b47694cb2af6a2e0edb9c82877715513af89 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sun, 4 Feb 2024 01:02:31 +0800 Subject: [PATCH 2/2] reorder comprisions and handle flow cache key --- src/compiler/checker.ts | 19 ++-- ...rrowsIndexedAccessOfAnyProperty.errors.txt | 62 +++++++++++ ...eGuardNarrowsIndexedAccessOfAnyProperty.js | 59 +++++++++- ...dNarrowsIndexedAccessOfAnyProperty.symbols | 78 ++++++++++++++ ...ardNarrowsIndexedAccessOfAnyProperty.types | 101 ++++++++++++++++++ ...eGuardNarrowsIndexedAccessOfAnyProperty.ts | 33 +++++- 6 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7577bdadf21f7..0bd0cfdec45a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26474,10 +26474,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); return key && key + "." + propName; } - else if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression) && isConstantReference(node.argumentExpression)) { - const propertySymbol = getResolvedSymbol(node.argumentExpression); + else if (isElementAccessExpression(node) && isConstantReference(node.argumentExpression)) { + const propertyCacheKey = getFlowCacheKey(node.argumentExpression, unknownType, unknownType, flowContainer); + if (!propertyCacheKey) { + return undefined; + } const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); - return key && key + "." + getSymbolId(propertySymbol); + return key && key + "[" + propertyCacheKey + "]"; } break; case SyntaxKind.ObjectBindingPattern: @@ -26529,14 +26532,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); } else { - if ( - isElementAccessExpression(source) && isElementAccessExpression(target) - && isConstantReference(source.argumentExpression) && isConstantReference(target.argumentExpression) - ) { - return isMatchingReference(source.expression, target.expression) && - isMatchingReference(source.argumentExpression, target.argumentExpression); - } - return false; + return isElementAccessExpression(source) && isElementAccessExpression(target) && isMatchingReference(source.expression, target.expression) && + isMatchingReference(source.argumentExpression, target.argumentExpression) && isConstantReference(source.argumentExpression) && isConstantReference(target.argumentExpression); } case SyntaxKind.QualifiedName: return isAccessExpression(target) && diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.errors.txt b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.errors.txt new file mode 100644 index 0000000000000..972cbd25a36a7 --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.errors.txt @@ -0,0 +1,62 @@ +typeGuardNarrowsIndexedAccessOfAnyProperty.ts(51,7): error TS2532: Object is possibly 'undefined'. + + +==== typeGuardNarrowsIndexedAccessOfAnyProperty.ts (1 errors) ==== + namespace Problem1 { + declare const obj: { [key: string]: string | undefined }; + declare let key: "a"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } + + namespace Problem2 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } + + namespace Problem3 { + declare const obj: { a?: string, b?: string }; + declare const key: "a" | "b"; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } + + namespace Problem4 { + function f(obj: { [P in K]?: string }, k: K) { + const key: K = k; + if (obj[key]) { obj[key].toUpperCase() } // should Ok + } + } + + namespace Problem5 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should Ok + } + } + } + + namespace Problem6 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + while(!!true) { + if (obj[key]) { + obj[key].toUpperCase() // should Ok + } + } + } + + namespace Problem7 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should error + ~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + obj[key] = undefined + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js index 2445765fd07be..6a87a2612d55b 100644 --- a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js @@ -24,7 +24,39 @@ namespace Problem4 { const key: K = k; if (obj[key]) { obj[key].toUpperCase() } // should Ok } -} +} + +namespace Problem5 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should Ok + } + } +} + +namespace Problem6 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + while(!!true) { + if (obj[key]) { + obj[key].toUpperCase() // should Ok + } + } +} + +namespace Problem7 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should error + obj[key] = undefined + } + } +} + //// [typeGuardNarrowsIndexedAccessOfAnyProperty.js] "use strict"; @@ -55,3 +87,28 @@ var Problem4; } // should Ok } })(Problem4 || (Problem4 = {})); +var Problem5; +(function (Problem5) { + if (obj[key]) { + while (!!true) { + obj[key].toUpperCase(); // should Ok + } + } +})(Problem5 || (Problem5 = {})); +var Problem6; +(function (Problem6) { + while (!!true) { + if (obj[key]) { + obj[key].toUpperCase(); // should Ok + } + } +})(Problem6 || (Problem6 = {})); +var Problem7; +(function (Problem7) { + if (obj[key]) { + while (!!true) { + obj[key].toUpperCase(); // should error + obj[key] = undefined; + } + } +})(Problem7 || (Problem7 = {})); diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols index 4146b0b29bf75..e25a76e0223be 100644 --- a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols @@ -85,3 +85,81 @@ namespace Problem4 { >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) } } + +namespace Problem5 { +>Problem5 : Symbol(Problem5, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 23, 1)) + + declare const obj: { [key: string]: string | undefined }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 26, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 26, 24)) + + declare const key: string; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 27, 15)) + + if (obj[key]) { +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 26, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 27, 15)) + + while(!!true) { + obj[key].toUpperCase() // should Ok +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 26, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 27, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + } +} + +namespace Problem6 { +>Problem6 : Symbol(Problem6, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 33, 1)) + + declare const obj: { [key: string]: string | undefined }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 36, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 36, 24)) + + declare const key: string; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 37, 15)) + + while(!!true) { + if (obj[key]) { +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 36, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 37, 15)) + + obj[key].toUpperCase() // should Ok +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 36, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 37, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + } +} + +namespace Problem7 { +>Problem7 : Symbol(Problem7, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 43, 1)) + + declare const obj: { [key: string]: string | undefined }; +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 46, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 46, 24)) + + declare const key: string; +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 47, 15)) + + if (obj[key]) { +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 46, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 47, 15)) + + while(!!true) { + obj[key].toUpperCase() // should error +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 46, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 47, 15)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + + obj[key] = undefined +>obj : Symbol(obj, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 46, 15)) +>key : Symbol(key, Decl(typeGuardNarrowsIndexedAccessOfAnyProperty.ts, 47, 15)) +>undefined : Symbol(undefined) + } + } +} + diff --git a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types index b809dc5ab9905..907ba3a5851b1 100644 --- a/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types @@ -92,3 +92,104 @@ namespace Problem4 { >toUpperCase : () => string } } + +namespace Problem5 { +>Problem5 : typeof Problem5 + + declare const obj: { [key: string]: string | undefined }; +>obj : { [key: string]: string | undefined; } +>key : string + + declare const key: string; +>key : string + + if (obj[key]) { +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string + + while(!!true) { +>!!true : true +>!true : false +>true : true + + obj[key].toUpperCase() // should Ok +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { [key: string]: string | undefined; } +>key : string +>toUpperCase : () => string + } + } +} + +namespace Problem6 { +>Problem6 : typeof Problem6 + + declare const obj: { [key: string]: string | undefined }; +>obj : { [key: string]: string | undefined; } +>key : string + + declare const key: string; +>key : string + + while(!!true) { +>!!true : true +>!true : false +>true : true + + if (obj[key]) { +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string + + obj[key].toUpperCase() // should Ok +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : { [key: string]: string | undefined; } +>key : string +>toUpperCase : () => string + } + } +} + +namespace Problem7 { +>Problem7 : typeof Problem7 + + declare const obj: { [key: string]: string | undefined }; +>obj : { [key: string]: string | undefined; } +>key : string + + declare const key: string; +>key : string + + if (obj[key]) { +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string + + while(!!true) { +>!!true : true +>!true : false +>true : true + + obj[key].toUpperCase() // should error +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string +>toUpperCase : () => string + + obj[key] = undefined +>obj[key] = undefined : undefined +>obj[key] : string | undefined +>obj : { [key: string]: string | undefined; } +>key : string +>undefined : undefined + } + } +} + diff --git a/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts b/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts index 63e1b3e8b2342..d88d491a4e601 100644 --- a/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts +++ b/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts @@ -22,4 +22,35 @@ namespace Problem4 { const key: K = k; if (obj[key]) { obj[key].toUpperCase() } // should Ok } -} \ No newline at end of file +} + +namespace Problem5 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should Ok + } + } +} + +namespace Problem6 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + while(!!true) { + if (obj[key]) { + obj[key].toUpperCase() // should Ok + } + } +} + +namespace Problem7 { + declare const obj: { [key: string]: string | undefined }; + declare const key: string; + if (obj[key]) { + while(!!true) { + obj[key].toUpperCase() // should error + obj[key] = undefined + } + } +}