diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b3d4256fecfc..0bd0cfdec45a1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26474,6 +26474,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); return key && key + "." + propName; } + 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 + "[" + propertyCacheKey + "]"; + } break; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: @@ -26519,8 +26527,14 @@ 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 { + 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) && (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.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 new file mode 100644 index 0000000000000..6a87a2612d55b --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.js @@ -0,0 +1,114 @@ +//// [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 + } +} + +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"; +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 = {})); +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 new file mode 100644 index 0000000000000..e25a76e0223be --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.symbols @@ -0,0 +1,165 @@ +//// [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, --, --)) + } +} + +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 new file mode 100644 index 0000000000000..907ba3a5851b1 --- /dev/null +++ b/tests/baselines/reference/typeGuardNarrowsIndexedAccessOfAnyProperty.types @@ -0,0 +1,195 @@ +//// [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 + } +} + +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 new file mode 100644 index 0000000000000..d88d491a4e601 --- /dev/null +++ b/tests/cases/compiler/typeGuardNarrowsIndexedAccessOfAnyProperty.ts @@ -0,0 +1,56 @@ +// @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 + } +} + +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 + } + } +}