diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7cff9af11039d..613698c4ffd96 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26622,13 +26622,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); case SyntaxKind.QualifiedName: const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); - return left && left + "." + (node as QualifiedName).right.escapedText; + return left && `${left}.${(node as QualifiedName).right.escapedText}`; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const propName = getAccessedPropertyName(node as AccessExpression); if (propName !== undefined) { const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); - return key && key + "." + propName; + return key && `${key}.${propName}`; + } + if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) { + const symbol = getResolvedSymbol(node.argumentExpression); + if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && `${key}.@${getSymbolId(symbol)}`; + } } break; case SyntaxKind.ObjectBindingPattern: @@ -26674,9 +26681,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.PropertyAccessExpression: 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) { + const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; + if (targetPropertyName !== undefined) { + return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + } + } + if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { + const symbol = getResolvedSymbol(source.argumentExpression); + if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) { + return isMatchingReference(source.expression, target.expression); + } + } + break; case SyntaxKind.QualifiedName: return isAccessExpression(target) && (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && diff --git a/tests/baselines/reference/controlFlowComputedPropertyNames.symbols b/tests/baselines/reference/controlFlowComputedPropertyNames.symbols new file mode 100644 index 0000000000000..2d3236aff4621 --- /dev/null +++ b/tests/baselines/reference/controlFlowComputedPropertyNames.symbols @@ -0,0 +1,134 @@ +//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames.ts] //// + +=== controlFlowComputedPropertyNames.ts === +function f1(obj: Record, key: string) { +>f1 : Symbol(f1, Decl(controlFlowComputedPropertyNames.ts, 0, 0)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 0, 12)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 0, 41)) + + if (typeof obj[key] === "string") { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 0, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 0, 41)) + + obj[key].toUpperCase(); +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 0, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 0, 41)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} + +function f2(obj: Record, key: string) { +>f2 : Symbol(f2, Decl(controlFlowComputedPropertyNames.ts, 4, 1)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) +>undefined : Symbol(undefined) + + obj[key].toUpperCase(); +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + let key2 = key + key; +>key2 : Symbol(key2, Decl(controlFlowComputedPropertyNames.ts, 10, 7)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) + + if (obj[key2] !== undefined) { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key2 : Symbol(key2, Decl(controlFlowComputedPropertyNames.ts, 10, 7)) +>undefined : Symbol(undefined) + + obj[key2].toUpperCase(); +>obj[key2].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key2 : Symbol(key2, Decl(controlFlowComputedPropertyNames.ts, 10, 7)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + const key3 = key + key; +>key3 : Symbol(key3, Decl(controlFlowComputedPropertyNames.ts, 14, 9)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 6, 52)) + + if (obj[key3] !== undefined) { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key3 : Symbol(key3, Decl(controlFlowComputedPropertyNames.ts, 14, 9)) +>undefined : Symbol(undefined) + + obj[key3].toUpperCase(); +>obj[key3].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 6, 12)) +>key3 : Symbol(key3, Decl(controlFlowComputedPropertyNames.ts, 14, 9)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} + +type Thing = { a?: string, b?: number, c?: number }; +>Thing : Symbol(Thing, Decl(controlFlowComputedPropertyNames.ts, 18, 1)) +>a : Symbol(a, Decl(controlFlowComputedPropertyNames.ts, 20, 14)) +>b : Symbol(b, Decl(controlFlowComputedPropertyNames.ts, 20, 26)) +>c : Symbol(c, Decl(controlFlowComputedPropertyNames.ts, 20, 38)) + +function f3(obj: Thing, key: keyof Thing) { +>f3 : Symbol(f3, Decl(controlFlowComputedPropertyNames.ts, 20, 52)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>Thing : Symbol(Thing, Decl(controlFlowComputedPropertyNames.ts, 18, 1)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) +>Thing : Symbol(Thing, Decl(controlFlowComputedPropertyNames.ts, 18, 1)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) +>undefined : Symbol(undefined) + + if (typeof obj[key] === "string") { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) + + obj[key].toUpperCase(); +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } + if (typeof obj[key] === "number") { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) + + obj[key].toFixed(); +>obj[key].toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 22, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 22, 23)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + } + } +} + +function f4(obj: Record, key: K) { +>f4 : Symbol(f4, Decl(controlFlowComputedPropertyNames.ts, 31, 1)) +>K : Symbol(K, Decl(controlFlowComputedPropertyNames.ts, 33, 12)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 33, 30)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>K : Symbol(K, Decl(controlFlowComputedPropertyNames.ts, 33, 12)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 33, 65)) +>K : Symbol(K, Decl(controlFlowComputedPropertyNames.ts, 33, 12)) + + if (obj[key]) { +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 33, 30)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 33, 65)) + + obj[key].toUpperCase(); +>obj[key].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(controlFlowComputedPropertyNames.ts, 33, 30)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames.ts, 33, 65)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) + } +} + diff --git a/tests/baselines/reference/controlFlowComputedPropertyNames.types b/tests/baselines/reference/controlFlowComputedPropertyNames.types new file mode 100644 index 0000000000000..6fd96872fa7f3 --- /dev/null +++ b/tests/baselines/reference/controlFlowComputedPropertyNames.types @@ -0,0 +1,163 @@ +//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames.ts] //// + +=== controlFlowComputedPropertyNames.ts === +function f1(obj: Record, key: string) { +>f1 : (obj: Record, key: string) => void +>obj : Record +>key : string + + if (typeof obj[key] === "string") { +>typeof obj[key] === "string" : boolean +>typeof obj[key] : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>obj[key] : unknown +>obj : Record +>key : string +>"string" : "string" + + obj[key].toUpperCase(); +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : Record +>key : string +>toUpperCase : () => string + } +} + +function f2(obj: Record, key: string) { +>f2 : (obj: Record, key: string) => void +>obj : Record +>key : string + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : string | undefined +>obj : Record +>key : string +>undefined : undefined + + obj[key].toUpperCase(); +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : Record +>key : string +>toUpperCase : () => string + } + let key2 = key + key; +>key2 : string +>key + key : string +>key : string +>key : string + + if (obj[key2] !== undefined) { +>obj[key2] !== undefined : boolean +>obj[key2] : string | undefined +>obj : Record +>key2 : string +>undefined : undefined + + obj[key2].toUpperCase(); +>obj[key2].toUpperCase() : string +>obj[key2].toUpperCase : () => string +>obj[key2] : string +>obj : Record +>key2 : string +>toUpperCase : () => string + } + const key3 = key + key; +>key3 : string +>key + key : string +>key : string +>key : string + + if (obj[key3] !== undefined) { +>obj[key3] !== undefined : boolean +>obj[key3] : string | undefined +>obj : Record +>key3 : string +>undefined : undefined + + obj[key3].toUpperCase(); +>obj[key3].toUpperCase() : string +>obj[key3].toUpperCase : () => string +>obj[key3] : string +>obj : Record +>key3 : string +>toUpperCase : () => string + } +} + +type Thing = { a?: string, b?: number, c?: number }; +>Thing : { a?: string | undefined; b?: number | undefined; c?: number | undefined; } +>a : string | undefined +>b : number | undefined +>c : number | undefined + +function f3(obj: Thing, key: keyof Thing) { +>f3 : (obj: Thing, key: keyof Thing) => void +>obj : Thing +>key : keyof Thing + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : string | number | undefined +>obj : Thing +>key : keyof Thing +>undefined : undefined + + if (typeof obj[key] === "string") { +>typeof obj[key] === "string" : boolean +>typeof obj[key] : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>obj[key] : string | number +>obj : Thing +>key : keyof Thing +>"string" : "string" + + obj[key].toUpperCase(); +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : Thing +>key : keyof Thing +>toUpperCase : () => string + } + if (typeof obj[key] === "number") { +>typeof obj[key] === "number" : boolean +>typeof obj[key] : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>obj[key] : string | number +>obj : Thing +>key : keyof Thing +>"number" : "number" + + obj[key].toFixed(); +>obj[key].toFixed() : string +>obj[key].toFixed : (fractionDigits?: number | undefined) => string +>obj[key] : number +>obj : Thing +>key : keyof Thing +>toFixed : (fractionDigits?: number | undefined) => string + } + } +} + +function f4(obj: Record, key: K) { +>f4 : (obj: Record, key: K) => void +>obj : Record +>key : K + + if (obj[key]) { +>obj[key] : Record[K] +>obj : Record +>key : K + + obj[key].toUpperCase(); +>obj[key].toUpperCase() : string +>obj[key].toUpperCase : () => string +>obj[key] : string +>obj : Record +>key : K +>toUpperCase : () => string + } +} + 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/cases/conformance/controlFlow/controlFlowComputedPropertyNames.ts b/tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames.ts new file mode 100644 index 0000000000000..5c29e86f97d3f --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames.ts @@ -0,0 +1,41 @@ +// @strict: true +// @noEmit: true + +function f1(obj: Record, key: string) { + if (typeof obj[key] === "string") { + obj[key].toUpperCase(); + } +} + +function f2(obj: Record, key: string) { + if (obj[key] !== undefined) { + obj[key].toUpperCase(); + } + let key2 = key + key; + if (obj[key2] !== undefined) { + obj[key2].toUpperCase(); + } + const key3 = key + key; + if (obj[key3] !== undefined) { + obj[key3].toUpperCase(); + } +} + +type Thing = { a?: string, b?: number, c?: number }; + +function f3(obj: Thing, key: keyof Thing) { + if (obj[key] !== undefined) { + if (typeof obj[key] === "string") { + obj[key].toUpperCase(); + } + if (typeof obj[key] === "number") { + obj[key].toFixed(); + } + } +} + +function f4(obj: Record, key: K) { + if (obj[key]) { + obj[key].toUpperCase(); + } +}