Skip to content

Commit

Permalink
Merge pull request #8849 from Microsoft/outerControlFlows
Browse files Browse the repository at this point in the history
Improve control flow analysis in function expressions
  • Loading branch information
ahejlsberg committed May 31, 2016
2 parents 5c095a8 + 41446fe commit 4095602
Show file tree
Hide file tree
Showing 16 changed files with 970 additions and 222 deletions.
332 changes: 163 additions & 169 deletions src/compiler/binder.ts

Large diffs are not rendered by default.

52 changes: 28 additions & 24 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7645,7 +7645,7 @@ namespace ts {
getInitialTypeOfBindingElement(<BindingElement>node);
}

function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean) {
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
let key: string;
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
Expand Down Expand Up @@ -7691,15 +7691,21 @@ namespace ts {
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
getTypeAtFlowLoopLabel(<FlowLabel>flow);
}
else if (flow.flags & FlowFlags.Unreachable) {
else if (flow.flags & FlowFlags.Start) {
// Check if we should continue with the control flow of the containing function.
const container = (<FlowStart>flow).container;
if (container && includeOuterFunctions) {
flow = container.flowNode;
continue;
}
// At the top of the flow we have the initial type.
type = initialType;
}
else {
// Unreachable code errors are reported in the binding phase. Here we
// simply return the declared type to reduce follow-on errors.
type = declaredType;
}
else {
// At the top of the flow we have the initial type.
type = initialType;
}
if (flow.flags & FlowFlags.Shared) {
// Record visited node and the associated type in the cache.
visitedFlowNodes[visitedFlowCount] = flow;
Expand Down Expand Up @@ -8068,6 +8074,17 @@ namespace ts {
return expression;
}

function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
const declarationContainer = getContainingFunctionOrModule(declaration);
let container = getContainingFunctionOrModule(reference);
while (container !== declarationContainer &&
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
container = getContainingFunctionOrModule(container);
}
return container === declarationContainer;
}

function checkIdentifier(node: Identifier): Type {
const symbol = getResolvedSymbol(node);

Expand Down Expand Up @@ -8124,10 +8141,11 @@ namespace ts {
return type;
}
const declaration = localOrExportSymbol.valueDeclaration;
const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized);
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
if (!assumeInitialized && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
Expand Down Expand Up @@ -8376,7 +8394,7 @@ namespace ts {
if (isClassLike(container.parent)) {
const symbol = getSymbolOfNode(container.parent);
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true);
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true);
}

if (isInJavaScriptFile(node)) {
Expand Down Expand Up @@ -8661,20 +8679,6 @@ namespace ts {
return undefined;
}

function getImmediatelyInvokedFunctionExpression(func: FunctionExpression | MethodDeclaration) {
if (isFunctionExpressionOrArrowFunction(func)) {
let prev: Node = func;
let parent: Node = func.parent;
while (parent.kind === SyntaxKind.ParenthesizedExpression) {
prev = parent;
parent = parent.parent;
}
if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) {
return parent as CallExpression;
}
}
}

// In a variable, parameter or property declaration with a type annotation,
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
Expand Down Expand Up @@ -9988,7 +9992,7 @@ namespace ts {
return propType;
}
}
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true);
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
}

function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ namespace ts {

ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn,
EmitHelperFlags = HasClassExtends | HasDecorators | HasParamDecorators | HasAsyncFunctions,
ReachabilityAndEmitFlags = ReachabilityCheckFlags | EmitHelperFlags,

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

// FlowStart represents the start of a control flow. For a function expression or arrow
// function, the container property references the function (which in turn has a flowNode
// property for the containing control flow).
export interface FlowStart extends FlowNode {
container?: FunctionExpression | ArrowFunction;
}

// FlowLabel represents a junction with multiple possible preceding control flows.
export interface FlowLabel extends FlowNode {
antecedents: FlowNode[];
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,20 @@ namespace ts {
}
}

export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression {
if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) {
let prev = func;
let parent = func.parent;
while (parent.kind === SyntaxKind.ParenthesizedExpression) {
prev = parent;
parent = parent.parent;
}
if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) {
return parent as CallExpression;
}
}
}

/**
* Determines whether a node is a property or element access expression for super.
*/
Expand Down
73 changes: 73 additions & 0 deletions tests/baselines/reference/constLocalsInFunctionExpressions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//// [constLocalsInFunctionExpressions.ts]
declare function getStringOrNumber(): string | number;

function f1() {
const x = getStringOrNumber();
if (typeof x === "string") {
const f = () => x.length;
}
}

function f2() {
const x = getStringOrNumber();
if (typeof x !== "string") {
return;
}
const f = () => x.length;
}

function f3() {
const x = getStringOrNumber();
if (typeof x === "string") {
const f = function() { return x.length; };
}
}

function f4() {
const x = getStringOrNumber();
if (typeof x !== "string") {
return;
}
const f = function() { return x.length; };
}

function f5() {
const x = getStringOrNumber();
if (typeof x === "string") {
const f = () => () => x.length;
}
}

//// [constLocalsInFunctionExpressions.js]
function f1() {
var x = getStringOrNumber();
if (typeof x === "string") {
var f = function () { return x.length; };
}
}
function f2() {
var x = getStringOrNumber();
if (typeof x !== "string") {
return;
}
var f = function () { return x.length; };
}
function f3() {
var x = getStringOrNumber();
if (typeof x === "string") {
var f = function () { return x.length; };
}
}
function f4() {
var x = getStringOrNumber();
if (typeof x !== "string") {
return;
}
var f = function () { return x.length; };
}
function f5() {
var x = getStringOrNumber();
if (typeof x === "string") {
var f = function () { return function () { return x.length; }; };
}
}
95 changes: 95 additions & 0 deletions tests/baselines/reference/constLocalsInFunctionExpressions.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
=== tests/cases/conformance/controlFlow/constLocalsInFunctionExpressions.ts ===
declare function getStringOrNumber(): string | number;
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

function f1() {
>f1 : Symbol(f1, Decl(constLocalsInFunctionExpressions.ts, 0, 54))

const x = getStringOrNumber();
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

if (typeof x === "string") {
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))

const f = () => x.length;
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 5, 13))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 3, 9))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}

function f2() {
>f2 : Symbol(f2, Decl(constLocalsInFunctionExpressions.ts, 7, 1))

const x = getStringOrNumber();
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

if (typeof x !== "string") {
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))

return;
}
const f = () => x.length;
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 14, 9))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 10, 9))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}

function f3() {
>f3 : Symbol(f3, Decl(constLocalsInFunctionExpressions.ts, 15, 1))

const x = getStringOrNumber();
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

if (typeof x === "string") {
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))

const f = function() { return x.length; };
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 20, 13))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 18, 9))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}

function f4() {
>f4 : Symbol(f4, Decl(constLocalsInFunctionExpressions.ts, 22, 1))

const x = getStringOrNumber();
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

if (typeof x !== "string") {
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))

return;
}
const f = function() { return x.length; };
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 29, 9))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 25, 9))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}

function f5() {
>f5 : Symbol(f5, Decl(constLocalsInFunctionExpressions.ts, 30, 1))

const x = getStringOrNumber();
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))
>getStringOrNumber : Symbol(getStringOrNumber, Decl(constLocalsInFunctionExpressions.ts, 0, 0))

if (typeof x === "string") {
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))

const f = () => () => x.length;
>f : Symbol(f, Decl(constLocalsInFunctionExpressions.ts, 35, 13))
>x.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(constLocalsInFunctionExpressions.ts, 33, 9))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}
Loading

0 comments on commit 4095602

Please sign in to comment.