mirror of
https://github.com/microsoft/pyright.git
synced 2024-08-17 03:40:44 +03:00
Renamed expressionEvaluator to typeEvaluator and typeAnalyzer to checker to better reflect their new roles.
This commit is contained in:
parent
406f167e17
commit
90caa3159e
@ -31,9 +31,9 @@ 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 [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 [binder](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/binder.ts) is responsible for building scopes populating the symbol table for each scope. It does not perform any type checking, but it detects and reports some 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 binder also builds a "reverse code flow graph" for each scope, allowing the type analyzer to determine a symbol's type at any point in the code flow based on its antecedents.
|
||||
|
||||
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.
|
||||
The [checker](https://github.com/Microsoft/pyright/blob/master/server/src/analyzer/checker.ts) is responsible for checking all of the statements and expressions within a source file. It relies heavily on the typeEvaluator module, which performs most of the heavy lifting. The checker doesn't run on all files, only those that require full diagnostic output. For example, if a source file is not part of the program but is imported by the program, the checker doesn't need to run on it.
|
||||
|
||||
## Type Checking Concepts
|
||||
|
||||
@ -41,7 +41,7 @@ Pyright uses an internal type called “Unknown” to represent types that are n
|
||||
|
||||
Pyright attempts to infer the types of global (module-level) variables, class variables, instance variables, and local variables. Return and yield types are also inferred. If type annotations are provided in these cases, the type annotation overrides any inferred types.
|
||||
|
||||
Pyright supports type constraints (sometimes called “path constraints”) to track assumptions that apply within certain paths of code flow. For example, consider the following code:
|
||||
Pyright supports type constraints (sometimes called “path constraints” or "type guards") to track assumptions that apply within certain code flow paths. For example, consider the following code:
|
||||
```python
|
||||
def (a: Optional[Union[str, List[str]]):
|
||||
if isinstance(a, str):
|
||||
@ -52,7 +52,7 @@ def (a: Optional[Union[str, List[str]]):
|
||||
log(a)
|
||||
```
|
||||
|
||||
In this example, the type checker knows that parameter a is either None, str, or List[str]. Within the first `if` clause, a is constrained to be a str. Within the `elif` clause, it is constrained to be a List[str], and within the `else` clause, it has to be None (by process of elimination). The type checker would therefore flag the final line as an error if the log method could not accept None as a parameter.
|
||||
In this example, the type evaluator knows that parameter a is either None, str, or List[str]. Within the first `if` clause, a is constrained to be a str. Within the `elif` clause, it is constrained to be a List[str], and within the `else` clause, it has to be None (by process of elimination). The type checker would therefore flag the final line as an error if the log method could not accept None as a parameter.
|
||||
|
||||
If the type constraint logic exhausts all possible subtypes, it can be assumed that a code path will never be taken. For example, consider the following:
|
||||
```python
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Author: Eric Traut
|
||||
*
|
||||
* Information associated with a source file that is used
|
||||
* by the binder and type checker.
|
||||
* by the binder and checker.
|
||||
*/
|
||||
|
||||
import { DiagnosticSettings, ExecutionEnvironment } from '../common/configOptions';
|
||||
|
@ -4,10 +4,10 @@
|
||||
* Licensed under the MIT license.
|
||||
* Author: Eric Traut
|
||||
*
|
||||
* Defines objects that the analyzer(s) hang off the parse nodes in
|
||||
* the parse tree. It contains information collected during the
|
||||
* analysis phases that can be used for later analysis steps or for
|
||||
* language services (e.g. hover information).
|
||||
* Defines objects that hang off the parse nodes in the parse tree.
|
||||
* It contains information collected during the binder phase that
|
||||
* can be used for later analysis steps or for language services
|
||||
* (e.g. hover information).
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
@ -49,7 +49,7 @@ interface AnalyzerNodeInfo {
|
||||
fileInfo?: AnalyzerFileInfo;
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Set by TypeAnalyzer
|
||||
// Set by Type Evaluator
|
||||
|
||||
// Cached type information for this node.
|
||||
typeCache?: ExpressionTypeCache;
|
||||
|
@ -1,12 +1,15 @@
|
||||
/*
|
||||
* typeAnalyzer.ts
|
||||
* checker.ts
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT license.
|
||||
* Author: Eric Traut
|
||||
*
|
||||
* A parse tree walker that performs static type checking. It assumes
|
||||
* that the binder has already run and added information to
|
||||
* the parse nodes.
|
||||
* A parse tree walker that performs static type checking for
|
||||
* a source file. Most of its work is performed by the type
|
||||
* evaluator, but this module touches every node in the file
|
||||
* to ensure that all statements and expressions are evaluated
|
||||
* and checked. It also performs some additional checks that
|
||||
* cannot (or should not be) performed lazily.
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
@ -27,7 +30,6 @@ import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||
import { FlowFlags } from './codeFlow';
|
||||
import { Declaration, DeclarationType } from './declaration';
|
||||
import * as DeclarationUtils from './declarationUtils';
|
||||
import { createExpressionEvaluator, EvaluatorFlags, ExpressionEvaluator } from './expressionEvaluator';
|
||||
import * as ParseTreeUtils from './parseTreeUtils';
|
||||
import { ParseTreeWalker } from './parseTreeWalker';
|
||||
import { Scope, ScopeType } from './scope';
|
||||
@ -35,6 +37,7 @@ import * as ScopeUtils from './scopeUtils';
|
||||
import { Symbol } from './symbol';
|
||||
import * as SymbolNameUtils from './symbolNameUtils';
|
||||
import { getEffectiveTypeOfSymbol, getLastTypedDeclaredForSymbol } from './symbolUtils';
|
||||
import { createTypeEvaluator, EvaluatorFlags, TypeEvaluator } from './typeEvaluator';
|
||||
import { ClassType, combineTypes, FunctionType, isAnyOrUnknown, isNoneOrNever, isTypeSame, NoneType,
|
||||
ObjectType, Type, TypeCategory, UnknownType } from './types';
|
||||
import { canAssignType, canOverrideMethod, containsUnknown, derivesFromClassRecursive,
|
||||
@ -42,10 +45,10 @@ import { canAssignType, canOverrideMethod, containsUnknown, derivesFromClassRecu
|
||||
getDeclaredGeneratorYieldType, getSymbolFromBaseClasses,
|
||||
isNoReturnType, printType, specializeType, transformTypeObjectToClass } from './typeUtils';
|
||||
|
||||
export class TypeAnalyzer extends ParseTreeWalker {
|
||||
export class Checker extends ParseTreeWalker {
|
||||
private readonly _moduleNode: ModuleNode;
|
||||
private readonly _fileInfo: AnalyzerFileInfo;
|
||||
private readonly _evaluator: ExpressionEvaluator;
|
||||
private readonly _evaluator: TypeEvaluator;
|
||||
private _currentScope: Scope;
|
||||
|
||||
// Indicates where there was a change in the type analysis
|
||||
@ -82,7 +85,7 @@ export class TypeAnalyzer extends ParseTreeWalker {
|
||||
this._didAnalysisChange = false;
|
||||
this._accessedSymbolMap = accessedSymbolMap;
|
||||
this._analysisVersion = analysisVersion;
|
||||
this._evaluator = createExpressionEvaluator(
|
||||
this._evaluator = createTypeEvaluator(
|
||||
this._fileInfo.diagnosticSink,
|
||||
this._analysisVersion,
|
||||
reason => {
|
@ -631,7 +631,7 @@ export class Program {
|
||||
// mark all of its dependencies as needing to be reanalyzed.
|
||||
let didAnalysisChange = false;
|
||||
while (true) {
|
||||
fileToAnalyze.sourceFile.doTypeAnalysis();
|
||||
fileToAnalyze.sourceFile.checkTypes();
|
||||
|
||||
if (!fileToAnalyze.sourceFile.isTypeAnalysisRequired()) {
|
||||
break;
|
||||
|
@ -37,6 +37,7 @@ import { Token } from '../parser/tokenizerTypes';
|
||||
import { AnalyzerFileInfo, ImportLookup } from './analyzerFileInfo';
|
||||
import * as AnalyzerNodeInfo from './analyzerNodeInfo';
|
||||
import { Binder, BinderResults } from './binder';
|
||||
import { Checker } from './checker';
|
||||
import { CircularDependency } from './circularDependency';
|
||||
import * as CommentUtils from './commentUtils';
|
||||
import { ImportResolver } from './importResolver';
|
||||
@ -45,17 +46,13 @@ import { ParseTreeCleanerWalker } from './parseTreeCleaner';
|
||||
import { Scope } from './scope';
|
||||
import { SymbolTable } from './symbol';
|
||||
import { TestWalker } from './testWalker';
|
||||
import { TypeAnalyzer } from './typeAnalyzer';
|
||||
|
||||
const _maxImportCyclesPerFile = 4;
|
||||
|
||||
// At some point, we'll cut off the analysis passes and assume
|
||||
// we're making no forward progress. This should happen only
|
||||
// on the case of bugs in the analyzer.
|
||||
// The number is somewhat arbitrary. It needs to be at least
|
||||
// 21 or so to handle all of the import cycles in the stdlib
|
||||
// files.
|
||||
const _maxAnalysisPassCount = 20;
|
||||
const _maxAnalysisPassCount = 25;
|
||||
|
||||
export class SourceFile {
|
||||
// Console interface to use for debugging.
|
||||
@ -720,7 +717,7 @@ export class SourceFile {
|
||||
this._isBindingNeeded = false;
|
||||
}
|
||||
|
||||
doTypeAnalysis() {
|
||||
checkTypes() {
|
||||
assert(!this.isParseRequired());
|
||||
assert(!this.isBindingRequired());
|
||||
assert(this.isTypeAnalysisRequired());
|
||||
@ -731,15 +728,15 @@ export class SourceFile {
|
||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(this._parseResults!.parseTree)!;
|
||||
|
||||
// Perform static type analysis.
|
||||
const typeAnalyzer = new TypeAnalyzer(this._parseResults!.parseTree,
|
||||
const checker = new Checker(this._parseResults!.parseTree,
|
||||
this._accessedSymbolMap!, this._typeAnalysisPassNumber);
|
||||
this._typeAnalysisPassNumber++;
|
||||
|
||||
// Repeatedly call the analyzer until everything converges.
|
||||
this._isTypeAnalysisPassNeeded = typeAnalyzer.analyze();
|
||||
// Repeatedly call the checker until everything converges.
|
||||
this._isTypeAnalysisPassNeeded = checker.analyze();
|
||||
|
||||
if (this._isTypeAnalysisPassNeeded) {
|
||||
this._lastReanalysisReason = typeAnalyzer.getLastReanalysisReason();
|
||||
this._lastReanalysisReason = checker.getLastReanalysisReason();
|
||||
|
||||
const passesSinceLastReanalysis = this._typeAnalysisPassNumber - this._typeAnalysisReanalysisPassStart;
|
||||
if (passesSinceLastReanalysis > _maxAnalysisPassCount) {
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* expressionEvaluator.ts
|
||||
* typeEvaluator.ts
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT license.
|
||||
* Author: Eric Traut
|
||||
*
|
||||
* Class that evaluates the type of expressions (parse trees)
|
||||
* within particular contexts and reports type errors.
|
||||
* Module that evaluates types of parse tree nodes within
|
||||
* a program.
|
||||
*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
@ -207,7 +207,7 @@ export interface FunctionTypeResult {
|
||||
decoratedType: Type;
|
||||
}
|
||||
|
||||
export interface ExpressionEvaluator {
|
||||
export interface TypeEvaluator {
|
||||
getType: (node: ExpressionNode, usage: EvaluatorUsage, flags: EvaluatorFlags) => Type;
|
||||
getTypeOfAnnotation: (node: ExpressionNode) => Type;
|
||||
getTypeFromObjectMember: (errorNode: ExpressionNode, objectType: ObjectType, memberName: string,
|
||||
@ -248,9 +248,9 @@ export interface ExpressionEvaluator {
|
||||
addDiagnostic: (diagLevel: DiagnosticLevel, rule: string, message: string, textRange: TextRange) => Diagnostic | undefined;
|
||||
}
|
||||
|
||||
export function createExpressionEvaluator(diagnosticSink: TextRangeDiagnosticSink,
|
||||
export function createTypeEvaluator(diagnosticSink: TextRangeDiagnosticSink,
|
||||
analysisVersion: number, setAnalysisChangedCallback: SetAnalysisChangedCallback,
|
||||
accessedSymbolMap: Map<number, true>, importLookup: ImportLookup): ExpressionEvaluator {
|
||||
accessedSymbolMap: Map<number, true>, importLookup: ImportLookup): TypeEvaluator {
|
||||
|
||||
let isSpeculativeMode = false;
|
||||
const typeResolutionRecursionMap = new Map<number, true>();
|
Loading…
Reference in New Issue
Block a user