Added control flow support for asserts. Added different flow flags for branch vs loop labels. Simplified and fixed bugs in the while/else control flow.

Changed control flow handling of return and yield statements.
This commit is contained in:
Eric Traut 2019-10-26 12:39:49 -07:00
parent bb7e9efc3f
commit 2de28529e9
3 changed files with 117 additions and 77 deletions

View File

@ -25,19 +25,21 @@ import { convertOffsetsToRange } from '../common/positionUtils';
import { PythonVersion } from '../common/pythonVersion';
import StringMap from '../common/stringMap';
import { TextRange } from '../common/textRange';
import { ArgumentCategory, AssignmentExpressionNode, AssignmentNode, AugmentedAssignmentExpressionNode,
AwaitExpressionNode, BinaryExpressionNode, BreakNode, ClassNode, ContinueNode, DelNode, ExceptNode,
ExpressionNode, ForNode, FunctionNode, GlobalNode, IfNode, ImportAsNode, ImportFromNode,
LambdaNode, ListComprehensionNode, MemberAccessExpressionNode, ModuleNameNode, ModuleNode,
NameNode, NonlocalNode, ParseNode, ParseNodeType, RaiseNode, ReturnNode, StatementNode,
StringListNode, SuiteNode, TernaryExpressionNode, TryNode, TypeAnnotationExpressionNode, UnaryExpressionNode,
import { ArgumentCategory, AssertNode, AssignmentExpressionNode, AssignmentNode,
AugmentedAssignmentExpressionNode, AwaitExpressionNode, BinaryExpressionNode, BreakNode,
ClassNode, ContinueNode, DelNode, ExceptNode, ExpressionNode, ForNode, FunctionNode,
GlobalNode, IfNode, ImportAsNode, ImportFromNode, LambdaNode, ListComprehensionNode,
MemberAccessExpressionNode, ModuleNameNode, ModuleNode, NameNode, NonlocalNode, ParseNode,
ParseNodeType, RaiseNode, ReturnNode, StatementNode, StringListNode, SuiteNode,
TernaryExpressionNode, TryNode, TypeAnnotationExpressionNode, UnaryExpressionNode,
WhileNode, WithNode, YieldExpressionNode, YieldFromExpressionNode } from '../parser/parseNodes';
import * as StringTokenUtils from '../parser/stringTokenUtils';
import { KeywordType, OperatorType, StringTokenFlags } from '../parser/tokenizerTypes';
import { AnalyzerFileInfo } from './analyzerFileInfo';
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
import { FlowAssignment, FlowCondition, FlowFlags, FlowLabel, FlowNode, FlowStart, FlowWildcardImport } from './codeFlow';
import { AliasDeclaration, DeclarationType, ModuleLoaderActions,
import { FlowAssignment, FlowCondition, FlowFlags, FlowLabel, FlowNode, FlowStart,
FlowWildcardImport } from './codeFlow';
import { AliasDeclaration, DeclarationType, FunctionDeclaration, ModuleLoaderActions,
VariableDeclaration } from './declaration';
import * as DocStringUtils from './docStringUtils';
import { ImplicitImport, ImportResult, ImportType } from './importResult';
@ -98,13 +100,10 @@ export class Binder extends ParseTreeWalker {
// Current control-flow node.
private _currentFlowNode: FlowNode;
// Flow node label that is the target of all return
// statements (including implicit returns).
private _currentReturnTarget?: FlowLabel;
// Flow node label that is the target of all yield
// statements.
private _currentYieldTarget?: FlowLabel;
// Current target function declaration, if currently binding
// a function. This allows return and yield statements to be
// added to the function declaration.
private _targetFunctionDeclaration: FunctionDeclaration | undefined;
// Flow node label that is the target of a "break" statement.
private _currentBreakTarget?: FlowLabel;
@ -357,22 +356,28 @@ export class Binder extends ParseTreeWalker {
functionFlags &= ~FunctionTypeFlags.InstanceMethod;
}
const savedTargetFunctionDeclaration = this._targetFunctionDeclaration;
this._targetFunctionDeclaration = undefined;
const functionType = FunctionType.create(functionFlags,
this._getDocString(node.suite.statements));
const symbol = this._bindNameToScope(this._currentScope, node.name.nameToken.value);
if (symbol) {
if (!this._isUnexecutedCode) {
const containingClassNode = ParseTreeUtils.getEnclosingClass(node, true);
const declarationType = containingClassNode ?
DeclarationType.Method : DeclarationType.Function;
symbol.addDeclaration({
const functionDeclaration: FunctionDeclaration = {
type: declarationType,
node,
path: this._fileInfo.filePath,
range: convertOffsetsToRange(node.name.start, TextRange.getEnd(node.name),
this._fileInfo.lines)
});
};
this._targetFunctionDeclaration = functionDeclaration;
if (symbol) {
symbol.addDeclaration(functionDeclaration);
}
}
@ -468,27 +473,18 @@ export class Binder extends ParseTreeWalker {
}
});
// Create a control flow label that represents the target
// of all return statements (including implicit returns).
this._currentReturnTarget = this._createFlowLabel();
AnalyzerNodeInfo.setFlowNode(node, this._currentReturnTarget);
// Create a control flow label that represents the target
// of all yield statements.
this._currentYieldTarget = this._createFlowLabel();
// Create a start node for the function.
this._currentFlowNode = this._createStartFlowNode(node);
// Walk the statements that make up the function.
this.walk(node.suite);
this._currentReturnTarget = undefined;
this._currentYieldTarget = undefined;
});
});
// We'll walk the child nodes in a deffered manner.
this._targetFunctionDeclaration = savedTargetFunctionDeclaration;
// We'll walk the child nodes in a deffered manner, so don't walk
// them now.
return false;
}
@ -594,9 +590,9 @@ export class Binder extends ParseTreeWalker {
this.walk(node.iterableExpression);
const preLoopLabel = this._createFlowLabel();
const postLoopLabel = this._createFlowLabel();
const postForElseLabel = this._createFlowLabel();
const preLoopLabel = this._createLoopLabel();
const postLoopLabel = this._createBranchLabel();
const postForElseLabel = this._createBranchLabel();
const preElseFlowNode = this._currentFlowNode;
this._addAntecedent(preLoopLabel, this._currentFlowNode);
@ -639,8 +635,11 @@ export class Binder extends ParseTreeWalker {
}
visitReturn(node: ReturnNode): boolean {
if (this._currentReturnTarget) {
this._addAntecedent(this._currentReturnTarget, this._currentFlowNode);
if (this._targetFunctionDeclaration) {
if (!this._targetFunctionDeclaration.returnExpressions) {
this._targetFunctionDeclaration.returnExpressions = [];
}
this._targetFunctionDeclaration.returnExpressions.push(node);
}
this._currentFlowNode = Binder._unreachableFlowNode;
@ -650,17 +649,35 @@ export class Binder extends ParseTreeWalker {
visitYield(node: YieldExpressionNode): boolean {
this._validateYieldUsage(node);
if (this._currentYieldTarget) {
this._addAntecedent(this._currentYieldTarget, this._currentFlowNode);
if (this._targetFunctionDeclaration) {
if (!this._targetFunctionDeclaration.yieldExpressions) {
this._targetFunctionDeclaration.yieldExpressions = [];
}
this._targetFunctionDeclaration.yieldExpressions.push(node);
}
return true;
}
visitMemberAccess(node: MemberAccessExpressionNode): boolean {
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode);
return true;
}
visitName(node: NameNode): boolean {
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode);
// Names have no children.
return false;
}
visitYieldFrom(node: YieldFromExpressionNode): boolean {
this._validateYieldUsage(node);
if (this._currentYieldTarget) {
this._addAntecedent(this._currentYieldTarget, this._currentFlowNode);
if (this._targetFunctionDeclaration) {
if (!this._targetFunctionDeclaration.yieldExpressions) {
this._targetFunctionDeclaration.yieldExpressions = [];
}
this._targetFunctionDeclaration.yieldExpressions.push(node);
}
return true;
}
@ -675,6 +692,21 @@ export class Binder extends ParseTreeWalker {
return false;
}
visitAssert(node: AssertNode): boolean {
const assertTrueLabel = this._createBranchLabel();
const assertFalseLabel = this._createBranchLabel();
this._bindConditional(node.testExpression, assertTrueLabel, assertFalseLabel);
if (node.exceptionExpression) {
this._currentFlowNode = this._finishFlowLabel(assertFalseLabel);
this.walk(node.exceptionExpression);
}
this._currentFlowNode = this._finishFlowLabel(assertTrueLabel);
return false;
}
visitExcept(node: ExceptNode): boolean {
if (node.name) {
const symbol = this._bindNameToScope(this._currentScope, node.name.nameToken.value);
@ -710,8 +742,8 @@ export class Binder extends ParseTreeWalker {
visitTry(node: TryNode): boolean {
// Create one flow label for every except clause.
const curExceptTargets = node.exceptClauses.map(() => this._createFlowLabel());
const preFinallyLabel = this._createFlowLabel();
const curExceptTargets = node.exceptClauses.map(() => this._createBranchLabel());
const preFinallyLabel = this._createBranchLabel();
// Handle the try block.
const prevExceptTargets = this._currentExceptTargets;
@ -1049,9 +1081,9 @@ export class Binder extends ParseTreeWalker {
}
visitTernary(node: TernaryExpressionNode): boolean {
const trueLabel = this._createFlowLabel();
const falseLabel = this._createFlowLabel();
const postExpressionLabel = this._createFlowLabel();
const trueLabel = this._createBranchLabel();
const falseLabel = this._createBranchLabel();
const postExpressionLabel = this._createBranchLabel();
// Handle the test expression.
this._bindConditional(node.testExpression, trueLabel, falseLabel);
@ -1093,7 +1125,7 @@ export class Binder extends ParseTreeWalker {
visitBinaryOperation(node: BinaryExpressionNode): boolean {
if (node.operator === OperatorType.And || node.operator === OperatorType.Or) {
if (this._isTopLevelLogicalExpression(node)) {
const postExpressionLabel = this._createFlowLabel();
const postExpressionLabel = this._createBranchLabel();
this._bindLogicalExpression(node, postExpressionLabel, postExpressionLabel);
this._currentFlowNode = this._finishFlowLabel(postExpressionLabel);
} else {
@ -1111,7 +1143,7 @@ export class Binder extends ParseTreeWalker {
// Allocate a new scope.
const prevScope = this._currentScope;
this._currentScope = new Scope(ScopeType.ListComprehension, prevScope);
const falseLabel = this._createFlowLabel();
const falseLabel = this._createBranchLabel();
for (let i = 0; i < node.comprehensions.length; i++) {
const compr = node.comprehensions[i];
@ -1122,7 +1154,7 @@ export class Binder extends ParseTreeWalker {
this._createAssignmentTargetFlowNodes(compr.targetExpression);
this.walk(compr.targetExpression);
} else {
const trueLabel = this._createFlowLabel();
const trueLabel = this._createBranchLabel();
this._bindConditional(compr.testExpression, trueLabel, falseLabel);
this._currentFlowNode = this._finishFlowLabel(trueLabel);
}
@ -1147,9 +1179,17 @@ export class Binder extends ParseTreeWalker {
return flowNode;
}
private _createFlowLabel() {
private _createBranchLabel() {
const flowNode: FlowLabel = {
flags: FlowFlags.Label,
flags: FlowFlags.BranchLabel,
antecedents: []
};
return flowNode;
}
private _createLoopLabel() {
const flowNode: FlowLabel = {
flags: FlowFlags.LoopLabel,
antecedents: []
};
return flowNode;
@ -1253,7 +1293,7 @@ export class Binder extends ParseTreeWalker {
private _bindLogicalExpression(node: BinaryExpressionNode,
trueTarget: FlowLabel, falseTarget: FlowLabel) {
const preRightLabel = this._createFlowLabel();
const preRightLabel = this._createBranchLabel();
if (node.operator === OperatorType.And) {
this._bindConditional(node.leftExpression, preRightLabel, falseTarget);
} else {
@ -1881,29 +1921,25 @@ export class Binder extends ParseTreeWalker {
private _handleIfWhileCommon(testExpression: ExpressionNode, ifWhileSuite: SuiteNode,
elseSuite: SuiteNode | IfNode | undefined, isWhile: boolean) {
const thenLabel = this._createFlowLabel();
const elseLabel = this._createFlowLabel();
const postIfLabel = this._createFlowLabel();
this._bindConditional(testExpression, thenLabel, elseLabel);
const thenLabel = isWhile ? this._createLoopLabel() : this._createBranchLabel();
const elseLabel = this._createBranchLabel();
const postIfLabel = this._createBranchLabel();
// Determine if the if condition is always true or always false. If so,
// we can treat either the if or the else clause as unconditional.
const constExprValue = StaticExpressions.evaluateStaticExpression(
testExpression, this._fileInfo.executionEnvironment);
this._bindConditional(testExpression, thenLabel, elseLabel);
this._currentFlowNode = this._finishFlowLabel(thenLabel);
if (isWhile) {
const preWhileLabel = this._createFlowLabel();
this._currentFlowNode = preWhileLabel;
this._bindConditional(testExpression, thenLabel, postIfLabel);
this._bindLoopStatement(preWhileLabel, postIfLabel, () => {
this._bindLoopStatement(thenLabel, postIfLabel, () => {
this._markNotExecuted(constExprValue !== false, () => {
this.walk(ifWhileSuite);
});
});
this._addAntecedent(preWhileLabel, this._currentFlowNode);
this._addAntecedent(thenLabel, this._currentFlowNode);
} else {
this._currentFlowNode = this._finishFlowLabel(thenLabel);
this._markNotExecuted(constExprValue !== false, () => {
this.walk(ifWhileSuite);
});

View File

@ -19,11 +19,12 @@ import { ExpressionNode, FunctionNode, ImportFromNode, LambdaNode,
export enum FlowFlags {
Unreachable = 1 << 0, // Unreachable code
Start = 1 << 1, // Entry point
Label = 1 << 2, // Junction
Assignment = 1 << 3, // Assignment statement
WildcardImport = 1 << 4, // For "from X import *" statements
TrueCondition = 1 << 5, // Condition known to be true
FalseCondition = 1 << 6 // Condition known to be false
BranchLabel = 1 << 2, // Junction for forward control flow
LoopLabel = 1 << 3, // Junction for backward control flow
Assignment = 1 << 4, // Assignment statement
WildcardImport = 1 << 5, // For "from X import *" statements
TrueCondition = 1 << 6, // Condition known to be true
FalseCondition = 1 << 7 // Condition known to be false
}
export interface FlowNodeBase {

View File

@ -11,7 +11,8 @@
import { DiagnosticTextRange } from '../common/diagnostic';
import { ClassNode, ExpressionNode, FunctionNode, NameNode,
ParameterNode, ParseNode, StringListNode } from '../parser/parseNodes';
ParameterNode, ParseNode, ReturnNode, StringListNode, YieldExpressionNode,
YieldFromExpressionNode } from '../parser/parseNodes';
import { Type } from './types';
export const enum DeclarationType {
@ -51,6 +52,8 @@ export interface ClassDeclaration extends DeclarationBase {
export interface FunctionDeclaration extends DeclarationBase {
type: DeclarationType.Function | DeclarationType.Method;
node: FunctionNode;
returnExpressions?: ReturnNode[];
yieldExpressions?: (YieldExpressionNode | YieldFromExpressionNode)[];
}
export interface ParameterDeclaration extends DeclarationBase {