Skip to content

Commit d06df2f

Browse files
committed
Improve narrowing by in operator under noUncheckedIndexedAccess for variable properties
1 parent 1efdcd9 commit d06df2f

3 files changed

Lines changed: 143 additions & 9 deletions

File tree

src/compiler/checker.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27097,8 +27097,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2709727097
case SyntaxKind.NonNullExpression:
2709827098
return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression);
2709927099
case SyntaxKind.BinaryExpression:
27100-
return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) ||
27101-
(isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right));
27100+
if (isAssignmentExpression(target)) {
27101+
return isMatchingReference(source, target.left);
27102+
}
27103+
if (isBinaryExpression(target)) {
27104+
switch (target.operatorToken.kind) {
27105+
case SyntaxKind.CommaToken:
27106+
return isMatchingReference(source, target.right);
27107+
case SyntaxKind.InKeyword:
27108+
return isMatchingElementAccess(source, target.right, target.left);
27109+
}
27110+
}
27111+
return false;
2710227112
}
2710327113
switch (source.kind) {
2710427114
case SyntaxKind.MetaProperty:
@@ -27129,12 +27139,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2712927139
return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression);
2713027140
}
2713127141
}
27132-
if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) {
27133-
const symbol = getResolvedSymbol(source.argumentExpression);
27134-
if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) {
27135-
return isMatchingReference(source.expression, target.expression);
27136-
}
27137-
}
27142+
return isElementAccessExpression(target) && isMatchingElementAccess(source, target.expression, target.argumentExpression);
2713827143
break;
2713927144
case SyntaxKind.QualifiedName:
2714027145
return isAccessExpression(target) &&
@@ -27144,6 +27149,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2714427149
return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target));
2714527150
}
2714627151
return false;
27152+
27153+
function isMatchingElementAccess(source: Node, targetObjNode: Node, targetPropNode: Node): boolean {
27154+
if (!isElementAccessExpression(source) || !isIdentifier(source.argumentExpression) || !isIdentifier(targetPropNode)) {
27155+
return false;
27156+
}
27157+
const symbol = getResolvedSymbol(source.argumentExpression);
27158+
return (symbol === getResolvedSymbol(targetPropNode) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)))
27159+
&& isMatchingReference(source.expression, targetObjNode);
27160+
}
2714727161
}
2714827162

2714927163
function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined {
@@ -28943,7 +28957,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2894328957
const target = getReferenceCandidate(expr.right);
2894428958
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) {
2894528959
const leftType = getTypeOfExpression(expr.left);
28946-
if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) {
28960+
if (
28961+
isTypeUsableAsPropertyName(leftType)
28962+
? getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)
28963+
: isMatchingReference(reference, expr)
28964+
) {
2894728965
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
2894828966
}
2894928967
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts] ////
2+
3+
=== controlFlowComputedPropertyNames2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/61389
5+
6+
const arr: number[] = [1, 2, 3];
7+
>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5))
8+
9+
const idx: number = 2;
10+
>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5))
11+
12+
if (idx in arr) {
13+
>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5))
14+
>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5))
15+
16+
const x: number = arr[idx]; // ok
17+
>x : Symbol(x, Decl(controlFlowComputedPropertyNames2.ts, 5, 7))
18+
>arr : Symbol(arr, Decl(controlFlowComputedPropertyNames2.ts, 2, 5))
19+
>idx : Symbol(idx, Decl(controlFlowComputedPropertyNames2.ts, 3, 5))
20+
}
21+
22+
const map: Record<string, number> = { a: 1 };
23+
>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5))
24+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
25+
>a : Symbol(a, Decl(controlFlowComputedPropertyNames2.ts, 8, 37))
26+
27+
const key: string = "a";
28+
>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5))
29+
30+
if (key in map) {
31+
>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5))
32+
>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5))
33+
34+
const x: number = map[key]; // ok
35+
>x : Symbol(x, Decl(controlFlowComputedPropertyNames2.ts, 11, 7))
36+
>map : Symbol(map, Decl(controlFlowComputedPropertyNames2.ts, 8, 5))
37+
>key : Symbol(key, Decl(controlFlowComputedPropertyNames2.ts, 9, 5))
38+
}
39+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/conformance/controlFlow/controlFlowComputedPropertyNames2.ts] ////
2+
3+
=== controlFlowComputedPropertyNames2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/61389
5+
6+
const arr: number[] = [1, 2, 3];
7+
>arr : number[]
8+
> : ^^^^^^^^
9+
>[1, 2, 3] : number[]
10+
> : ^^^^^^^^
11+
>1 : 1
12+
> : ^
13+
>2 : 2
14+
> : ^
15+
>3 : 3
16+
> : ^
17+
18+
const idx: number = 2;
19+
>idx : number
20+
> : ^^^^^^
21+
>2 : 2
22+
> : ^
23+
24+
if (idx in arr) {
25+
>idx in arr : boolean
26+
> : ^^^^^^^
27+
>idx : number
28+
> : ^^^^^^
29+
>arr : number[]
30+
> : ^^^^^^^^
31+
32+
const x: number = arr[idx]; // ok
33+
>x : number
34+
> : ^^^^^^
35+
>arr[idx] : number
36+
> : ^^^^^^
37+
>arr : number[]
38+
> : ^^^^^^^^
39+
>idx : number
40+
> : ^^^^^^
41+
}
42+
43+
const map: Record<string, number> = { a: 1 };
44+
>map : Record<string, number>
45+
> : ^^^^^^^^^^^^^^^^^^^^^^
46+
>{ a: 1 } : { a: number; }
47+
> : ^^^^^^^^^^^^^^
48+
>a : number
49+
> : ^^^^^^
50+
>1 : 1
51+
> : ^
52+
53+
const key: string = "a";
54+
>key : string
55+
> : ^^^^^^
56+
>"a" : "a"
57+
> : ^^^
58+
59+
if (key in map) {
60+
>key in map : boolean
61+
> : ^^^^^^^
62+
>key : string
63+
> : ^^^^^^
64+
>map : Record<string, number>
65+
> : ^^^^^^^^^^^^^^^^^^^^^^
66+
67+
const x: number = map[key]; // ok
68+
>x : number
69+
> : ^^^^^^
70+
>map[key] : number
71+
> : ^^^^^^
72+
>map : Record<string, number>
73+
> : ^^^^^^^^^^^^^^^^^^^^^^
74+
>key : string
75+
> : ^^^^^^
76+
}
77+

0 commit comments

Comments
 (0)