Skip to content

Commit 4095602

Browse files
committed
Merge pull request #8849 from Microsoft/outerControlFlows
Improve control flow analysis in function expressions
2 parents 5c095a8 + 41446fe commit 4095602

16 files changed

+970
-222
lines changed

src/compiler/binder.ts

Lines changed: 163 additions & 169 deletions
Large diffs are not rendered by default.

src/compiler/checker.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7645,7 +7645,7 @@ namespace ts {
76457645
getInitialTypeOfBindingElement(<BindingElement>node);
76467646
}
76477647

7648-
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) {
7648+
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
76497649
let key: string;
76507650
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
76517651
return declaredType;
@@ -7691,15 +7691,21 @@ namespace ts {
76917691
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
76927692
getTypeAtFlowLoopLabel(<FlowLabel>flow);
76937693
}
7694-
else if (flow.flags & FlowFlags.Unreachable) {
7694+
else if (flow.flags & FlowFlags.Start) {
7695+
// Check if we should continue with the control flow of the containing function.
7696+
const container = (<FlowStart>flow).container;
7697+
if (container && includeOuterFunctions) {
7698+
flow = container.flowNode;
7699+
continue;
7700+
}
7701+
// At the top of the flow we have the initial type.
7702+
type = initialType;
7703+
}
7704+
else {
76957705
// Unreachable code errors are reported in the binding phase. Here we
76967706
// simply return the declared type to reduce follow-on errors.
76977707
type = declaredType;
76987708
}
7699-
else {
7700-
// At the top of the flow we have the initial type.
7701-
type = initialType;
7702-
}
77037709
if (flow.flags & FlowFlags.Shared) {
77047710
// Record visited node and the associated type in the cache.
77057711
visitedFlowNodes[visitedFlowCount] = flow;
@@ -8068,6 +8074,17 @@ namespace ts {
80688074
return expression;
80698075
}
80708076

8077+
function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
8078+
const declarationContainer = getContainingFunctionOrModule(declaration);
8079+
let container = getContainingFunctionOrModule(reference);
8080+
while (container !== declarationContainer &&
8081+
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
8082+
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
8083+
container = getContainingFunctionOrModule(container);
8084+
}
8085+
return container === declarationContainer;
8086+
}
8087+
80718088
function checkIdentifier(node: Identifier): Type {
80728089
const symbol = getResolvedSymbol(node);
80738090

@@ -8124,10 +8141,11 @@ namespace ts {
81248141
return type;
81258142
}
81268143
const declaration = localOrExportSymbol.valueDeclaration;
8144+
const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
81278145
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
81288146
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
8129-
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
8130-
const flowType = getFlowTypeOfReference(node, type, assumeInitialized);
8147+
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
8148+
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
81318149
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
81328150
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
81338151
// Return the declared type to reduce follow-on errors
@@ -8376,7 +8394,7 @@ namespace ts {
83768394
if (isClassLike(container.parent)) {
83778395
const symbol = getSymbolOfNode(container.parent);
83788396
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
8379-
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true);
8397+
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true);
83808398
}
83818399

83828400
if (isInJavaScriptFile(node)) {
@@ -8661,20 +8679,6 @@ namespace ts {
86618679
return undefined;
86628680
}
86638681

8664-
function getImmediatelyInvokedFunctionExpression(func: FunctionExpression | MethodDeclaration) {
8665-
if (isFunctionExpressionOrArrowFunction(func)) {
8666-
let prev: Node = func;
8667-
let parent: Node = func.parent;
8668-
while (parent.kind === SyntaxKind.ParenthesizedExpression) {
8669-
prev = parent;
8670-
parent = parent.parent;
8671-
}
8672-
if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) {
8673-
return parent as CallExpression;
8674-
}
8675-
}
8676-
}
8677-
86788682
// In a variable, parameter or property declaration with a type annotation,
86798683
// the contextual type of an initializer expression is the type of the variable, parameter or property.
86808684
// Otherwise, in a parameter declaration of a contextually typed function expression,
@@ -9988,7 +9992,7 @@ namespace ts {
99889992
return propType;
99899993
}
99909994
}
9991-
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true);
9995+
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
99929996
}
99939997

99949998
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {

src/compiler/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ namespace ts {
416416

417417
ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn,
418418
EmitHelperFlags = HasClassExtends | HasDecorators | HasParamDecorators | HasAsyncFunctions,
419+
ReachabilityAndEmitFlags = ReachabilityCheckFlags | EmitHelperFlags,
419420

420421
// Parsing context flags
421422
ContextFlags = DisallowInContext | YieldContext | DecoratorContext | AwaitContext | JavaScriptFile,
@@ -1537,6 +1538,13 @@ namespace ts {
15371538
id?: number; // Node id used by flow type cache in checker
15381539
}
15391540

1541+
// FlowStart represents the start of a control flow. For a function expression or arrow
1542+
// function, the container property references the function (which in turn has a flowNode
1543+
// property for the containing control flow).
1544+
export interface FlowStart extends FlowNode {
1545+
container?: FunctionExpression | ArrowFunction;
1546+
}
1547+
15401548
// FlowLabel represents a junction with multiple possible preceding control flows.
15411549
export interface FlowLabel extends FlowNode {
15421550
antecedents: FlowNode[];

src/compiler/utilities.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,20 @@ namespace ts {
987987
}
988988
}
989989

990+
export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression {
991+
if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) {
992+
let prev = func;
993+
let parent = func.parent;
994+
while (parent.kind === SyntaxKind.ParenthesizedExpression) {
995+
prev = parent;
996+
parent = parent.parent;
997+
}
998+
if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) {
999+
return parent as CallExpression;
1000+
}
1001+
}
1002+
}
1003+
9901004
/**
9911005
* Determines whether a node is a property or element access expression for super.
9921006
*/
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//// [constLocalsInFunctionExpressions.ts]
2+
declare function getStringOrNumber(): string | number;
3+
4+
function f1() {
5+
const x = getStringOrNumber();
6+
if (typeof x === "string") {
7+
const f = () => x.length;
8+
}
9+
}
10+
11+
function f2() {
12+
const x = getStringOrNumber();
13+
if (typeof x !== "string") {
14+
return;
15+
}
16+
const f = () => x.length;
17+
}
18+
19+
function f3() {
20+
const x = getStringOrNumber();
21+
if (typeof x === "string") {
22+
const f = function() { return x.length; };
23+
}
24+
}
25+
26+
function f4() {
27+
const x = getStringOrNumber();
28+
if (typeof x !== "string") {
29+
return;
30+
}
31+
const f = function() { return x.length; };
32+
}
33+
34+
function f5() {
35+
const x = getStringOrNumber();
36+
if (typeof x === "string") {
37+
const f = () => () => x.length;
38+
}
39+
}
40+
41+
//// [constLocalsInFunctionExpressions.js]
42+
function f1() {
43+
var x = getStringOrNumber();
44+
if (typeof x === "string") {
45+
var f = function () { return x.length; };
46+
}
47+
}
48+
function f2() {
49+
var x = getStringOrNumber();
50+
if (typeof x !== "string") {
51+
return;
52+
}
53+
var f = function () { return x.length; };
54+
}
55+
function f3() {
56+
var x = getStringOrNumber();
57+
if (typeof x === "string") {
58+
var f = function () { return x.length; };
59+
}
60+
}
61+
function f4() {
62+
var x = getStringOrNumber();
63+
if (typeof x !== "string") {
64+
return;
65+
}
66+
var f = function () { return x.length; };
67+
}
68+
function f5() {
69+
var x = getStringOrNumber();
70+
if (typeof x === "string") {
71+
var f = function () { return function () { return x.length; }; };
72+
}
73+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
=== tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts ===
2+
declare function getStringOrNumber(): string | number;
3+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
4+
5+
function f1() {
6+
>f1 : Symbol(f1, Decl(constLocalsInFunctionExpressions.ts, 0, 54))
7+
8+
const x = getStringOrNumber();
9+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))
10+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
11+
12+
if (typeof x === "string") {
13+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))
14+
15+
const f = () => x.length;
16+
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 5, 13))
17+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
18+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))
19+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
20+
}
21+
}
22+
23+
function f2() {
24+
>f2 : Symbol(f2, Decl(constLocalsInFunctionExpressions.ts, 7, 1))
25+
26+
const x = getStringOrNumber();
27+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))
28+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
29+
30+
if (typeof x !== "string") {
31+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))
32+
33+
return;
34+
}
35+
const f = () => x.length;
36+
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 14, 9))
37+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
38+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))
39+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
40+
}
41+
42+
function f3() {
43+
>f3 : Symbol(f3, Decl(constLocalsInFunctionExpressions.ts, 15, 1))
44+
45+
const x = getStringOrNumber();
46+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))
47+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
48+
49+
if (typeof x === "string") {
50+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))
51+
52+
const f = function() { return x.length; };
53+
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 20, 13))
54+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
55+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))
56+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
57+
}
58+
}
59+
60+
function f4() {
61+
>f4 : Symbol(f4, Decl(constLocalsInFunctionExpressions.ts, 22, 1))
62+
63+
const x = getStringOrNumber();
64+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))
65+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
66+
67+
if (typeof x !== "string") {
68+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))
69+
70+
return;
71+
}
72+
const f = function() { return x.length; };
73+
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 29, 9))
74+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
75+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))
76+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
77+
}
78+
79+
function f5() {
80+
>f5 : Symbol(f5, Decl(constLocalsInFunctionExpressions.ts, 30, 1))
81+
82+
const x = getStringOrNumber();
83+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))
84+
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))
85+
86+
if (typeof x === "string") {
87+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))
88+
89+
const f = () => () => x.length;
90+
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 35, 13))
91+
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
92+
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))
93+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
94+
}
95+
}

0 commit comments

Comments
 (0)