Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[experiment] Try using consistent shapes for FlowNodes #51585

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 96 additions & 12 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,17 @@ import {
Expression,
ExpressionStatement,
findAncestor,
FlowArrayMutation,
FlowAssignment,
FlowCall,
FlowCondition,
FlowFlags,
FlowLabel,
FlowNode,
FlowNodeBase,
FlowReduceLabel,
FlowStart,
FlowSwitchClause,
forEach,
forEachChild,
ForInOrOfStatement,
Expand Down Expand Up @@ -464,11 +471,88 @@ const enum ContainerFlags {
IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7,
}

function initFlowNode<T extends FlowNode>(node: T) {
Debug.attachFlowNodeDebugInfo(node);
return node;
function createFlowNode<T extends FlowNode>(
flags: FlowFlags,
node?: Node,
antecedent?: FlowNode,
antecedents?: FlowNode[],
target?: FlowLabel,
switchStatement?: SwitchStatement,
clauseStart?: number,
clauseEnd?: number,
): T {
// Create all flow nodes in a predictable order. If FlowNode changes, add more
// properties here.
const flowNode: FlowNodeBase = {
flags,
id: undefined,
node,
antecedent,
antecedents,
target,
switchStatement,
clauseStart,
clauseEnd,
};
Debug.attachFlowNodeDebugInfo(flowNode);
return flowNode as T;
}

function createFlowStartNode(flags: FlowFlags): FlowStart {
return createFlowNode(flags);
}

function createFlowLabelNode(
flags: FlowFlags,
antecedents: FlowNode[] | undefined,
): FlowLabel {
return createFlowNode(flags, /*node*/ undefined, /*antecedent*/ undefined, antecedents);
}

function createFlowMutationNode(
flags: FlowFlags,
node: FlowAssignment["node"] | FlowArrayMutation["node"],
antecedent: FlowNode,
): FlowAssignment | FlowArrayMutation {
return createFlowNode(flags, node, antecedent);
}

function createFlowConditionNode(
flags: FlowFlags,
node: FlowCondition["node"],
antecedent: FlowNode,
): FlowCondition {
return createFlowNode(flags, node, antecedent);
}

function createFlowSwitchClauseNode(
flags: FlowFlags,
switchStatement: SwitchStatement,
clauseStart: number,
clauseEnd: number,
antecedent: FlowNode,
): FlowSwitchClause {
return createFlowNode(flags, /*node*/ undefined, antecedent, /*antecedents*/ undefined, /*target*/ undefined, switchStatement, clauseStart, clauseEnd);
}

function createFlowCallNode(
flags: FlowFlags,
node: FlowCall["node"],
antecedent: FlowNode,
): FlowCall {
return createFlowNode(flags, node, antecedent);
}

function createFlowReduceLabelNode(
flags: FlowFlags,
antecedent: FlowNode,
antecedents: FlowNode[],
target: FlowLabel,
): FlowReduceLabel {
return createFlowNode(flags, /*node*/ undefined, antecedent, antecedents, target);
}


const binder = createBinder();

/** @internal */
Expand Down Expand Up @@ -965,7 +1049,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
// A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave
// similarly to break statements that exit to a label just past the statement body.
if (!isImmediatelyInvoked) {
currentFlow = initFlowNode({ flags: FlowFlags.Start });
currentFlow = createFlowStartNode(FlowFlags.Start);
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) {
currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration;
}
Expand Down Expand Up @@ -1261,15 +1345,15 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
}

function createBranchLabel(): FlowLabel {
return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined });
return createFlowLabelNode(FlowFlags.BranchLabel, /*antecedents*/ undefined);
}

function createLoopLabel(): FlowLabel {
return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined });
return createFlowLabelNode(FlowFlags.LoopLabel, /*antecedents*/ undefined);
}

function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel {
return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent });
return createFlowReduceLabelNode(FlowFlags.ReduceLabel, antecedent, antecedents, target);
}

function setFlowNodeReferenced(flow: FlowNode) {
Expand Down Expand Up @@ -1300,17 +1384,17 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
return antecedent;
}
setFlowNodeReferenced(antecedent);
return initFlowNode({ flags, antecedent, node: expression });
return createFlowConditionNode(flags, expression, antecedent);
}

function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
setFlowNodeReferenced(antecedent);
return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd });
return createFlowSwitchClauseNode(FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent);
}

function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode {
setFlowNodeReferenced(antecedent);
const result = initFlowNode({ flags, antecedent, node });
const result = createFlowMutationNode(flags, node, antecedent);
if (currentExceptionTarget) {
addAntecedent(currentExceptionTarget, result);
}
Expand All @@ -1319,7 +1403,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {

function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode {
setFlowNodeReferenced(antecedent);
return initFlowNode({ flags: FlowFlags.Call, antecedent, node });
return createFlowCallNode(FlowFlags.Call, node, antecedent);
}

function finishFlowLabel(flow: FlowLabel): FlowNode {
Expand Down Expand Up @@ -2415,7 +2499,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
const host = typeAlias.parent.parent;
container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file;
blockScopeContainer = getEnclosingBlockScopeContainer(host) || file;
currentFlow = initFlowNode({ flags: FlowFlags.Start });
currentFlow = createFlowStartNode(FlowFlags.Start);
parent = typeAlias;
bind(typeAlias.typeExpression);
const declName = getNameOfDeclaration(typeAlias);
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ export namespace Debug {
}
},
__debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } },
__debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } }
__debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this as FlowNode); } }
});
}
}
Expand Down Expand Up @@ -912,7 +912,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
return !!(f.flags & hasAntecedentFlags);
}

function hasNode(f: FlowNode): f is Extract<FlowNode, { node?: Node }> {
function hasNode(f: FlowNode): f is Extract<FlowNode, { node: Node | undefined }> {
return !!(f.flags & hasNodeFlags);
}

Expand Down
9 changes: 9 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3889,6 +3889,15 @@ export type FlowNode =
export interface FlowNodeBase {
flags: FlowFlags;
id?: number; // Node id used by flow type cache in checker

// Declared here so we can construct flow nodes with properties in a consistent order.
/** @internal */ node?: Node;
/** @internal */ antecedent?: FlowNode;
/** @internal */ antecedents?: FlowNode[];
/** @internal */ target?: FlowLabel;
/** @internal */ switchStatement?: SwitchStatement;
/** @internal */ clauseStart?: number;
/** @internal */ clauseEnd?: number;
}

// FlowStart represents the start of a control flow. For a function expression or arrow
Expand Down