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

View File

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

View File

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