diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index caab75c99fa8a..c3542de04d819 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27097,8 +27097,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.NonNullExpression: return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: - return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || - (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); + if (isAssignmentExpression(target)) { + return isMatchingReference(source, target.left); + } + if (isBinaryExpression(target)) { + switch (target.operatorToken.kind) { + case SyntaxKind.CommaToken: + return isMatchingReference(source, target.right); + case SyntaxKind.InKeyword: + return isMatchingElementAccess(source, target.right, target.left); + } + } + return false; } switch (source.kind) { case SyntaxKind.MetaProperty: @@ -27129,12 +27139,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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); - } - } + return isElementAccessExpression(target) && isMatchingElementAccess(source, target.expression, target.argumentExpression); break; case SyntaxKind.QualifiedName: return isAccessExpression(target) && @@ -27144,6 +27149,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); } return false; + + function isMatchingElementAccess(source: Node, targetObjNode: Node, targetPropNode: Node): boolean { + if (!isElementAccessExpression(source) || !isIdentifier(source.argumentExpression) || !isIdentifier(targetPropNode)) { + return false; + } + const symbol = getResolvedSymbol(source.argumentExpression); + return (symbol === getResolvedSymbol(targetPropNode) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) + && isMatchingReference(source.expression, targetObjNode); + } } function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { @@ -28943,7 +28957,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const target = getReferenceCandidate(expr.right); if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) { const leftType = getTypeOfExpression(expr.left); - if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) { + if ( + isTypeUsableAsPropertyName(leftType) + ? getAccessedPropertyName(reference) === getPropertyNameFromType(leftType) + : isMatchingReference(reference, expr) + ) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } } diff --git a/tests/baselines/reference/controlFlowComputedPropertyNames2.symbols b/tests/baselines/reference/controlFlowComputedPropertyNames2.symbols new file mode 100644 index 0000000000000..32bb4e796c6b4 --- /dev/null +++ b/tests/baselines/reference/controlFlowComputedPropertyNames2.symbols @@ -0,0 +1,39 @@ +//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts] //// + +=== controlFlowComputedPropertyNames2.ts === +// https://github.com/microsoft/TypeScript/issues/61389 + +const arr: number[] = [1, 2, 3]; +>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5)) + +const idx: number = 2; +>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5)) + +if (idx in arr) { +>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5)) +>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5)) + + const x: number = arr[idx]; // ok +>x : Symbol(x, Decl(controlFlowComputedPropertyNames2.ts, 5, 7)) +>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5)) +>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5)) +} + +const map: Record = { a: 1 }; +>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(controlFlowComputedPropertyNames2.ts, 8, 37)) + +const key: string = "a"; +>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5)) + +if (key in map) { +>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5)) +>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5)) + + const x: number = map[key]; // ok +>x : Symbol(x, Decl(controlFlowComputedPropertyNames2.ts, 11, 7)) +>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5)) +>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5)) +} + diff --git a/tests/baselines/reference/controlFlowComputedPropertyNames2.types b/tests/baselines/reference/controlFlowComputedPropertyNames2.types new file mode 100644 index 0000000000000..fd358950403d8 --- /dev/null +++ b/tests/baselines/reference/controlFlowComputedPropertyNames2.types @@ -0,0 +1,77 @@ +//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts] //// + +=== controlFlowComputedPropertyNames2.ts === +// https://github.com/microsoft/TypeScript/issues/61389 + +const arr: number[] = [1, 2, 3]; +>arr : number[] +> : ^^^^^^^^ +>[1, 2, 3] : number[] +> : ^^^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +>3 : 3 +> : ^ + +const idx: number = 2; +>idx : number +> : ^^^^^^ +>2 : 2 +> : ^ + +if (idx in arr) { +>idx in arr : boolean +> : ^^^^^^^ +>idx : number +> : ^^^^^^ +>arr : number[] +> : ^^^^^^^^ + + const x: number = arr[idx]; // ok +>x : number +> : ^^^^^^ +>arr[idx] : number +> : ^^^^^^ +>arr : number[] +> : ^^^^^^^^ +>idx : number +> : ^^^^^^ +} + +const map: Record = { a: 1 }; +>map : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>{ a: 1 } : { a: number; } +> : ^^^^^^^^^^^^^^ +>a : number +> : ^^^^^^ +>1 : 1 +> : ^ + +const key: string = "a"; +>key : string +> : ^^^^^^ +>"a" : "a" +> : ^^^ + +if (key in map) { +>key in map : boolean +> : ^^^^^^^ +>key : string +> : ^^^^^^ +>map : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ + + const x: number = map[key]; // ok +>x : number +> : ^^^^^^ +>map[key] : number +> : ^^^^^^ +>map : Record +> : ^^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts b/tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts new file mode 100644 index 0000000000000..7e25b3bba6d3e --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts @@ -0,0 +1,17 @@ +// @strict: true +// @noUncheckedIndexedAccess: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/61389 + +const arr: number[] = [1, 2, 3]; +const idx: number = 2; +if (idx in arr) { + const x: number = arr[idx]; // ok +} + +const map: Record = { a: 1 }; +const key: string = "a"; +if (key in map) { + const x: number = map[key]; // ok +}