Skip to content

Commit 40208a8

Browse files
authored
Narrow references within optional chains in for-in loops (microsoft#52059)
1 parent 4775381 commit 40208a8

File tree

6 files changed

+158
-3
lines changed

6 files changed

+158
-3
lines changed

src/compiler/checker.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -26273,7 +26273,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2627326273
return declaredType;
2627426274
}
2627526275
// for (const _ in ref) acts as a nonnull on ref
26276-
if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) {
26276+
if (
26277+
isVariableDeclaration(node) &&
26278+
node.parent.parent.kind === SyntaxKind.ForInStatement &&
26279+
(isMatchingReference(reference, node.parent.parent.expression) || optionalChainContainsReference(node.parent.parent.expression, reference))
26280+
) {
2627726281
return getNonNullableTypeIfNeeded(finalizeEvolvingArrayType(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))));
2627826282
}
2627926283
// Assignment doesn't affect reference

tests/baselines/reference/controlFlowOptionalChain.errors.txt

+18-1
Original file line numberDiff line numberDiff line change
@@ -773,4 +773,21 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error T
773773
console.log("I should ALSO be reachable");
774774
}
775775
}
776-
776+
777+
778+
// Repro from #51941
779+
780+
type Test5 = {
781+
main?: {
782+
childs: Record<string, Test5>;
783+
};
784+
};
785+
786+
function f50(obj: Test5) {
787+
for (const key in obj.main?.childs) {
788+
if (obj.main.childs[key] === obj) {
789+
return obj;
790+
}
791+
}
792+
return null;
793+
}

tests/baselines/reference/controlFlowOptionalChain.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,24 @@ while (arr[i]?.tag === "left") {
588588
console.log("I should ALSO be reachable");
589589
}
590590
}
591-
591+
592+
593+
// Repro from #51941
594+
595+
type Test5 = {
596+
main?: {
597+
childs: Record<string, Test5>;
598+
};
599+
};
600+
601+
function f50(obj: Test5) {
602+
for (const key in obj.main?.childs) {
603+
if (obj.main.childs[key] === obj) {
604+
return obj;
605+
}
606+
}
607+
return null;
608+
}
592609

593610
//// [controlFlowOptionalChain.js]
594611
"use strict";
@@ -1091,3 +1108,12 @@ while (((_u = arr[i]) === null || _u === void 0 ? void 0 : _u.tag) === "left") {
10911108
console.log("I should ALSO be reachable");
10921109
}
10931110
}
1111+
function f50(obj) {
1112+
var _a;
1113+
for (var key in (_a = obj.main) === null || _a === void 0 ? void 0 : _a.childs) {
1114+
if (obj.main.childs[key] === obj) {
1115+
return obj;
1116+
}
1117+
}
1118+
return null;
1119+
}

tests/baselines/reference/controlFlowOptionalChain.symbols

+45
Original file line numberDiff line numberDiff line change
@@ -1832,3 +1832,48 @@ while (arr[i]?.tag === "left") {
18321832
}
18331833
}
18341834

1835+
1836+
// Repro from #51941
1837+
1838+
type Test5 = {
1839+
>Test5 : Symbol(Test5, Decl(controlFlowOptionalChain.ts, 588, 1))
1840+
1841+
main?: {
1842+
>main : Symbol(main, Decl(controlFlowOptionalChain.ts, 593, 14))
1843+
1844+
childs: Record<string, Test5>;
1845+
>childs : Symbol(childs, Decl(controlFlowOptionalChain.ts, 594, 10))
1846+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
1847+
>Test5 : Symbol(Test5, Decl(controlFlowOptionalChain.ts, 588, 1))
1848+
1849+
};
1850+
};
1851+
1852+
function f50(obj: Test5) {
1853+
>f50 : Symbol(f50, Decl(controlFlowOptionalChain.ts, 597, 2))
1854+
>obj : Symbol(obj, Decl(controlFlowOptionalChain.ts, 599, 13))
1855+
>Test5 : Symbol(Test5, Decl(controlFlowOptionalChain.ts, 588, 1))
1856+
1857+
for (const key in obj.main?.childs) {
1858+
>key : Symbol(key, Decl(controlFlowOptionalChain.ts, 600, 13))
1859+
>obj.main?.childs : Symbol(childs, Decl(controlFlowOptionalChain.ts, 594, 10))
1860+
>obj.main : Symbol(main, Decl(controlFlowOptionalChain.ts, 593, 14))
1861+
>obj : Symbol(obj, Decl(controlFlowOptionalChain.ts, 599, 13))
1862+
>main : Symbol(main, Decl(controlFlowOptionalChain.ts, 593, 14))
1863+
>childs : Symbol(childs, Decl(controlFlowOptionalChain.ts, 594, 10))
1864+
1865+
if (obj.main.childs[key] === obj) {
1866+
>obj.main.childs : Symbol(childs, Decl(controlFlowOptionalChain.ts, 594, 10))
1867+
>obj.main : Symbol(main, Decl(controlFlowOptionalChain.ts, 593, 14))
1868+
>obj : Symbol(obj, Decl(controlFlowOptionalChain.ts, 599, 13))
1869+
>main : Symbol(main, Decl(controlFlowOptionalChain.ts, 593, 14))
1870+
>childs : Symbol(childs, Decl(controlFlowOptionalChain.ts, 594, 10))
1871+
>key : Symbol(key, Decl(controlFlowOptionalChain.ts, 600, 13))
1872+
>obj : Symbol(obj, Decl(controlFlowOptionalChain.ts, 599, 13))
1873+
1874+
return obj;
1875+
>obj : Symbol(obj, Decl(controlFlowOptionalChain.ts, 599, 13))
1876+
}
1877+
}
1878+
return null;
1879+
}

tests/baselines/reference/controlFlowOptionalChain.types

+45
Original file line numberDiff line numberDiff line change
@@ -2073,3 +2073,48 @@ while (arr[i]?.tag === "left") {
20732073
}
20742074
}
20752075

2076+
2077+
// Repro from #51941
2078+
2079+
type Test5 = {
2080+
>Test5 : { main?: { childs: Record<string, Test5>; } | undefined; }
2081+
2082+
main?: {
2083+
>main : { childs: Record<string, Test5>; } | undefined
2084+
2085+
childs: Record<string, Test5>;
2086+
>childs : Record<string, Test5>
2087+
2088+
};
2089+
};
2090+
2091+
function f50(obj: Test5) {
2092+
>f50 : (obj: Test5) => Test5 | null
2093+
>obj : Test5
2094+
2095+
for (const key in obj.main?.childs) {
2096+
>key : string
2097+
>obj.main?.childs : Record<string, Test5> | undefined
2098+
>obj.main : { childs: Record<string, Test5>; } | undefined
2099+
>obj : Test5
2100+
>main : { childs: Record<string, Test5>; } | undefined
2101+
>childs : Record<string, Test5> | undefined
2102+
2103+
if (obj.main.childs[key] === obj) {
2104+
>obj.main.childs[key] === obj : boolean
2105+
>obj.main.childs[key] : Test5
2106+
>obj.main.childs : Record<string, Test5>
2107+
>obj.main : { childs: Record<string, Test5>; }
2108+
>obj : Test5
2109+
>main : { childs: Record<string, Test5>; }
2110+
>childs : Record<string, Test5>
2111+
>key : string
2112+
>obj : Test5
2113+
2114+
return obj;
2115+
>obj : Test5
2116+
}
2117+
}
2118+
return null;
2119+
>null : null
2120+
}

tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts

+18
Original file line numberDiff line numberDiff line change
@@ -590,3 +590,21 @@ while (arr[i]?.tag === "left") {
590590
console.log("I should ALSO be reachable");
591591
}
592592
}
593+
594+
595+
// Repro from #51941
596+
597+
type Test5 = {
598+
main?: {
599+
childs: Record<string, Test5>;
600+
};
601+
};
602+
603+
function f50(obj: Test5) {
604+
for (const key in obj.main?.childs) {
605+
if (obj.main.childs[key] === obj) {
606+
return obj;
607+
}
608+
}
609+
return null;
610+
}

0 commit comments

Comments
 (0)