mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-19 08:24:15 -06:00
Initial implementation of control flow based type analysis
This commit is contained in:
parent
3853bb86d0
commit
e67d15a1ce
@ -11,19 +11,11 @@ namespace ts {
|
||||
ConstEnumOnly = 2
|
||||
}
|
||||
|
||||
const enum Reachability {
|
||||
Uninitialized = 1 << 0,
|
||||
Reachable = 1 << 1,
|
||||
Unreachable = 1 << 2,
|
||||
ReportedUnreachable = 1 << 3
|
||||
}
|
||||
|
||||
function or(state1: Reachability, state2: Reachability): Reachability {
|
||||
return (state1 | state2) & Reachability.Reachable
|
||||
? Reachability.Reachable
|
||||
: (state1 & state2) & Reachability.ReportedUnreachable
|
||||
? Reachability.ReportedUnreachable
|
||||
: Reachability.Unreachable;
|
||||
interface ActiveLabel {
|
||||
name: string;
|
||||
breakTarget: FlowLabel;
|
||||
continueTarget: FlowLabel;
|
||||
referenced: boolean;
|
||||
}
|
||||
|
||||
export function getModuleInstanceState(node: Node): ModuleInstanceState {
|
||||
@ -112,10 +104,11 @@ namespace ts {
|
||||
|
||||
// state used by reachability checks
|
||||
let hasExplicitReturn: boolean;
|
||||
let currentReachabilityState: Reachability;
|
||||
let labelStack: Reachability[];
|
||||
let labelIndexMap: Map<number>;
|
||||
let implicitLabels: number[];
|
||||
let currentFlow: FlowNode;
|
||||
let breakTarget: FlowLabel;
|
||||
let continueTarget: FlowLabel;
|
||||
let preSwitchCaseFlow: FlowNode;
|
||||
let activeLabels: ActiveLabel[];
|
||||
|
||||
// state used for emit helpers
|
||||
let hasClassExtends: boolean;
|
||||
@ -132,6 +125,9 @@ namespace ts {
|
||||
let Symbol: { new (flags: SymbolFlags, name: string): Symbol };
|
||||
let classifiableNames: Map<string>;
|
||||
|
||||
const unreachableFlow: FlowNode = { kind: FlowKind.Unreachable };
|
||||
const reportedUncreachableFlow: FlowNode = { kind: FlowKind.Unreachable };
|
||||
|
||||
function bindSourceFile(f: SourceFile, opts: CompilerOptions) {
|
||||
file = f;
|
||||
options = opts;
|
||||
@ -154,9 +150,10 @@ namespace ts {
|
||||
lastContainer = undefined;
|
||||
seenThisKeyword = false;
|
||||
hasExplicitReturn = false;
|
||||
labelStack = undefined;
|
||||
labelIndexMap = undefined;
|
||||
implicitLabels = undefined;
|
||||
currentFlow = undefined;
|
||||
breakTarget = undefined;
|
||||
continueTarget = undefined;
|
||||
activeLabels = undefined;
|
||||
hasClassExtends = false;
|
||||
hasAsyncFunctions = false;
|
||||
hasDecorators = false;
|
||||
@ -436,11 +433,11 @@ namespace ts {
|
||||
blockScopeContainer.locals = undefined;
|
||||
}
|
||||
|
||||
let savedReachabilityState: Reachability;
|
||||
let savedLabelStack: Reachability[];
|
||||
let savedLabels: Map<number>;
|
||||
let savedImplicitLabels: number[];
|
||||
let savedHasExplicitReturn: boolean;
|
||||
let savedCurrentFlow: FlowNode;
|
||||
let savedBreakTarget: FlowLabel;
|
||||
let savedContinueTarget: FlowLabel;
|
||||
let savedActiveLabels: ActiveLabel[];
|
||||
|
||||
const kind = node.kind;
|
||||
let flags = node.flags;
|
||||
@ -457,15 +454,17 @@ namespace ts {
|
||||
|
||||
const saveState = kind === SyntaxKind.SourceFile || kind === SyntaxKind.ModuleBlock || isFunctionLikeKind(kind);
|
||||
if (saveState) {
|
||||
savedReachabilityState = currentReachabilityState;
|
||||
savedLabelStack = labelStack;
|
||||
savedLabels = labelIndexMap;
|
||||
savedImplicitLabels = implicitLabels;
|
||||
savedHasExplicitReturn = hasExplicitReturn;
|
||||
savedCurrentFlow = currentFlow;
|
||||
savedBreakTarget = breakTarget;
|
||||
savedContinueTarget = continueTarget;
|
||||
savedActiveLabels = activeLabels;
|
||||
|
||||
currentReachabilityState = Reachability.Reachable;
|
||||
hasExplicitReturn = false;
|
||||
labelStack = labelIndexMap = implicitLabels = undefined;
|
||||
currentFlow = { kind: FlowKind.Start };
|
||||
breakTarget = undefined;
|
||||
continueTarget = undefined;
|
||||
activeLabels = undefined;
|
||||
}
|
||||
|
||||
if (isInJavaScriptFile(node) && node.jsDocComment) {
|
||||
@ -474,7 +473,7 @@ namespace ts {
|
||||
|
||||
bindReachableStatement(node);
|
||||
|
||||
if (currentReachabilityState === Reachability.Reachable && isFunctionLikeKind(kind) && nodeIsPresent((<FunctionLikeDeclaration>node).body)) {
|
||||
if (currentFlow.kind !== FlowKind.Unreachable && isFunctionLikeKind(kind) && nodeIsPresent((<FunctionLikeDeclaration>node).body)) {
|
||||
flags |= NodeFlags.HasImplicitReturn;
|
||||
if (hasExplicitReturn) {
|
||||
flags |= NodeFlags.HasExplicitReturn;
|
||||
@ -503,11 +502,11 @@ namespace ts {
|
||||
node.flags = flags;
|
||||
|
||||
if (saveState) {
|
||||
activeLabels = savedActiveLabels;
|
||||
continueTarget = savedContinueTarget;
|
||||
breakTarget = savedBreakTarget;
|
||||
currentFlow = savedCurrentFlow;
|
||||
hasExplicitReturn = savedHasExplicitReturn;
|
||||
currentReachabilityState = savedReachabilityState;
|
||||
labelStack = savedLabelStack;
|
||||
labelIndexMap = savedLabels;
|
||||
implicitLabels = savedImplicitLabels;
|
||||
}
|
||||
|
||||
container = saveContainer;
|
||||
@ -562,174 +561,381 @@ namespace ts {
|
||||
case SyntaxKind.LabeledStatement:
|
||||
bindLabeledStatement(<LabeledStatement>node);
|
||||
break;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
bindBinaryExpressionFlow(<BinaryExpression>node);
|
||||
break;
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
bindConditionalExpressionFlow(<ConditionalExpression>node);
|
||||
break;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
bindVariableDeclarationFlow(<VariableDeclaration>node);
|
||||
break;
|
||||
default:
|
||||
forEachChild(node, bind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function bindWhileStatement(n: WhileStatement): void {
|
||||
const preWhileState =
|
||||
n.expression.kind === SyntaxKind.FalseKeyword ? Reachability.Unreachable : currentReachabilityState;
|
||||
const postWhileState =
|
||||
n.expression.kind === SyntaxKind.TrueKeyword ? Reachability.Unreachable : currentReachabilityState;
|
||||
|
||||
// bind expressions (don't affect reachability)
|
||||
bind(n.expression);
|
||||
|
||||
currentReachabilityState = preWhileState;
|
||||
const postWhileLabel = pushImplicitLabel();
|
||||
bind(n.statement);
|
||||
popImplicitLabel(postWhileLabel, postWhileState);
|
||||
function isNarrowableReference(expr: Expression): boolean {
|
||||
return expr.kind === SyntaxKind.Identifier ||
|
||||
expr.kind === SyntaxKind.ThisKeyword ||
|
||||
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
|
||||
}
|
||||
|
||||
function bindDoStatement(n: DoStatement): void {
|
||||
const preDoState = currentReachabilityState;
|
||||
|
||||
const postDoLabel = pushImplicitLabel();
|
||||
bind(n.statement);
|
||||
const postDoState = n.expression.kind === SyntaxKind.TrueKeyword ? Reachability.Unreachable : preDoState;
|
||||
popImplicitLabel(postDoLabel, postDoState);
|
||||
|
||||
// bind expressions (don't affect reachability)
|
||||
bind(n.expression);
|
||||
}
|
||||
|
||||
function bindForStatement(n: ForStatement): void {
|
||||
const preForState = currentReachabilityState;
|
||||
const postForLabel = pushImplicitLabel();
|
||||
|
||||
// bind expressions (don't affect reachability)
|
||||
bind(n.initializer);
|
||||
bind(n.condition);
|
||||
bind(n.incrementor);
|
||||
|
||||
bind(n.statement);
|
||||
|
||||
// for statement is considered infinite when it condition is either omitted or is true keyword
|
||||
// - for(..;;..)
|
||||
// - for(..;true;..)
|
||||
const isInfiniteLoop = (!n.condition || n.condition.kind === SyntaxKind.TrueKeyword);
|
||||
const postForState = isInfiniteLoop ? Reachability.Unreachable : preForState;
|
||||
popImplicitLabel(postForLabel, postForState);
|
||||
}
|
||||
|
||||
function bindForInOrForOfStatement(n: ForInStatement | ForOfStatement): void {
|
||||
const preStatementState = currentReachabilityState;
|
||||
const postStatementLabel = pushImplicitLabel();
|
||||
|
||||
// bind expressions (don't affect reachability)
|
||||
bind(n.initializer);
|
||||
bind(n.expression);
|
||||
|
||||
bind(n.statement);
|
||||
popImplicitLabel(postStatementLabel, preStatementState);
|
||||
}
|
||||
|
||||
function bindIfStatement(n: IfStatement): void {
|
||||
// denotes reachability state when entering 'thenStatement' part of the if statement:
|
||||
// i.e. if condition is false then thenStatement is unreachable
|
||||
const ifTrueState = n.expression.kind === SyntaxKind.FalseKeyword ? Reachability.Unreachable : currentReachabilityState;
|
||||
// denotes reachability state when entering 'elseStatement':
|
||||
// i.e. if condition is true then elseStatement is unreachable
|
||||
const ifFalseState = n.expression.kind === SyntaxKind.TrueKeyword ? Reachability.Unreachable : currentReachabilityState;
|
||||
|
||||
currentReachabilityState = ifTrueState;
|
||||
|
||||
// bind expression (don't affect reachability)
|
||||
bind(n.expression);
|
||||
|
||||
bind(n.thenStatement);
|
||||
if (n.elseStatement) {
|
||||
const preElseState = currentReachabilityState;
|
||||
currentReachabilityState = ifFalseState;
|
||||
bind(n.elseStatement);
|
||||
currentReachabilityState = or(currentReachabilityState, preElseState);
|
||||
function isNarrowingExpression(expr: Expression): boolean {
|
||||
switch (expr.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.ThisKeyword:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
return isNarrowableReference(expr);
|
||||
case SyntaxKind.CallExpression:
|
||||
return true;
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
return isNarrowingExpression((<ParenthesizedExpression>expr).expression);
|
||||
case SyntaxKind.BinaryExpression:
|
||||
return isNarrowingBinaryExpression(<BinaryExpression>expr);
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((<PrefixUnaryExpression>expr).operand);
|
||||
}
|
||||
else {
|
||||
currentReachabilityState = or(currentReachabilityState, ifFalseState);
|
||||
return false;
|
||||
}
|
||||
|
||||
function isNarrowingBinaryExpression(expr: BinaryExpression) {
|
||||
switch (expr.operatorToken.kind) {
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
if (isNarrowingExpression(expr.left) && (expr.right.kind === SyntaxKind.NullKeyword || expr.right.kind === SyntaxKind.Identifier)) {
|
||||
return true;
|
||||
}
|
||||
if (expr.left.kind === SyntaxKind.TypeOfExpression && isNarrowingExpression((<TypeOfExpression>expr.left).expression) && expr.right.kind === SyntaxKind.StringLiteral) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case SyntaxKind.AmpersandAmpersandToken:
|
||||
case SyntaxKind.BarBarToken:
|
||||
return isNarrowingExpression(expr.left) || isNarrowingExpression(expr.right);
|
||||
case SyntaxKind.InstanceOfKeyword:
|
||||
return isNarrowingExpression(expr.left);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function createFlowLabel(): FlowLabel {
|
||||
return {
|
||||
kind: FlowKind.Label,
|
||||
antecedents: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function addAntecedent(label: FlowLabel, antecedent: FlowNode): void {
|
||||
if (antecedent.kind !== FlowKind.Unreachable && !contains(label.antecedents, antecedent)) {
|
||||
(label.antecedents || (label.antecedents = [])).push(antecedent);
|
||||
}
|
||||
}
|
||||
|
||||
function bindReturnOrThrow(n: ReturnStatement | ThrowStatement): void {
|
||||
// bind expression (don't affect reachability)
|
||||
bind(n.expression);
|
||||
if (n.kind === SyntaxKind.ReturnStatement) {
|
||||
function createFlowCondition(antecedent: FlowNode, expression: Expression, assumeTrue: boolean): FlowNode {
|
||||
if (!expression) {
|
||||
return assumeTrue ? antecedent : unreachableFlow;
|
||||
}
|
||||
if (expression.kind === SyntaxKind.TrueKeyword && !assumeTrue || expression.kind === SyntaxKind.FalseKeyword && assumeTrue) {
|
||||
return unreachableFlow;
|
||||
}
|
||||
if (!isNarrowingExpression(expression)) {
|
||||
return antecedent;
|
||||
}
|
||||
return <FlowCondition>{
|
||||
kind: FlowKind.Condition,
|
||||
antecedent,
|
||||
expression,
|
||||
assumeTrue
|
||||
};
|
||||
}
|
||||
|
||||
function createFlowAssignment(antecedent: FlowNode, node: BinaryExpression | VariableDeclaration | ForInStatement | ForOfStatement): FlowNode {
|
||||
return <FlowAssignment>{
|
||||
kind: FlowKind.Assignment,
|
||||
antecedent,
|
||||
node
|
||||
};
|
||||
}
|
||||
|
||||
function finishFlow(flow: FlowNode): FlowNode {
|
||||
while (flow.kind === FlowKind.Label) {
|
||||
const antecedents = (<FlowLabel>flow).antecedents;
|
||||
if (!antecedents) {
|
||||
return unreachableFlow;
|
||||
}
|
||||
if (antecedents.length > 1) {
|
||||
break;
|
||||
}
|
||||
flow = antecedents[0];
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
|
||||
function bindWhileStatement(node: WhileStatement): void {
|
||||
const preWhileLabel = createFlowLabel();
|
||||
const postWhileLabel = createFlowLabel();
|
||||
addAntecedent(preWhileLabel, currentFlow);
|
||||
currentFlow = preWhileLabel;
|
||||
bind(node.expression);
|
||||
addAntecedent(postWhileLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ false));
|
||||
currentFlow = createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true);
|
||||
const saveBreakTarget = breakTarget;
|
||||
const saveContinueTarget = continueTarget;
|
||||
breakTarget = postWhileLabel;
|
||||
continueTarget = preWhileLabel;
|
||||
bind(node.statement);
|
||||
breakTarget = saveBreakTarget;
|
||||
continueTarget = saveContinueTarget;
|
||||
addAntecedent(preWhileLabel, currentFlow);
|
||||
currentFlow = finishFlow(postWhileLabel);
|
||||
}
|
||||
|
||||
function bindDoStatement(node: DoStatement): void {
|
||||
const preDoLabel = createFlowLabel();
|
||||
const postDoLabel = createFlowLabel();
|
||||
addAntecedent(preDoLabel, currentFlow);
|
||||
currentFlow = preDoLabel;
|
||||
const saveBreakTarget = breakTarget;
|
||||
const saveContinueTarget = continueTarget;
|
||||
breakTarget = postDoLabel;
|
||||
continueTarget = preDoLabel;
|
||||
bind(node.statement);
|
||||
breakTarget = saveBreakTarget;
|
||||
continueTarget = saveContinueTarget;
|
||||
bind(node.expression);
|
||||
addAntecedent(preDoLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true));
|
||||
addAntecedent(postDoLabel, createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ false));
|
||||
currentFlow = finishFlow(postDoLabel);
|
||||
}
|
||||
|
||||
function bindForStatement(node: ForStatement): void {
|
||||
const preLoopLabel = createFlowLabel();
|
||||
const postLoopLabel = createFlowLabel();
|
||||
bind(node.initializer);
|
||||
addAntecedent(preLoopLabel, currentFlow);
|
||||
currentFlow = preLoopLabel;
|
||||
bind(node.condition);
|
||||
addAntecedent(postLoopLabel, createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ false));
|
||||
currentFlow = createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ true);
|
||||
const saveBreakTarget = breakTarget;
|
||||
const saveContinueTarget = continueTarget;
|
||||
breakTarget = postLoopLabel;
|
||||
continueTarget = preLoopLabel;
|
||||
bind(node.statement);
|
||||
bind(node.incrementor);
|
||||
breakTarget = saveBreakTarget;
|
||||
continueTarget = saveContinueTarget;
|
||||
addAntecedent(preLoopLabel, currentFlow);
|
||||
currentFlow = finishFlow(postLoopLabel);
|
||||
}
|
||||
|
||||
function bindForInOrForOfStatement(node: ForInStatement | ForOfStatement): void {
|
||||
const preLoopLabel = createFlowLabel();
|
||||
const postLoopLabel = createFlowLabel();
|
||||
bind(node.initializer);
|
||||
bind(node.expression);
|
||||
addAntecedent(preLoopLabel, currentFlow);
|
||||
addAntecedent(postLoopLabel, currentFlow);
|
||||
currentFlow = preLoopLabel;
|
||||
const saveBreakTarget = breakTarget;
|
||||
const saveContinueTarget = continueTarget;
|
||||
breakTarget = postLoopLabel;
|
||||
continueTarget = preLoopLabel;
|
||||
currentFlow = createFlowAssignment(currentFlow, node);
|
||||
bind(node.statement);
|
||||
breakTarget = saveBreakTarget;
|
||||
continueTarget = saveContinueTarget;
|
||||
addAntecedent(preLoopLabel, currentFlow);
|
||||
addAntecedent(postLoopLabel, currentFlow);
|
||||
currentFlow = finishFlow(postLoopLabel);
|
||||
}
|
||||
|
||||
function bindIfStatement(node: IfStatement): void {
|
||||
const postIfLabel = createFlowLabel();
|
||||
bind(node.expression);
|
||||
const postConditionFlow = currentFlow;
|
||||
currentFlow = createFlowCondition(currentFlow, node.expression, /*assumeTrue*/ true);
|
||||
bind(node.thenStatement);
|
||||
addAntecedent(postIfLabel, currentFlow);
|
||||
currentFlow = createFlowCondition(postConditionFlow, node.expression, /*assumeTrue*/ false);
|
||||
bind(node.elseStatement);
|
||||
addAntecedent(postIfLabel, currentFlow);
|
||||
currentFlow = finishFlow(postIfLabel);
|
||||
}
|
||||
|
||||
function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void {
|
||||
bind(node.expression);
|
||||
if (node.kind === SyntaxKind.ReturnStatement) {
|
||||
hasExplicitReturn = true;
|
||||
}
|
||||
currentReachabilityState = Reachability.Unreachable;
|
||||
currentFlow = unreachableFlow;
|
||||
}
|
||||
|
||||
function bindBreakOrContinueStatement(n: BreakOrContinueStatement): void {
|
||||
// call bind on label (don't affect reachability)
|
||||
bind(n.label);
|
||||
// for continue case touch label so it will be marked a used
|
||||
const isValidJump = jumpToLabel(n.label, n.kind === SyntaxKind.BreakStatement ? currentReachabilityState : Reachability.Unreachable);
|
||||
if (isValidJump) {
|
||||
currentReachabilityState = Reachability.Unreachable;
|
||||
function findActiveLabel(name: string) {
|
||||
if (activeLabels) {
|
||||
for (const label of activeLabels) {
|
||||
if (label.name === name) {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function bindbreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel, continueTarget: FlowLabel) {
|
||||
const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget;
|
||||
if (flowLabel) {
|
||||
addAntecedent(flowLabel, currentFlow);
|
||||
currentFlow = unreachableFlow;
|
||||
}
|
||||
}
|
||||
|
||||
function bindTryStatement(n: TryStatement): void {
|
||||
// catch\finally blocks has the same reachability as try block
|
||||
const preTryState = currentReachabilityState;
|
||||
bind(n.tryBlock);
|
||||
const postTryState = currentReachabilityState;
|
||||
|
||||
currentReachabilityState = preTryState;
|
||||
bind(n.catchClause);
|
||||
const postCatchState = currentReachabilityState;
|
||||
|
||||
currentReachabilityState = preTryState;
|
||||
bind(n.finallyBlock);
|
||||
|
||||
// post catch/finally state is reachable if
|
||||
// - post try state is reachable - control flow can fall out of try block
|
||||
// - post catch state is reachable - control flow can fall out of catch block
|
||||
currentReachabilityState = n.catchClause ? or(postTryState, postCatchState) : postTryState;
|
||||
function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void {
|
||||
bind(node.label);
|
||||
if (node.label) {
|
||||
const activeLabel = findActiveLabel(node.label.text);
|
||||
if (activeLabel) {
|
||||
activeLabel.referenced = true;
|
||||
bindbreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bindbreakOrContinueFlow(node, breakTarget, continueTarget);
|
||||
}
|
||||
}
|
||||
|
||||
function bindSwitchStatement(n: SwitchStatement): void {
|
||||
const preSwitchState = currentReachabilityState;
|
||||
const postSwitchLabel = pushImplicitLabel();
|
||||
|
||||
// bind expression (don't affect reachability)
|
||||
bind(n.expression);
|
||||
|
||||
bind(n.caseBlock);
|
||||
|
||||
const hasDefault = forEach(n.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
|
||||
|
||||
// post switch state is unreachable if switch is exhaustive (has a default case ) and does not have fallthrough from the last case
|
||||
const postSwitchState = hasDefault && currentReachabilityState !== Reachability.Reachable ? Reachability.Unreachable : preSwitchState;
|
||||
|
||||
popImplicitLabel(postSwitchLabel, postSwitchState);
|
||||
function bindTryStatement(node: TryStatement): void {
|
||||
const postFinallyLabel = createFlowLabel();
|
||||
const preTryFlow = currentFlow;
|
||||
// TODO: Every statement in try block is potentially an exit point!
|
||||
bind(node.tryBlock);
|
||||
addAntecedent(postFinallyLabel, currentFlow);
|
||||
if (node.catchClause) {
|
||||
currentFlow = preTryFlow;
|
||||
bind(node.catchClause);
|
||||
addAntecedent(postFinallyLabel, currentFlow);
|
||||
}
|
||||
if (node.finallyBlock) {
|
||||
currentFlow = preTryFlow;
|
||||
bind(node.finallyBlock);
|
||||
}
|
||||
currentFlow = finishFlow(postFinallyLabel);
|
||||
}
|
||||
|
||||
function bindCaseBlock(n: CaseBlock): void {
|
||||
const startState = currentReachabilityState;
|
||||
function bindSwitchStatement(node: SwitchStatement): void {
|
||||
const postSwitchLabel = createFlowLabel();
|
||||
bind(node.expression);
|
||||
const saveBreakTarget = breakTarget;
|
||||
const savePreSwitchCaseFlow = preSwitchCaseFlow;
|
||||
breakTarget = postSwitchLabel;
|
||||
preSwitchCaseFlow = currentFlow;
|
||||
bind(node.caseBlock);
|
||||
addAntecedent(postSwitchLabel, currentFlow);
|
||||
const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
|
||||
if (!hasDefault) {
|
||||
addAntecedent(postSwitchLabel, preSwitchCaseFlow);
|
||||
}
|
||||
breakTarget = saveBreakTarget;
|
||||
preSwitchCaseFlow = savePreSwitchCaseFlow;
|
||||
currentFlow = finishFlow(postSwitchLabel);
|
||||
}
|
||||
|
||||
for (let i = 0; i < n.clauses.length; i++) {
|
||||
const clause = n.clauses[i];
|
||||
currentReachabilityState = startState;
|
||||
bind(clause);
|
||||
if (clause.statements.length &&
|
||||
i !== n.clauses.length - 1 && // allow fallthrough from the last case
|
||||
currentReachabilityState === Reachability.Reachable &&
|
||||
options.noFallthroughCasesInSwitch) {
|
||||
errorOnFirstToken(clause, Diagnostics.Fallthrough_case_in_switch);
|
||||
function bindCaseBlock(node: CaseBlock): void {
|
||||
const clauses = node.clauses;
|
||||
for (let i = 0; i < clauses.length; i++) {
|
||||
const clause = clauses[i];
|
||||
if (clause.statements.length) {
|
||||
if (currentFlow.kind === FlowKind.Unreachable) {
|
||||
currentFlow = preSwitchCaseFlow;
|
||||
}
|
||||
else {
|
||||
const preCaseLabel = createFlowLabel();
|
||||
addAntecedent(preCaseLabel, preSwitchCaseFlow);
|
||||
addAntecedent(preCaseLabel, currentFlow);
|
||||
currentFlow = finishFlow(preCaseLabel);
|
||||
}
|
||||
bind(clause);
|
||||
if (currentFlow.kind !== FlowKind.Unreachable && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) {
|
||||
errorOnFirstToken(clause, Diagnostics.Fallthrough_case_in_switch);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bind(clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindLabeledStatement(n: LabeledStatement): void {
|
||||
// call bind on label (don't affect reachability)
|
||||
bind(n.label);
|
||||
function pushActiveLabel(name: string, breakTarget: FlowLabel, continueTarget: FlowLabel): ActiveLabel {
|
||||
const activeLabel = {
|
||||
name,
|
||||
breakTarget,
|
||||
continueTarget,
|
||||
referenced: false
|
||||
};
|
||||
(activeLabels || (activeLabels = [])).push(activeLabel);
|
||||
return activeLabel;
|
||||
}
|
||||
|
||||
const ok = pushNamedLabel(n.label);
|
||||
bind(n.statement);
|
||||
if (ok) {
|
||||
popNamedLabel(n.label, currentReachabilityState);
|
||||
function popActiveLabel() {
|
||||
activeLabels.pop();
|
||||
}
|
||||
|
||||
function bindLabeledStatement(node: LabeledStatement): void {
|
||||
const preStatementLabel = createFlowLabel();
|
||||
const postStatementLabel = createFlowLabel();
|
||||
bind(node.label);
|
||||
addAntecedent(preStatementLabel, currentFlow);
|
||||
const activeLabel = pushActiveLabel(node.label.text, postStatementLabel, preStatementLabel);
|
||||
bind(node.statement);
|
||||
popActiveLabel();
|
||||
if (!activeLabel.referenced && !options.allowUnusedLabels) {
|
||||
file.bindDiagnostics.push(createDiagnosticForNode(node.label, Diagnostics.Unused_label));
|
||||
}
|
||||
addAntecedent(postStatementLabel, currentFlow);
|
||||
currentFlow = finishFlow(postStatementLabel);
|
||||
}
|
||||
|
||||
function bindBinaryExpressionFlow(node: BinaryExpression) {
|
||||
const operator = node.operatorToken.kind;
|
||||
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken) {
|
||||
const postExpressionLabel = createFlowLabel();
|
||||
bind(node.left);
|
||||
bind(node.operatorToken);
|
||||
addAntecedent(postExpressionLabel, currentFlow);
|
||||
currentFlow = createFlowCondition(currentFlow, node.left, /*assumeTrue*/ operator === SyntaxKind.AmpersandAmpersandToken);
|
||||
bind(node.right);
|
||||
addAntecedent(postExpressionLabel, currentFlow);
|
||||
currentFlow = finishFlow(postExpressionLabel);
|
||||
}
|
||||
else {
|
||||
forEachChild(node, bind);
|
||||
if (operator === SyntaxKind.EqualsToken) {
|
||||
currentFlow = createFlowAssignment(currentFlow, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindConditionalExpressionFlow(node: ConditionalExpression) {
|
||||
const postExpressionLabel = createFlowLabel();
|
||||
bind(node.condition);
|
||||
const postConditionFlow = currentFlow;
|
||||
currentFlow = createFlowCondition(currentFlow, node.condition, /*assumeTrue*/ true);
|
||||
bind(node.whenTrue);
|
||||
addAntecedent(postExpressionLabel, currentFlow);
|
||||
currentFlow = createFlowCondition(postConditionFlow, node.condition, /*assumeTrue*/ false);
|
||||
bind(node.whenFalse);
|
||||
addAntecedent(postExpressionLabel, currentFlow);
|
||||
currentFlow = finishFlow(postExpressionLabel);
|
||||
}
|
||||
|
||||
function bindVariableDeclarationFlow(node: VariableDeclaration) {
|
||||
forEachChild(node, bind);
|
||||
if (node.initializer) {
|
||||
currentFlow = createFlowAssignment(currentFlow, node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1239,7 +1445,16 @@ namespace ts {
|
||||
switch (node.kind) {
|
||||
/* Strict mode checks */
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.ThisKeyword:
|
||||
if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) {
|
||||
node.flowNode = currentFlow;
|
||||
}
|
||||
return checkStrictModeIdentifier(<Identifier>node);
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
if (currentFlow && isNarrowableReference(<Expression>node)) {
|
||||
node.flowNode = currentFlow;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
if (isInJavaScriptFile(node)) {
|
||||
const specialKind = getSpecialPropertyAssignmentKind(node);
|
||||
@ -1663,132 +1878,53 @@ namespace ts {
|
||||
|
||||
// reachability checks
|
||||
|
||||
function pushNamedLabel(name: Identifier): boolean {
|
||||
initializeReachabilityStateIfNecessary();
|
||||
|
||||
if (hasProperty(labelIndexMap, name.text)) {
|
||||
return false;
|
||||
}
|
||||
labelIndexMap[name.text] = labelStack.push(Reachability.Uninitialized) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
function pushImplicitLabel(): number {
|
||||
initializeReachabilityStateIfNecessary();
|
||||
|
||||
const index = labelStack.push(Reachability.Uninitialized) - 1;
|
||||
implicitLabels.push(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
function popNamedLabel(label: Identifier, outerState: Reachability): void {
|
||||
const index = labelIndexMap[label.text];
|
||||
Debug.assert(index !== undefined);
|
||||
Debug.assert(labelStack.length == index + 1);
|
||||
|
||||
labelIndexMap[label.text] = undefined;
|
||||
|
||||
setCurrentStateAtLabel(labelStack.pop(), outerState, label);
|
||||
}
|
||||
|
||||
function popImplicitLabel(implicitLabelIndex: number, outerState: Reachability): void {
|
||||
if (labelStack.length !== implicitLabelIndex + 1) {
|
||||
Debug.assert(false, `Label stack: ${labelStack.length}, index:${implicitLabelIndex}`);
|
||||
}
|
||||
|
||||
const i = implicitLabels.pop();
|
||||
|
||||
if (implicitLabelIndex !== i) {
|
||||
Debug.assert(false, `i: ${i}, index: ${implicitLabelIndex}`);
|
||||
}
|
||||
|
||||
setCurrentStateAtLabel(labelStack.pop(), outerState, /*name*/ undefined);
|
||||
}
|
||||
|
||||
function setCurrentStateAtLabel(innerMergedState: Reachability, outerState: Reachability, label: Identifier): void {
|
||||
if (innerMergedState === Reachability.Uninitialized) {
|
||||
if (label && !options.allowUnusedLabels) {
|
||||
file.bindDiagnostics.push(createDiagnosticForNode(label, Diagnostics.Unused_label));
|
||||
}
|
||||
currentReachabilityState = outerState;
|
||||
}
|
||||
else {
|
||||
currentReachabilityState = or(innerMergedState, outerState);
|
||||
}
|
||||
}
|
||||
|
||||
function jumpToLabel(label: Identifier, outerState: Reachability): boolean {
|
||||
initializeReachabilityStateIfNecessary();
|
||||
|
||||
const index = label ? labelIndexMap[label.text] : lastOrUndefined(implicitLabels);
|
||||
if (index === undefined) {
|
||||
// reference to unknown label or
|
||||
// break/continue used outside of loops
|
||||
return false;
|
||||
}
|
||||
const stateAtLabel = labelStack[index];
|
||||
labelStack[index] = stateAtLabel === Reachability.Uninitialized ? outerState : or(stateAtLabel, outerState);
|
||||
return true;
|
||||
function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {
|
||||
const instanceState = getModuleInstanceState(node);
|
||||
return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && options.preserveConstEnums);
|
||||
}
|
||||
|
||||
function checkUnreachable(node: Node): boolean {
|
||||
switch (currentReachabilityState) {
|
||||
case Reachability.Unreachable:
|
||||
const reportError =
|
||||
// report error on all statements except empty ones
|
||||
(isStatement(node) && node.kind !== SyntaxKind.EmptyStatement) ||
|
||||
// report error on class declarations
|
||||
node.kind === SyntaxKind.ClassDeclaration ||
|
||||
// report error on instantiated modules or const-enums only modules if preserveConstEnums is set
|
||||
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(<ModuleDeclaration>node)) ||
|
||||
// report error on regular enums and const enums if preserveConstEnums is set
|
||||
(node.kind === SyntaxKind.EnumDeclaration && (!isConstEnumDeclaration(node) || options.preserveConstEnums));
|
||||
if (currentFlow.kind !== FlowKind.Unreachable) {
|
||||
return false;
|
||||
}
|
||||
if (currentFlow === unreachableFlow) {
|
||||
const reportError =
|
||||
// report error on all statements except empty ones
|
||||
(isStatement(node) && node.kind !== SyntaxKind.EmptyStatement) ||
|
||||
// report error on class declarations
|
||||
node.kind === SyntaxKind.ClassDeclaration ||
|
||||
// report error on instantiated modules or const-enums only modules if preserveConstEnums is set
|
||||
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(<ModuleDeclaration>node)) ||
|
||||
// report error on regular enums and const enums if preserveConstEnums is set
|
||||
(node.kind === SyntaxKind.EnumDeclaration && (!isConstEnumDeclaration(node) || options.preserveConstEnums));
|
||||
|
||||
if (reportError) {
|
||||
currentReachabilityState = Reachability.ReportedUnreachable;
|
||||
if (reportError) {
|
||||
currentFlow = reportedUncreachableFlow;
|
||||
|
||||
// unreachable code is reported if
|
||||
// - user has explicitly asked about it AND
|
||||
// - statement is in not ambient context (statements in ambient context is already an error
|
||||
// so we should not report extras) AND
|
||||
// - node is not variable statement OR
|
||||
// - node is block scoped variable statement OR
|
||||
// - node is not block scoped variable statement and at least one variable declaration has initializer
|
||||
// Rationale: we don't want to report errors on non-initialized var's since they are hoisted
|
||||
// On the other side we do want to report errors on non-initialized 'lets' because of TDZ
|
||||
const reportUnreachableCode =
|
||||
!options.allowUnreachableCode &&
|
||||
!isInAmbientContext(node) &&
|
||||
(
|
||||
node.kind !== SyntaxKind.VariableStatement ||
|
||||
getCombinedNodeFlags((<VariableStatement>node).declarationList) & NodeFlags.BlockScoped ||
|
||||
forEach((<VariableStatement>node).declarationList.declarations, d => d.initializer)
|
||||
);
|
||||
// unreachable code is reported if
|
||||
// - user has explicitly asked about it AND
|
||||
// - statement is in not ambient context (statements in ambient context is already an error
|
||||
// so we should not report extras) AND
|
||||
// - node is not variable statement OR
|
||||
// - node is block scoped variable statement OR
|
||||
// - node is not block scoped variable statement and at least one variable declaration has initializer
|
||||
// Rationale: we don't want to report errors on non-initialized var's since they are hoisted
|
||||
// On the other side we do want to report errors on non-initialized 'lets' because of TDZ
|
||||
const reportUnreachableCode =
|
||||
!options.allowUnreachableCode &&
|
||||
!isInAmbientContext(node) &&
|
||||
(
|
||||
node.kind !== SyntaxKind.VariableStatement ||
|
||||
getCombinedNodeFlags((<VariableStatement>node).declarationList) & NodeFlags.BlockScoped ||
|
||||
forEach((<VariableStatement>node).declarationList.declarations, d => d.initializer)
|
||||
);
|
||||
|
||||
if (reportUnreachableCode) {
|
||||
errorOnFirstToken(node, Diagnostics.Unreachable_code_detected);
|
||||
}
|
||||
if (reportUnreachableCode) {
|
||||
errorOnFirstToken(node, Diagnostics.Unreachable_code_detected);
|
||||
}
|
||||
case Reachability.ReportedUnreachable:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {
|
||||
const instanceState = getModuleInstanceState(node);
|
||||
return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && options.preserveConstEnums);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeReachabilityStateIfNecessary(): void {
|
||||
if (labelIndexMap) {
|
||||
return;
|
||||
}
|
||||
currentReachabilityState = Reachability.Reachable;
|
||||
labelIndexMap = {};
|
||||
labelStack = [];
|
||||
implicitLabels = [];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ namespace ts {
|
||||
let nextSymbolId = 1;
|
||||
let nextNodeId = 1;
|
||||
let nextMergeId = 1;
|
||||
let nextFlowId = 1;
|
||||
|
||||
export function getNodeId(node: Node): number {
|
||||
if (!node.id) {
|
||||
@ -118,6 +119,7 @@ namespace ts {
|
||||
const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null");
|
||||
const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined");
|
||||
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
|
||||
const resolvingFlowType = createIntrinsicType(TypeFlags.Void, "__resolving__");
|
||||
|
||||
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
|
||||
const emptyUnionType = emptyObjectType;
|
||||
@ -186,6 +188,7 @@ namespace ts {
|
||||
const mergedSymbols: Symbol[] = [];
|
||||
const symbolLinks: SymbolLinks[] = [];
|
||||
const nodeLinks: NodeLinks[] = [];
|
||||
const flowTypeCaches: Map<Type>[] = [];
|
||||
const potentialThisCollisions: Node[] = [];
|
||||
const awaitedTypeStack: number[] = [];
|
||||
|
||||
@ -7136,11 +7139,11 @@ namespace ts {
|
||||
Debug.fail("should not get here");
|
||||
}
|
||||
|
||||
// Return the assignment key for a "dotted name" (i.e. a sequence of identifiers
|
||||
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
|
||||
// separated by dots). The key consists of the id of the symbol referenced by the
|
||||
// leftmost identifier followed by zero or more property names separated by dots.
|
||||
// The result is undefined if the reference isn't a dotted name.
|
||||
function getAssignmentKey(node: Node): string {
|
||||
function getFlowCacheKey(node: Node): string {
|
||||
if (node.kind === SyntaxKind.Identifier) {
|
||||
const symbol = getResolvedSymbol(<Identifier>node);
|
||||
return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined;
|
||||
@ -7149,125 +7152,12 @@ namespace ts {
|
||||
return "0";
|
||||
}
|
||||
if (node.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
const key = getAssignmentKey((<PropertyAccessExpression>node).expression);
|
||||
const key = getFlowCacheKey((<PropertyAccessExpression>node).expression);
|
||||
return key && key + "." + (<PropertyAccessExpression>node).name.text;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function hasInitializer(node: VariableLikeDeclaration): boolean {
|
||||
return !!(node.initializer || isBindingPattern(node.parent) && hasInitializer(<VariableLikeDeclaration>node.parent.parent));
|
||||
}
|
||||
|
||||
// For a given node compute a map of which dotted names are assigned within
|
||||
// the node.
|
||||
function getAssignmentMap(node: Node): Map<boolean> {
|
||||
const assignmentMap: Map<boolean> = {};
|
||||
visit(node);
|
||||
return assignmentMap;
|
||||
|
||||
function visitReference(node: Identifier | PropertyAccessExpression) {
|
||||
if (isAssignmentTarget(node) || isCompoundAssignmentTarget(node)) {
|
||||
const key = getAssignmentKey(node);
|
||||
if (key) {
|
||||
assignmentMap[key] = true;
|
||||
}
|
||||
}
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
|
||||
function visitVariableDeclaration(node: VariableLikeDeclaration) {
|
||||
if (!isBindingPattern(node.name) && hasInitializer(node)) {
|
||||
assignmentMap[getSymbolId(getSymbolOfNode(node))] = true;
|
||||
}
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
|
||||
function visit(node: Node) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
visitReference(<Identifier | PropertyAccessExpression>node);
|
||||
break;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.BindingElement:
|
||||
visitVariableDeclaration(<VariableLikeDeclaration>node);
|
||||
break;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
case SyntaxKind.AsExpression:
|
||||
case SyntaxKind.NonNullExpression:
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
case SyntaxKind.DeleteExpression:
|
||||
case SyntaxKind.AwaitExpression:
|
||||
case SyntaxKind.TypeOfExpression:
|
||||
case SyntaxKind.VoidExpression:
|
||||
case SyntaxKind.PostfixUnaryExpression:
|
||||
case SyntaxKind.YieldExpression:
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
case SyntaxKind.SpreadElementExpression:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.VariableStatement:
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
case SyntaxKind.IfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.ReturnStatement:
|
||||
case SyntaxKind.WithStatement:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.CaseBlock:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
case SyntaxKind.LabeledStatement:
|
||||
case SyntaxKind.ThrowStatement:
|
||||
case SyntaxKind.TryStatement:
|
||||
case SyntaxKind.CatchClause:
|
||||
case SyntaxKind.JsxElement:
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
case SyntaxKind.JsxAttribute:
|
||||
case SyntaxKind.JsxSpreadAttribute:
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
case SyntaxKind.JsxExpression:
|
||||
forEachChild(node, visit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isReferenceAssignedWithin(reference: Node, node: Node): boolean {
|
||||
if (reference.kind !== SyntaxKind.ThisKeyword) {
|
||||
const key = getAssignmentKey(reference);
|
||||
if (key) {
|
||||
const links = getNodeLinks(node);
|
||||
return (links.assignmentMap || (links.assignmentMap = getAssignmentMap(node)))[key];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAnyPartOfReferenceAssignedWithin(reference: Node, node: Node) {
|
||||
while (true) {
|
||||
if (isReferenceAssignedWithin(reference, node)) {
|
||||
return true;
|
||||
}
|
||||
if (reference.kind !== SyntaxKind.PropertyAccessExpression) {
|
||||
return false;
|
||||
}
|
||||
reference = (<PropertyAccessExpression>reference).expression;
|
||||
}
|
||||
}
|
||||
|
||||
function isNullOrUndefinedLiteral(node: Expression) {
|
||||
return node.kind === SyntaxKind.NullKeyword ||
|
||||
node.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>node) === undefinedSymbol;
|
||||
@ -7299,16 +7189,68 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the narrowed type of a given symbol at a given location
|
||||
function containsMatchingReference(source: Node, target: Node) {
|
||||
while (true) {
|
||||
if (isMatchingReference(source, target)) {
|
||||
return true;
|
||||
}
|
||||
if (source.kind !== SyntaxKind.PropertyAccessExpression) {
|
||||
return false;
|
||||
}
|
||||
source = (<PropertyAccessExpression>source).expression;
|
||||
}
|
||||
}
|
||||
|
||||
function hasMatchingArgument(callExpression: CallExpression, target: Node) {
|
||||
if (callExpression.arguments) {
|
||||
for (const argument of callExpression.arguments) {
|
||||
if (isMatchingReference(argument, target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
isMatchingReference((<PropertyAccessExpression>callExpression.expression).expression, target)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getFlowTypeCache(flow: FlowNode): Map<Type> {
|
||||
if (!flow.id) {
|
||||
flow.id = nextFlowId;
|
||||
nextFlowId++;
|
||||
}
|
||||
return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {});
|
||||
}
|
||||
|
||||
function isNarrowableReference(expr: Node): boolean {
|
||||
return expr.kind === SyntaxKind.Identifier ||
|
||||
expr.kind === SyntaxKind.ThisKeyword ||
|
||||
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
|
||||
}
|
||||
|
||||
function getAssignmentReducedType(type: Type, assignedType: Type) {
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
const reducedTypes = filter((<UnionType>type).types, t => isTypeAssignableTo(assignedType, t));
|
||||
if (reducedTypes.length) {
|
||||
return reducedTypes.length === 1 ? reducedTypes[0] : getUnionType(reducedTypes);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function getNarrowedTypeOfReference(type: Type, reference: Node) {
|
||||
if (!(type.flags & (TypeFlags.Any | TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter))) {
|
||||
return type;
|
||||
}
|
||||
if (!isNarrowableReference(reference)) {
|
||||
return type;
|
||||
}
|
||||
const leftmostNode = getLeftmostIdentifierOrThis(reference);
|
||||
if (!leftmostNode) {
|
||||
return type;
|
||||
}
|
||||
let top: Node;
|
||||
if (leftmostNode.kind === SyntaxKind.Identifier) {
|
||||
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
|
||||
if (!leftmostSymbol) {
|
||||
@ -7318,74 +7260,138 @@ namespace ts {
|
||||
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
|
||||
return type;
|
||||
}
|
||||
top = getDeclarationContainer(declaration);
|
||||
}
|
||||
const originalType = type;
|
||||
const nodeStack: { node: Node, child: Node }[] = [];
|
||||
let node: Node = reference;
|
||||
loop: while (node.parent) {
|
||||
const child = node;
|
||||
node = node.parent;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.IfStatement:
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
case SyntaxKind.BinaryExpression:
|
||||
nodeStack.push({node, child});
|
||||
break;
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
break loop;
|
||||
default:
|
||||
if (node === top || isFunctionLikeKind(node.kind)) {
|
||||
break loop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return getFlowTypeOfReference(reference, type, type);
|
||||
}
|
||||
|
||||
let nodes: { node: Node, child: Node };
|
||||
while (nodes = nodeStack.pop()) {
|
||||
const {node, child} = nodes;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.IfStatement:
|
||||
// In a branch of an if statement, narrow based on controlling expression
|
||||
if (child !== (<IfStatement>node).expression) {
|
||||
type = narrowType(type, (<IfStatement>node).expression, /*assumeTrue*/ child === (<IfStatement>node).thenStatement);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
// In a branch of a conditional expression, narrow based on controlling condition
|
||||
if (child !== (<ConditionalExpression>node).condition) {
|
||||
type = narrowType(type, (<ConditionalExpression>node).condition, /*assumeTrue*/ child === (<ConditionalExpression>node).whenTrue);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
// In the right operand of an && or ||, narrow based on left operand
|
||||
if (child === (<BinaryExpression>node).right) {
|
||||
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
|
||||
type = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ true);
|
||||
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType: Type) {
|
||||
let key: string;
|
||||
return reference.flowNode ? getTypeAtFlowNode(reference.flowNode) : initialType;
|
||||
|
||||
function getTypeAtFlowNode(flow: FlowNode): Type {
|
||||
while (true) {
|
||||
switch (flow.kind) {
|
||||
case FlowKind.Assignment:
|
||||
const type = getTypeAtFlowAssignment(<FlowAssignment>flow);
|
||||
if (!type) {
|
||||
flow = (<FlowAssignment>flow).antecedent;
|
||||
continue;
|
||||
}
|
||||
else if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken) {
|
||||
type = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ false);
|
||||
return type;
|
||||
case FlowKind.Condition:
|
||||
return getTypeAtFlowCondition(<FlowCondition>flow);
|
||||
case FlowKind.Label:
|
||||
if ((<FlowLabel>flow).antecedents.length === 1) {
|
||||
flow = (<FlowLabel>flow).antecedents[0];
|
||||
continue;
|
||||
}
|
||||
return getTypeAtFlowLabel(<FlowLabel>flow);
|
||||
}
|
||||
// At the top of the flow we have the initial type
|
||||
return initialType;
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAtVariableDeclaration(node: VariableDeclaration) {
|
||||
if (reference.kind === SyntaxKind.Identifier && !isBindingPattern(node.name) && getResolvedSymbol(<Identifier>reference) === getSymbolOfNode(node)) {
|
||||
return getAssignmentReducedType(declaredType, checkExpressionCached((<VariableDeclaration>node).initializer));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getTypeAtForInOrForOfStatement(node: ForInStatement | ForOfStatement) {
|
||||
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
|
||||
if (reference.kind === SyntaxKind.Identifier) {
|
||||
const variable = (<VariableDeclarationList>node.initializer).declarations[0];
|
||||
if (variable && !isBindingPattern(variable.name) && getResolvedSymbol(<Identifier>reference) === getSymbolOfNode(variable)) {
|
||||
return declaredType;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isMatchingReference(reference, <Expression>node.initializer)) {
|
||||
const type = node.kind === SyntaxKind.ForOfStatement ? checkRightHandSideOfForOf(node.expression) : stringType;
|
||||
return getAssignmentReducedType(declaredType, type);
|
||||
}
|
||||
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
containsMatchingReference((<PropertyAccessExpression>reference).expression, <Expression>node.initializer)) {
|
||||
return declaredType;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getTypeAtFlowAssignment(flow: FlowAssignment) {
|
||||
const node = flow.node;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.BinaryExpression:
|
||||
// If reference matches left hand side and type on right is properly assignable,
|
||||
// return type on right. Otherwise default to the declared type.
|
||||
if (isMatchingReference(reference, (<BinaryExpression>node).left)) {
|
||||
return getAssignmentReducedType(declaredType, checkExpressionCached((<BinaryExpression>node).right));
|
||||
}
|
||||
// We didn't have a direct match. However, if the reference is a dotted name, this
|
||||
// may be an assignment to a left hand part of the reference. For example, for a
|
||||
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
|
||||
// return the declared type.
|
||||
if (reference.kind === SyntaxKind.PropertyAccessExpression &&
|
||||
containsMatchingReference((<PropertyAccessExpression>reference).expression, (<BinaryExpression>node).left)) {
|
||||
return declaredType;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug.fail("Unreachable!");
|
||||
}
|
||||
|
||||
// Use original type if construct contains assignments to variable
|
||||
if (type !== originalType && isAnyPartOfReferenceAssignedWithin(reference, node)) {
|
||||
type = originalType;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return getTypeAtVariableDeclaration(<VariableDeclaration>node);
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
return getTypeAtForInOrForOfStatement(<ForInStatement | ForOfStatement>node);
|
||||
}
|
||||
// Assignment doesn't affect reference
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Preserve old top-level behavior - if the branch is really an empty set, revert to prior type
|
||||
if (type === emptyUnionType) {
|
||||
type = originalType;
|
||||
function getTypeAtFlowCondition(flow: FlowCondition) {
|
||||
const type = getTypeAtFlowNode(flow.antecedent);
|
||||
if (type === resolvingFlowType) {
|
||||
return type;
|
||||
}
|
||||
return narrowType(type, (<FlowCondition>flow).expression, (<FlowCondition>flow).assumeTrue);
|
||||
}
|
||||
|
||||
return type;
|
||||
function getTypeAtFlowNodeCached(flow: FlowNode) {
|
||||
const cache = getFlowTypeCache(flow);
|
||||
if (!key) {
|
||||
key = getFlowCacheKey(reference);
|
||||
}
|
||||
let type = cache[key];
|
||||
if (type) {
|
||||
return type;
|
||||
}
|
||||
cache[key] = resolvingFlowType;
|
||||
type = getTypeAtFlowNode(flow);
|
||||
cache[key] = type !== resolvingFlowType ? type : undefined;
|
||||
return type;
|
||||
}
|
||||
|
||||
function getTypeAtFlowLabel(flow: FlowLabel) {
|
||||
const antecedentTypes: Type[] = [];
|
||||
for (const antecedent of flow.antecedents) {
|
||||
const t = getTypeAtFlowNodeCached(antecedent);
|
||||
if (t !== resolvingFlowType) {
|
||||
// If the type at a particular antecedent path is the declared type, there is no
|
||||
// reason to process more antecedents since the only possible outcome is subtypes
|
||||
// that are be removed in the final union type anyway.
|
||||
if (t === declaredType) {
|
||||
return t;
|
||||
}
|
||||
if (!contains(antecedentTypes, t)) {
|
||||
antecedentTypes.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
return antecedentTypes.length === 0 ? declaredType :
|
||||
antecedentTypes.length === 1 ? antecedentTypes[0] :
|
||||
getUnionType(antecedentTypes);
|
||||
}
|
||||
|
||||
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
|
||||
return strictNullChecks && assumeTrue && isMatchingReference(expr, reference) ? getNonNullableType(type) : type;
|
||||
@ -7574,7 +7580,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
|
||||
if (type.flags & TypeFlags.Any) {
|
||||
if (type.flags & TypeFlags.Any || !hasMatchingArgument(callExpression, reference)) {
|
||||
return type;
|
||||
}
|
||||
const signature = getResolvedSignature(callExpression);
|
||||
@ -7656,98 +7662,6 @@ namespace ts {
|
||||
return expression;
|
||||
}
|
||||
|
||||
function findFirstAssignment(symbol: Symbol, container: Node): Node {
|
||||
return visit(isFunctionLike(container) ? (<FunctionLikeDeclaration>container).body : container);
|
||||
|
||||
function visit(node: Node): Node {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
const assignment = getAssignmentRoot(node);
|
||||
return assignment && getResolvedSymbol(<Identifier>node) === symbol ? assignment : undefined;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.BindingElement:
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
case SyntaxKind.AsExpression:
|
||||
case SyntaxKind.NonNullExpression:
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
case SyntaxKind.DeleteExpression:
|
||||
case SyntaxKind.AwaitExpression:
|
||||
case SyntaxKind.TypeOfExpression:
|
||||
case SyntaxKind.VoidExpression:
|
||||
case SyntaxKind.PostfixUnaryExpression:
|
||||
case SyntaxKind.YieldExpression:
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
case SyntaxKind.SpreadElementExpression:
|
||||
case SyntaxKind.VariableStatement:
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
case SyntaxKind.IfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.ReturnStatement:
|
||||
case SyntaxKind.WithStatement:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.CaseBlock:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
case SyntaxKind.LabeledStatement:
|
||||
case SyntaxKind.ThrowStatement:
|
||||
case SyntaxKind.TryStatement:
|
||||
case SyntaxKind.CatchClause:
|
||||
case SyntaxKind.JsxElement:
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
case SyntaxKind.JsxAttribute:
|
||||
case SyntaxKind.JsxSpreadAttribute:
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
case SyntaxKind.JsxExpression:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.SourceFile:
|
||||
return forEachChild(node, visit);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function checkVariableAssignedBefore(symbol: Symbol, reference: Node) {
|
||||
if (!(symbol.flags & SymbolFlags.Variable)) {
|
||||
return;
|
||||
}
|
||||
const declaration = symbol.valueDeclaration;
|
||||
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration || (<VariableDeclaration>declaration).initializer) {
|
||||
return;
|
||||
}
|
||||
const parentParentKind = declaration.parent.parent.kind;
|
||||
if (parentParentKind === SyntaxKind.ForOfStatement || parentParentKind === SyntaxKind.ForInStatement) {
|
||||
return;
|
||||
}
|
||||
const declarationContainer = getContainingFunction(declaration) || getSourceFileOfNode(declaration);
|
||||
const referenceContainer = getContainingFunction(reference) || getSourceFileOfNode(reference);
|
||||
if (declarationContainer !== referenceContainer) {
|
||||
return;
|
||||
}
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links.firstAssignmentChecked) {
|
||||
links.firstAssignmentChecked = true;
|
||||
links.firstAssignment = findFirstAssignment(symbol, declarationContainer);
|
||||
}
|
||||
if (links.firstAssignment && links.firstAssignment.end <= reference.pos) {
|
||||
return;
|
||||
}
|
||||
error(reference, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
|
||||
}
|
||||
|
||||
function checkIdentifier(node: Identifier): Type {
|
||||
const symbol = getResolvedSymbol(node);
|
||||
|
||||
@ -7800,10 +7714,18 @@ namespace ts {
|
||||
checkNestedBlockScopedBinding(node, symbol);
|
||||
|
||||
const type = getTypeOfSymbol(localOrExportSymbol);
|
||||
if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined)) {
|
||||
checkVariableAssignedBefore(symbol, node);
|
||||
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) {
|
||||
return type;
|
||||
}
|
||||
return getNarrowedTypeOfReference(type, node);
|
||||
const declaration = localOrExportSymbol.valueDeclaration;
|
||||
const defaultsToDeclaredType = !strictNullChecks || !declaration ||
|
||||
declaration.kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
|
||||
getContainingFunction(declaration) !== getContainingFunction(node);
|
||||
const flowType = getFlowTypeOfReference(node, type, defaultsToDeclaredType ? type : undefinedType);
|
||||
if (strictNullChecks && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
|
||||
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
|
||||
}
|
||||
return flowType;
|
||||
}
|
||||
|
||||
function isInsideFunction(node: Node, threshold: Node): boolean {
|
||||
@ -8715,8 +8637,10 @@ namespace ts {
|
||||
return mapper && mapper.context;
|
||||
}
|
||||
|
||||
// Return the root assignment node of an assignment target
|
||||
function getAssignmentRoot(node: Node): Node {
|
||||
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
|
||||
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
|
||||
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
|
||||
function isAssignmentTarget(node: Node): boolean {
|
||||
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
|
||||
node = node.parent;
|
||||
}
|
||||
@ -8734,23 +8658,7 @@ namespace ts {
|
||||
const parent = node.parent;
|
||||
return parent.kind === SyntaxKind.BinaryExpression &&
|
||||
(<BinaryExpression>parent).operatorToken.kind === SyntaxKind.EqualsToken &&
|
||||
(<BinaryExpression>parent).left === node ? parent : undefined;
|
||||
}
|
||||
|
||||
// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
|
||||
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
|
||||
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'.
|
||||
function isAssignmentTarget(node: Node): boolean {
|
||||
return !!getAssignmentRoot(node);
|
||||
}
|
||||
|
||||
function isCompoundAssignmentTarget(node: Node) {
|
||||
const parent = node.parent;
|
||||
if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).left === node) {
|
||||
const operator = (<BinaryExpression>parent).operatorToken.kind;
|
||||
return operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment;
|
||||
}
|
||||
return false;
|
||||
(<BinaryExpression>parent).left === node;
|
||||
}
|
||||
|
||||
function checkSpreadElementExpression(node: SpreadElementExpression, contextualMapper?: TypeMapper): Type {
|
||||
@ -9604,7 +9512,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
const propType = getTypeOfSymbol(prop);
|
||||
return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & SymbolFlags.Property ?
|
||||
return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & SymbolFlags.Property && !isAssignmentTarget(node) ?
|
||||
getNarrowedTypeOfReference(propType, <PropertyAccessExpression>node) : propType;
|
||||
}
|
||||
|
||||
@ -16177,7 +16085,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (entityName.parent.kind === SyntaxKind.ExportAssignment) {
|
||||
return resolveEntityName(<Identifier>entityName,
|
||||
return resolveEntityName(<Identifier><EntityName>entityName,
|
||||
/*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
|
||||
}
|
||||
|
||||
|
||||
@ -449,6 +449,7 @@ namespace ts {
|
||||
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
|
||||
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
|
||||
/* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes)
|
||||
/* @internal */ flowNode?: FlowNode; // Associated FlowNode (initialized by binding)
|
||||
}
|
||||
|
||||
export interface NodeArray<T> extends Array<T>, TextRange {
|
||||
@ -1518,6 +1519,39 @@ namespace ts {
|
||||
isBracketed: boolean;
|
||||
}
|
||||
|
||||
export const enum FlowKind {
|
||||
Unreachable,
|
||||
Start,
|
||||
Label,
|
||||
Assignment,
|
||||
Condition
|
||||
}
|
||||
|
||||
export interface FlowNode {
|
||||
kind: FlowKind; // Node kind
|
||||
id?: number; // Node id used by flow type cache in checker
|
||||
}
|
||||
|
||||
// FlowLabel represents a junction with multiple possible preceding control flows.
|
||||
export interface FlowLabel extends FlowNode {
|
||||
antecedents: FlowNode[];
|
||||
}
|
||||
|
||||
// FlowAssignment represents a node that possibly assigns a value to one or more
|
||||
// references.
|
||||
export interface FlowAssignment extends FlowNode {
|
||||
node: BinaryExpression | VariableDeclaration | ForInStatement | ForOfStatement;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
// FlowCondition represents a condition that is known to be true or false at the
|
||||
// node's location in the control flow.
|
||||
export interface FlowCondition extends FlowNode {
|
||||
expression: Expression;
|
||||
assumeTrue: boolean;
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
export interface AmdDependency {
|
||||
path: string;
|
||||
name: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user