mirror of
https://github.com/microsoft/pyright.git
synced 2024-08-17 03:40:44 +03:00
Completely eliminated postParseWalker, moving all name binding into the binder module.
This commit is contained in:
parent
9fa224cf80
commit
92a0b6e4be
@ -31,9 +31,7 @@ The [tokenizer](https://github.com/Microsoft/pyright/blob/master/server/src/pars
|
||||
|
||||
The [parser](https://github.com/Microsoft/pyright/blob/master/server/src/parser/parser.ts) is responsible for converting the token stream into a parse tree. A generalized [parseTreeWalker](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/parseTreeWalker.ts) provides a convenient way to traverse the parse tree. All subsequent analysis phases utilize the parseTreeWalker.
|
||||
|
||||
The [postParseWalker](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/postParseWalker.ts) adds parent links to parse tree nodes and builds name bindings for names that appear within modules, classes and functions. It also detects and reports inconsistent name bindings (e.g. a variable that uses both a global and nonlocal binding in the same scope). It is also responsible for creating a list of all imports, allowing the program object to resolve these imports (using the importResolver) and add the imported source files to the program.
|
||||
|
||||
The [binder](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/binder.ts) is responsible for building scopes and associated symbol tables and populating those symbol tables. It does not perform any type checking, but it detects and reports other semantic errors that will result in unintended runtime exceptions.
|
||||
The [binder](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/binder.ts) is responsible for building scopes (and associated symbol tables) and populating symbol tables. It does not perform any type checking, but it detects and reports other semantic errors that will result in unintended runtime exceptions. It also detects and reports inconsistent name bindings (e.g. a variable that uses both a global and nonlocal binding in the same scope).
|
||||
|
||||
The [typeAnalyzer](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/typeAnalyzer.ts) is responsible for interpreting type annotations, performing type inference, and reporting type inconsistencies. Unlike all previous passes, the typeAnalyzer pass runs multiple times — at least twice per file. This is necessary because type annotations can contain forward references within a file and because Python supports circular import dependencies across files. The typeAnalyzer therefore runs multiple times until all type information “converges”, and no new information is discovered.
|
||||
|
||||
|
@ -10,9 +10,7 @@
|
||||
* language services (e.g. hover information).
|
||||
*/
|
||||
|
||||
import { NameBindings } from '../parser/nameBindings';
|
||||
import { ParseNode, ParseNodeType, StringListNode } from '../parser/parseNodes';
|
||||
import { Declaration } from './declaration';
|
||||
import { ParseNode, StringListNode } from '../parser/parseNodes';
|
||||
import { ImportResult } from './importResult';
|
||||
import { TypeSourceId } from './inferredType';
|
||||
import { Scope, ScopeType } from './scope';
|
||||
@ -22,14 +20,6 @@ import { Type } from './types';
|
||||
let _nextTypeSourceId: TypeSourceId = 1;
|
||||
|
||||
export class AnalyzerNodeInfo {
|
||||
//---------------------------------------------------------------
|
||||
// Set by PostParseWalker
|
||||
|
||||
// Name bindings for nodes that introduce namespaces (modules,
|
||||
// functions, classes and lambdas). It records the static binding
|
||||
// type (local, non-local, global) for names used within that scope.
|
||||
_nameBindings?: NameBindings;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Set as part of import resolution
|
||||
|
||||
@ -80,16 +70,6 @@ export class AnalyzerNodeInfo {
|
||||
delete analyzerNode._ignoreTypeAnnotation;
|
||||
}
|
||||
|
||||
static getNameBindings(node: ParseNode): NameBindings | undefined {
|
||||
const analyzerNode = node as AnalyzerNodeInfo;
|
||||
return analyzerNode._nameBindings;
|
||||
}
|
||||
|
||||
static setNameBindings(node: ParseNode, nameBindings: NameBindings) {
|
||||
const analyzerNode = node as AnalyzerNodeInfo;
|
||||
analyzerNode._nameBindings = nameBindings;
|
||||
}
|
||||
|
||||
static getScope(node: ParseNode): Scope | undefined {
|
||||
const analyzerNode = node as AnalyzerNodeInfo;
|
||||
return analyzerNode._scope;
|
||||
|
@ -23,10 +23,19 @@ import { CreateTypeStubFileAction } from '../common/diagnostic';
|
||||
import { DiagnosticRule } from '../common/diagnosticRules';
|
||||
import { PythonVersion } from '../common/pythonVersion';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { AwaitExpressionNode, ClassNode, ExpressionNode, FunctionNode,
|
||||
GlobalNode, IfNode, LambdaNode, ModuleNameNode, ModuleNode, NonlocalNode, ParseNode,
|
||||
ParseNodeArray, ParseNodeType, RaiseNode, StatementNode, StringListNode, SuiteNode,
|
||||
TryNode, WhileNode, YieldExpressionNode,
|
||||
import { NameBindings, NameBindingType } from '../parser/nameBindings';
|
||||
import { AssignmentNode, AugmentedAssignmentExpressionNode, AwaitExpressionNode, ClassNode,
|
||||
DelNode, ExpressionNode, ForNode, FunctionNode, GlobalNode, IfNode, ImportAsNode,
|
||||
ImportFromAsNode, LambdaNode, ModuleNameNode, ModuleNode, NonlocalNode, ParseNode,
|
||||
ParseNodeArray, ParseNodeType, RaiseNode,
|
||||
StatementNode,
|
||||
StringListNode,
|
||||
SuiteNode,
|
||||
TryNode,
|
||||
TypeAnnotationExpressionNode,
|
||||
WhileNode,
|
||||
WithNode,
|
||||
YieldExpressionNode,
|
||||
YieldFromExpressionNode } from '../parser/parseNodes';
|
||||
import { StringTokenUtils, UnescapeErrorType } from '../parser/stringTokenUtils';
|
||||
import { StringTokenFlags } from '../parser/tokenizerTypes';
|
||||
@ -65,13 +74,18 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
// because it's in an unexecuted section of code.
|
||||
protected _isUnexecutedCode = false;
|
||||
|
||||
// Name binding information used within the current scope.
|
||||
protected _nameBindings: NameBindings;
|
||||
|
||||
constructor(node: ScopedNode, scopeType: ScopeType, parentScope: Scope | undefined,
|
||||
nameBindingType: NameBindingType, parentBindings: NameBindings | undefined,
|
||||
fileInfo: AnalyzerFileInfo) {
|
||||
|
||||
super();
|
||||
|
||||
this._scopedNode = node;
|
||||
this._fileInfo = fileInfo;
|
||||
this._nameBindings = new NameBindings(nameBindingType, parentBindings);
|
||||
|
||||
// Allocate a new scope and associate it with the node
|
||||
// we've been asked to analyze.
|
||||
@ -185,6 +199,8 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
AnalyzerNodeInfo.getTypeSourceId(node),
|
||||
this._getDocString(node.suite.statements));
|
||||
|
||||
this._bindName(node.name.nameToken.value);
|
||||
|
||||
this.walkMultiple(node.arguments);
|
||||
|
||||
let sawMetaclass = false;
|
||||
@ -226,7 +242,8 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
|
||||
AnalyzerNodeInfo.setExpressionType(node, classType);
|
||||
|
||||
const binder = new ClassScopeBinder(node, this._currentScope, classType, this._fileInfo);
|
||||
const binder = new ClassScopeBinder(node, this._currentScope, classType,
|
||||
this._nameBindings, this._fileInfo);
|
||||
this._queueSubScopeAnalyzer(binder);
|
||||
|
||||
// Add the class symbol. We do this in the binder to speed
|
||||
@ -254,6 +271,8 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
const functionType = new FunctionType(functionFlags,
|
||||
this._getDocString(node.suite.statements));
|
||||
|
||||
this._bindName(node.name.nameToken.value);
|
||||
|
||||
this.walkMultiple(node.decorators);
|
||||
node.parameters.forEach(param => {
|
||||
this._addParentLinks(param, [param.name, param.typeAnnotation,
|
||||
@ -301,7 +320,8 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
const functionOrModuleScope = AnalyzerNodeInfo.getScope(functionOrModuleNode!);
|
||||
assert(functionOrModuleScope !== undefined);
|
||||
|
||||
const binder = new FunctionScopeBinder(node, functionOrModuleScope!, this._fileInfo);
|
||||
const binder = new FunctionScopeBinder(node, functionOrModuleScope!,
|
||||
this._nameBindings, this._fileInfo);
|
||||
this._queueSubScopeAnalyzer(binder);
|
||||
|
||||
return false;
|
||||
@ -321,12 +341,41 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
}
|
||||
});
|
||||
|
||||
const binder = new LambdaScopeBinder(node, this._currentScope, this._fileInfo);
|
||||
const binder = new LambdaScopeBinder(node, this._currentScope,
|
||||
this._nameBindings, this._fileInfo);
|
||||
this._queueSubScopeAnalyzer(binder);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
visitAssignment(node: AssignmentNode) {
|
||||
this._bindPossibleTupleNamedTarget(node.leftExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitAugmentedAssignment(node: AugmentedAssignmentExpressionNode) {
|
||||
this._bindPossibleTupleNamedTarget(node.leftExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitDel(node: DelNode) {
|
||||
node.expressions.forEach(expr => {
|
||||
this._bindPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
visitTypeAnnotation(node: TypeAnnotationExpressionNode): boolean {
|
||||
this._bindPossibleTupleNamedTarget(node.valueExpression);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitFor(node: ForNode) {
|
||||
this._bindPossibleTupleNamedTarget(node.targetExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitYield(node: YieldExpressionNode): boolean {
|
||||
this._validateYieldUsage(node);
|
||||
return true;
|
||||
@ -433,6 +482,23 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
|
||||
visitGlobal(node: GlobalNode): boolean {
|
||||
node.nameList.forEach(name => {
|
||||
if (!this._nameBindings.addName(name.nameToken.value, NameBindingType.Global)) {
|
||||
this._addError(`'${ name.nameToken.value }' is assigned before global declaration`,
|
||||
name);
|
||||
}
|
||||
|
||||
// Add it to the global scope as well, in case it's not already added there.
|
||||
if (this._nameBindings.getBindingType() !== NameBindingType.Global) {
|
||||
let globalScope: NameBindings | undefined = this._nameBindings;
|
||||
while (globalScope && globalScope.getBindingType() !== NameBindingType.Global) {
|
||||
globalScope = globalScope.getParentScope();
|
||||
}
|
||||
|
||||
if (globalScope) {
|
||||
globalScope.addName(name.nameToken.value, NameBindingType.Global);
|
||||
}
|
||||
}
|
||||
|
||||
const valueWithScope = this._currentScope.lookUpSymbolRecursive(name.nameToken.value);
|
||||
|
||||
if (!valueWithScope || valueWithScope.scope.getType() !== ScopeType.Module) {
|
||||
@ -444,7 +510,16 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
visitNonlocal(node: NonlocalNode): boolean {
|
||||
if (this._nameBindings.getBindingType() === NameBindingType.Global) {
|
||||
this._addError('Nonlocal declaration not allowed at module level', node);
|
||||
}
|
||||
|
||||
node.nameList.forEach(name => {
|
||||
if (!this._nameBindings.addName(name.nameToken.value, NameBindingType.Nonlocal)) {
|
||||
this._addError(`'${ name.nameToken.value }' is assigned before nonlocal declaration`,
|
||||
name);
|
||||
}
|
||||
|
||||
const valueWithScope = this._currentScope.lookUpSymbolRecursive(name.nameToken.value);
|
||||
|
||||
if (!valueWithScope || (valueWithScope.scope.getType() !== ScopeType.Function &&
|
||||
@ -457,6 +532,33 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
return true;
|
||||
}
|
||||
|
||||
visitImportAs(node: ImportAsNode): boolean {
|
||||
if (node.alias) {
|
||||
this._bindName(node.alias.nameToken.value);
|
||||
} else if (node.module.nameParts.length > 0) {
|
||||
this._bindName(node.module.nameParts[0].nameToken.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitImportFromAs(node: ImportFromAsNode): boolean {
|
||||
const nameNode = node.alias || node.name;
|
||||
this._bindName(nameNode.nameToken.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitWith(node: WithNode): boolean {
|
||||
node.withItems.forEach(item => {
|
||||
if (item.target) {
|
||||
this._bindPossibleTupleNamedTarget(item.target);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected _addNamesToScope(namesToAdd: string[]) {
|
||||
// Add the names for this scope. They are initially unbound.
|
||||
namesToAdd.forEach(name => {
|
||||
@ -478,6 +580,33 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
this._subscopesToAnalyze = [];
|
||||
}
|
||||
|
||||
protected _bindPossibleTupleNamedTarget(node: ExpressionNode) {
|
||||
if (node.nodeType === ParseNodeType.Name) {
|
||||
this._bindName(node.nameToken.value);
|
||||
} else if (node.nodeType === ParseNodeType.Tuple) {
|
||||
node.expressions.forEach(expr => {
|
||||
this._bindPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
} else if (node.nodeType === ParseNodeType.List) {
|
||||
node.entries.forEach(expr => {
|
||||
this._bindPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
} else if (node.nodeType === ParseNodeType.TypeAnnotation) {
|
||||
this._bindPossibleTupleNamedTarget(node.valueExpression);
|
||||
} else if (node.nodeType === ParseNodeType.Unpack) {
|
||||
this._bindPossibleTupleNamedTarget(node.expression);
|
||||
}
|
||||
}
|
||||
|
||||
protected _bindName(name: string) {
|
||||
// Has this name already been added to the current scope? If not,
|
||||
// add it with the appropriate binding type.
|
||||
const bindingType = this._nameBindings.lookUpName(name);
|
||||
if (bindingType === undefined) {
|
||||
this._nameBindings.addName(name, this._nameBindings.getBindingType());
|
||||
}
|
||||
}
|
||||
|
||||
// Finds the nearest permanent scope (as opposed to temporary scope) and
|
||||
// adds a new symbol with the specified name if it doesn't already exist.
|
||||
protected _addSymbolToPermanentScope(nameValue: string, type: Type,
|
||||
@ -621,7 +750,7 @@ export abstract class Binder extends ParseTreeWalker {
|
||||
export class ModuleScopeBinder extends Binder {
|
||||
constructor(node: ModuleNode, fileInfo: AnalyzerFileInfo) {
|
||||
super(node, fileInfo.builtinsScope ? ScopeType.Module : ScopeType.BuiltIn,
|
||||
fileInfo.builtinsScope, fileInfo);
|
||||
fileInfo.builtinsScope, NameBindingType.Global, undefined, fileInfo);
|
||||
}
|
||||
|
||||
bind() {
|
||||
@ -631,9 +760,6 @@ export class ModuleScopeBinder extends Binder {
|
||||
|
||||
bindImmediate() {
|
||||
this._bindImplicitNames();
|
||||
const nameBindings = AnalyzerNodeInfo.getNameBindings(this._scopedNode);
|
||||
assert(nameBindings !== undefined);
|
||||
this._addNamesToScope(nameBindings!.getGlobalNames());
|
||||
|
||||
const moduleNode = this._scopedNode as ModuleNode;
|
||||
this._addParentLinks(moduleNode, moduleNode.statements);
|
||||
@ -643,6 +769,8 @@ export class ModuleScopeBinder extends Binder {
|
||||
const moduleType = new ModuleType(this._currentScope.getSymbolTable(),
|
||||
this._getDocString((this._scopedNode as ModuleNode).statements));
|
||||
AnalyzerNodeInfo.setExpressionType(this._scopedNode, moduleType);
|
||||
|
||||
this._addNamesToScope(this._nameBindings.getGlobalNames());
|
||||
}
|
||||
|
||||
bindDeferred() {
|
||||
@ -667,16 +795,16 @@ export class ModuleScopeBinder extends Binder {
|
||||
export class ClassScopeBinder extends Binder {
|
||||
private _classType: ClassType;
|
||||
|
||||
constructor(node: ClassNode, parentScope: Scope, classType: ClassType, fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Class, parentScope, fileInfo);
|
||||
constructor(node: ClassNode, parentScope: Scope, classType: ClassType,
|
||||
parentNameBindings: NameBindings, fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Class, parentScope, NameBindingType.Local,
|
||||
parentNameBindings, fileInfo);
|
||||
|
||||
this._classType = classType;
|
||||
}
|
||||
|
||||
bindImmediate() {
|
||||
this._bindImplicitNames();
|
||||
const nameBindings = AnalyzerNodeInfo.getNameBindings(this._scopedNode);
|
||||
assert(nameBindings !== undefined);
|
||||
this._addNamesToScope(nameBindings!.getLocalNames());
|
||||
|
||||
// Analyze the suite.
|
||||
const classNode = this._scopedNode as ClassNode;
|
||||
@ -685,7 +813,9 @@ export class ClassScopeBinder extends Binder {
|
||||
|
||||
// Record the class fields for this class.
|
||||
this._classType.setClassFields(this._currentScope.getSymbolTable());
|
||||
}
|
||||
|
||||
this._addNamesToScope(this._nameBindings.getLocalNames());
|
||||
}
|
||||
|
||||
bindDeferred() {
|
||||
// Analyze any sub-scopes that were discovered during the earlier pass.
|
||||
@ -706,8 +836,10 @@ export class ClassScopeBinder extends Binder {
|
||||
}
|
||||
|
||||
export class FunctionScopeBinder extends Binder {
|
||||
constructor(node: FunctionNode, parentScope: Scope, fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Function, parentScope, fileInfo);
|
||||
constructor(node: FunctionNode, parentScope: Scope, parentNameBindings: NameBindings,
|
||||
fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Function, parentScope, NameBindingType.Local,
|
||||
parentNameBindings, fileInfo);
|
||||
}
|
||||
|
||||
bindImmediate() {
|
||||
@ -719,16 +851,17 @@ export class FunctionScopeBinder extends Binder {
|
||||
bindDeferred() {
|
||||
const functionNode = this._scopedNode as FunctionNode;
|
||||
|
||||
// Add the names for this scope. They are initially unbound. We
|
||||
// do this because current versions of Python use static namespace
|
||||
// resolution for functions.
|
||||
const nameBindings = AnalyzerNodeInfo.getNameBindings(this._scopedNode);
|
||||
assert(nameBindings !== undefined);
|
||||
this._addNamesToScope(nameBindings!.getLocalNames());
|
||||
functionNode.parameters.forEach(param => {
|
||||
if (param.name) {
|
||||
this._bindName(param.name.nameToken.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Walk the statements that make up the function.
|
||||
this.walk(functionNode.suite);
|
||||
|
||||
this._addNamesToScope(this._nameBindings.getLocalNames());
|
||||
|
||||
// Analyze any sub-scopes that were discovered during the earlier pass.
|
||||
this._analyzeSubscopesDeferred();
|
||||
}
|
||||
@ -752,8 +885,10 @@ export class FunctionScopeBinder extends Binder {
|
||||
}
|
||||
|
||||
export class LambdaScopeBinder extends Binder {
|
||||
constructor(node: LambdaNode, parentScope: Scope, fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Function, parentScope, fileInfo);
|
||||
constructor(node: LambdaNode, parentScope: Scope, parentNameBindings: NameBindings,
|
||||
fileInfo: AnalyzerFileInfo) {
|
||||
super(node, ScopeType.Function, parentScope, NameBindingType.Local,
|
||||
parentNameBindings, fileInfo);
|
||||
}
|
||||
|
||||
bindImmediate() {
|
||||
@ -763,17 +898,18 @@ export class LambdaScopeBinder extends Binder {
|
||||
bindDeferred() {
|
||||
const lambdaNode = this._scopedNode as LambdaNode;
|
||||
|
||||
// Add the names for this scope. They are initially unbound. We
|
||||
// do this because current versions of Python use static namespace
|
||||
// resolution for functions.
|
||||
const nameBindings = AnalyzerNodeInfo.getNameBindings(this._scopedNode);
|
||||
assert(nameBindings !== undefined);
|
||||
this._addNamesToScope(nameBindings!.getLocalNames());
|
||||
lambdaNode.parameters.forEach(param => {
|
||||
if (param.name) {
|
||||
this._bindName(param.name.nameToken.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Walk the expression that make up the lambda body.
|
||||
this.walk(lambdaNode.expression);
|
||||
|
||||
// Analyze any sub-scopes that were discovered during the earlier pass.
|
||||
this._analyzeSubscopesDeferred();
|
||||
|
||||
this._addNamesToScope(this._nameBindings.getLocalNames());
|
||||
}
|
||||
}
|
||||
|
@ -1,249 +0,0 @@
|
||||
/*
|
||||
* postParseWalker.ts
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT license.
|
||||
* Author: Eric Traut
|
||||
*
|
||||
* A parse tree walker that's used immediately after generating
|
||||
* the parse tree, effectively as an extension of the parser.
|
||||
* It does the following:
|
||||
* Builds nameBindings for module, class, function and lambda scopes
|
||||
* Reports name binding inconsistencies (e.g. if a name is bound
|
||||
* both locally and globally)
|
||||
*/
|
||||
|
||||
import { TextRangeDiagnosticSink } from '../common/diagnosticSink';
|
||||
import { NameBindings, NameBindingType } from '../parser/nameBindings';
|
||||
import { AssignmentNode, AugmentedAssignmentExpressionNode, ClassNode, DelNode,
|
||||
ExpressionNode, ForNode, FunctionNode, GlobalNode, ImportAsNode,
|
||||
ImportFromAsNode, LambdaNode, ModuleNode, NonlocalNode, ParseNodeType,
|
||||
TypeAnnotationExpressionNode, WithNode } from '../parser/parseNodes';
|
||||
import { AnalyzerNodeInfo } from './analyzerNodeInfo';
|
||||
import { ParseTreeWalker } from './parseTreeWalker';
|
||||
|
||||
export class PostParseWalker extends ParseTreeWalker {
|
||||
private _parseTree: ModuleNode;
|
||||
private _diagnosticSink: TextRangeDiagnosticSink;
|
||||
private _currentNameBindings: NameBindings;
|
||||
private _currentBindingType: NameBindingType;
|
||||
|
||||
constructor(diagSink: TextRangeDiagnosticSink, parseTree: ModuleNode) {
|
||||
super();
|
||||
|
||||
this._diagnosticSink = diagSink;
|
||||
this._parseTree = parseTree;
|
||||
|
||||
const moduleNameBindings = new NameBindings(NameBindingType.Global, undefined);
|
||||
AnalyzerNodeInfo.setNameBindings(parseTree, moduleNameBindings);
|
||||
this._currentNameBindings = moduleNameBindings;
|
||||
this._currentBindingType = NameBindingType.Global;
|
||||
}
|
||||
|
||||
analyze() {
|
||||
this.walk(this._parseTree);
|
||||
}
|
||||
|
||||
visitImportAs(node: ImportAsNode): boolean {
|
||||
if (node.alias) {
|
||||
this._addName(node.alias.nameToken.value);
|
||||
} else if (node.module.nameParts.length > 0) {
|
||||
this._addName(node.module.nameParts[0].nameToken.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitImportFromAs(node: ImportFromAsNode): boolean {
|
||||
const nameNode = node.alias || node.name;
|
||||
this._addName(nameNode.nameToken.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitWith(node: WithNode): boolean {
|
||||
node.withItems.forEach(item => {
|
||||
if (item.target) {
|
||||
this._addPossibleTupleNamedTarget(item.target);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitFunction(node: FunctionNode): boolean {
|
||||
const nameBindings = new NameBindings(
|
||||
NameBindingType.Local, this._currentNameBindings);
|
||||
AnalyzerNodeInfo.setNameBindings(node, nameBindings);
|
||||
|
||||
// Decorators are executed in the scope outside the function.
|
||||
this.walkMultiple(node.decorators);
|
||||
|
||||
this._addName(node.name.nameToken.value);
|
||||
this._createNewScope(nameBindings, () => {
|
||||
// Populate the new scope with parameter names.
|
||||
node.parameters.forEach(param => {
|
||||
if (param.name) {
|
||||
this._addName(param.name.nameToken.value);
|
||||
}
|
||||
});
|
||||
|
||||
this.walkMultiple(node.parameters);
|
||||
|
||||
if (node.returnTypeAnnotation) {
|
||||
this.walk(node.returnTypeAnnotation);
|
||||
}
|
||||
|
||||
this.walk(node.suite);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
visitClass(node: ClassNode) {
|
||||
const nameBindings = new NameBindings(
|
||||
NameBindingType.Local, this._currentNameBindings);
|
||||
AnalyzerNodeInfo.setNameBindings(node, nameBindings);
|
||||
|
||||
// Decorators are executed in the scope outside the class.
|
||||
this.walkMultiple(node.decorators);
|
||||
|
||||
this._addName(node.name.nameToken.value);
|
||||
this._createNewScope(nameBindings, () => {
|
||||
this.walkMultiple(node.arguments);
|
||||
this.walk(node.suite);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
visitLambda(node: LambdaNode): boolean {
|
||||
const nameBindings = new NameBindings(
|
||||
NameBindingType.Local, this._currentNameBindings);
|
||||
AnalyzerNodeInfo.setNameBindings(node, nameBindings);
|
||||
|
||||
this._createNewScope(nameBindings, () => {
|
||||
// Populate the new scope with parameter names.
|
||||
node.parameters.forEach(param => {
|
||||
if (param.name) {
|
||||
this._addName(param.name.nameToken.value);
|
||||
}
|
||||
});
|
||||
|
||||
this.walkMultiple(node.parameters);
|
||||
|
||||
this.walk(node.expression);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
visitAssignment(node: AssignmentNode) {
|
||||
this._addPossibleTupleNamedTarget(node.leftExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitAugmentedAssignment(node: AugmentedAssignmentExpressionNode) {
|
||||
this._addPossibleTupleNamedTarget(node.leftExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitDel(node: DelNode) {
|
||||
node.expressions.forEach(expr => {
|
||||
this._addPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
visitTypeAnnotation(node: TypeAnnotationExpressionNode): boolean {
|
||||
this._addPossibleTupleNamedTarget(node.valueExpression);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
visitFor(node: ForNode) {
|
||||
this._addPossibleTupleNamedTarget(node.targetExpression);
|
||||
return true;
|
||||
}
|
||||
|
||||
visitGlobal(node: GlobalNode) {
|
||||
node.nameList.forEach(name => {
|
||||
if (!this._currentNameBindings.addName(name.nameToken.value, NameBindingType.Global)) {
|
||||
this._diagnosticSink.addErrorWithTextRange(
|
||||
`'${ name.nameToken.value }' is assigned before global declaration`,
|
||||
name);
|
||||
}
|
||||
|
||||
// Add it to the global scope as well, in case it's not already added there.
|
||||
if (this._currentNameBindings.getBindingType() !== NameBindingType.Global) {
|
||||
let globalScope: NameBindings | undefined = this._currentNameBindings;
|
||||
while (globalScope && globalScope.getBindingType() !== NameBindingType.Global) {
|
||||
globalScope = globalScope.getParentScope();
|
||||
}
|
||||
|
||||
if (globalScope) {
|
||||
globalScope.addName(name.nameToken.value, NameBindingType.Global);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
visitNonlocal(node: NonlocalNode) {
|
||||
const moduleNameBindings = AnalyzerNodeInfo.getNameBindings(this._parseTree);
|
||||
if (this._currentNameBindings === moduleNameBindings) {
|
||||
this._diagnosticSink.addErrorWithTextRange(
|
||||
'Nonlocal declaration not allowed at module level',
|
||||
node);
|
||||
} else {
|
||||
node.nameList.forEach(name => {
|
||||
if (!this._currentNameBindings.addName(name.nameToken.value, NameBindingType.Nonlocal)) {
|
||||
this._diagnosticSink.addErrorWithTextRange(
|
||||
`'${ name.nameToken.value }' is assigned before nonlocal declaration`,
|
||||
name);
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _addPossibleTupleNamedTarget(node: ExpressionNode) {
|
||||
if (node.nodeType === ParseNodeType.Name) {
|
||||
this._addName(node.nameToken.value);
|
||||
} else if (node.nodeType === ParseNodeType.Tuple) {
|
||||
node.expressions.forEach(expr => {
|
||||
this._addPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
} else if (node.nodeType === ParseNodeType.List) {
|
||||
node.entries.forEach(expr => {
|
||||
this._addPossibleTupleNamedTarget(expr);
|
||||
});
|
||||
} else if (node.nodeType === ParseNodeType.TypeAnnotation) {
|
||||
this._addPossibleTupleNamedTarget(node.valueExpression);
|
||||
} else if (node.nodeType === ParseNodeType.Unpack) {
|
||||
this._addPossibleTupleNamedTarget(node.expression);
|
||||
}
|
||||
}
|
||||
|
||||
private _addName(name: string) {
|
||||
// Has this name already been added to the current scope? If not,
|
||||
// add it with the appropriate binding type.
|
||||
const scopeType = this._currentNameBindings.lookUpName(name);
|
||||
if (scopeType === undefined) {
|
||||
this._currentNameBindings.addName(name, this._currentBindingType);
|
||||
}
|
||||
}
|
||||
|
||||
private _createNewScope(nameBindings: NameBindings, walkInnerScopeCallback: () => void) {
|
||||
const prevNameBindings = this._currentNameBindings;
|
||||
this._currentNameBindings = nameBindings;
|
||||
|
||||
const prevBindingScope = this._currentBindingType;
|
||||
this._currentBindingType = NameBindingType.Local;
|
||||
|
||||
walkInnerScopeCallback();
|
||||
|
||||
this._currentNameBindings = prevNameBindings;
|
||||
this._currentBindingType = prevBindingScope;
|
||||
return false;
|
||||
}
|
||||
}
|
@ -43,7 +43,6 @@ import { CommentUtils } from './commentUtils';
|
||||
import { ImportResolver } from './importResolver';
|
||||
import { ImportResult } from './importResult';
|
||||
import { ParseTreeCleanerWalker } from './parseTreeCleaner';
|
||||
import { PostParseWalker } from './postParseWalker';
|
||||
import { Scope } from './scope';
|
||||
import { SymbolTable } from './symbol';
|
||||
import { TypeAnalyzer } from './typeAnalyzer';
|
||||
@ -433,19 +432,10 @@ export class SourceFile {
|
||||
const parser = new Parser();
|
||||
const parseResults = parser.parseSourceFile(fileContents!, parseOptions, diagSink);
|
||||
|
||||
// Convert the diagnostic sink into one that knows how to convert
|
||||
// to line numbers.
|
||||
const textRangeDiagSink = new TextRangeDiagnosticSink(parseResults.lines, diagSink.diagnostics);
|
||||
|
||||
// Fill in the parent links and get the list of imports.
|
||||
const walker = new PostParseWalker(textRangeDiagSink, parseResults.parseTree);
|
||||
timingStats.postParseWalkerTime.timeOperation(() => {
|
||||
walker.analyze();
|
||||
});
|
||||
|
||||
// Save information in the analysis job.
|
||||
this._analysisJob.parseResults = parseResults;
|
||||
|
||||
// Resolve imports.
|
||||
timingStats.resolveImportsTime.timeOperation(() => {
|
||||
[this._analysisJob.imports, this._analysisJob.builtinsImport, this._analysisJob.typingModulePath] =
|
||||
this._resolveImports(importResolver, parseResults.importedModules, execEnvironment);
|
||||
|
@ -53,7 +53,6 @@ export class TimingStats {
|
||||
readFileTime = new TimingStat();
|
||||
tokenizeFileTime = new TimingStat();
|
||||
parseFileTime = new TimingStat();
|
||||
postParseWalkerTime = new TimingStat();
|
||||
resolveImportsTime = new TimingStat();
|
||||
bindTime = new TimingStat();
|
||||
typeAnalyzerTime = new TimingStat();
|
||||
@ -69,7 +68,6 @@ export class TimingStats {
|
||||
console.log('Read Source Files: ' + this.readFileTime.printTime());
|
||||
console.log('Tokenize: ' + this.tokenizeFileTime.printTime());
|
||||
console.log('Parse: ' + this.parseFileTime.printTime());
|
||||
console.log('Post-parse Walker: ' + this.postParseWalkerTime.printTime());
|
||||
console.log('Resolve Imports: ' + this.resolveImportsTime.printTime());
|
||||
console.log('Binder: ' + this.bindTime.printTime());
|
||||
console.log('Type Analyzer: ' + this.typeAnalyzerTime.printTime());
|
||||
|
@ -99,7 +99,7 @@ export const enum ErrorExpressionCategory {
|
||||
export interface ParseNodeBase extends TextRange {
|
||||
readonly nodeType: ParseNodeType;
|
||||
|
||||
// The parent field is filled in by the PostParseWalker,
|
||||
// The parent field is filled in by the binder,
|
||||
// which isn't technically part of the parser.
|
||||
parent?: ParseNode;
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import * as path from 'path';
|
||||
import { AnalyzerFileInfo } from '../analyzer/analyzerFileInfo';
|
||||
import { ModuleScopeBinder } from '../analyzer/binder';
|
||||
import { ImportResolver } from '../analyzer/importResolver';
|
||||
import { PostParseWalker } from '../analyzer/postParseWalker';
|
||||
import { Program } from '../analyzer/program';
|
||||
import { cloneDiagnosticSettings, ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
|
||||
import { StandardConsole } from '../common/console';
|
||||
@ -57,16 +56,7 @@ export class TestUtils {
|
||||
parseOptions: ParseOptions = new ParseOptions()): ParseResults {
|
||||
|
||||
const parser = new Parser();
|
||||
const parseResults = parser.parseSourceFile(textToParse, parseOptions, diagSink);
|
||||
const textRangeDiagSink = new TextRangeDiagnosticSink(parseResults.lines,
|
||||
diagSink.diagnostics);
|
||||
|
||||
// Link the parents.
|
||||
const parentWalker = new PostParseWalker(textRangeDiagSink,
|
||||
parseResults.parseTree);
|
||||
parentWalker.analyze();
|
||||
|
||||
return parseResults;
|
||||
return parser.parseSourceFile(textToParse, parseOptions, diagSink);
|
||||
}
|
||||
|
||||
static parseSampleFile(fileName: string, diagSink: DiagnosticSink,
|
||||
|
Loading…
Reference in New Issue
Block a user