mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Redesigned the handling of PEP 695-style type parameter scoping to better match the CPython runtime implementation. This addresses #7716 and #7737. (#7754)
This commit is contained in:
parent
3875288287
commit
400bb92da0
@ -17,7 +17,6 @@ import {
|
||||
LambdaNode,
|
||||
ListComprehensionNode,
|
||||
ModuleNode,
|
||||
NameNode,
|
||||
ParseNode,
|
||||
ParseNodeType,
|
||||
StringNode,
|
||||
@ -187,16 +186,6 @@ export function setDunderAllInfo(node: ModuleNode, names: DunderAllInfo | undefi
|
||||
analyzerNode.dunderAllInfo = names;
|
||||
}
|
||||
|
||||
export function getTypeParameterSymbol(node: NameNode) {
|
||||
const analyzerNode = node as AnalyzerNodeInfo;
|
||||
return analyzerNode.typeParameterSymbol;
|
||||
}
|
||||
|
||||
export function setTypeParameterSymbol(node: NameNode, symbol: Symbol) {
|
||||
const analyzerNode = node as AnalyzerNodeInfo;
|
||||
analyzerNode.typeParameterSymbol = symbol;
|
||||
}
|
||||
|
||||
export function isCodeUnreachable(node: ParseNode): boolean {
|
||||
let curNode: ParseNode | undefined = node;
|
||||
|
||||
|
@ -123,7 +123,6 @@ import { ImplicitImport, ImportResult, ImportType } from './importResult';
|
||||
import * as ParseTreeUtils from './parseTreeUtils';
|
||||
import { ParseTreeWalker } from './parseTreeWalker';
|
||||
import { NameBindingType, Scope, ScopeType } from './scope';
|
||||
import { isScopeContainedWithin } from './scopeUtils';
|
||||
import * as StaticExpressions from './staticExpressions';
|
||||
import { Symbol, SymbolFlags, indeterminateSymbolId } from './symbol';
|
||||
import { isConstantName, isPrivateName, isPrivateOrProtectedName } from './symbolNameUtils';
|
||||
@ -138,7 +137,6 @@ interface MemberAccessInfo {
|
||||
interface DeferredBindingTask {
|
||||
scope: Scope;
|
||||
codeFlowExpressions: Set<string>;
|
||||
activeTypeParams: Map<string, ActiveTypeParameter>;
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
@ -152,11 +150,6 @@ interface ClassVarInfo {
|
||||
classVarTypeNode: ExpressionNode | undefined;
|
||||
}
|
||||
|
||||
interface ActiveTypeParameter {
|
||||
symbol: Symbol;
|
||||
scope: Scope;
|
||||
}
|
||||
|
||||
// For each flow node within an execution context, we'll add a small
|
||||
// amount to the complexity factor. Without this, the complexity
|
||||
// calculation fails to take into account large numbers of non-cyclical
|
||||
@ -175,10 +168,6 @@ export class Binder extends ParseTreeWalker {
|
||||
// Current control-flow node.
|
||||
private _currentFlowNode: FlowNode | undefined;
|
||||
|
||||
// Tracks the type parameters that are currently active within the
|
||||
// scope and any outer scopes.
|
||||
private _activeTypeParams = new Map<string, ActiveTypeParameter>();
|
||||
|
||||
// Current target function declaration, if currently binding
|
||||
// a function. This allows return and yield statements to be
|
||||
// added to the function declaration.
|
||||
@ -275,6 +264,7 @@ export class Binder extends ParseTreeWalker {
|
||||
this._createNewScope(
|
||||
isBuiltInModule ? ScopeType.Builtin : ScopeType.Module,
|
||||
this._fileInfo.builtinsScope,
|
||||
/* proxyScope */ undefined,
|
||||
() => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode!);
|
||||
@ -433,37 +423,40 @@ export class Binder extends ParseTreeWalker {
|
||||
// Stash the declaration in the parse node for later access.
|
||||
AnalyzerNodeInfo.setDeclaration(node, classDeclaration);
|
||||
|
||||
let typeParamScope: Scope | undefined;
|
||||
if (node.typeParameters) {
|
||||
this.walk(node.typeParameters);
|
||||
typeParamScope = AnalyzerNodeInfo.getScope(node.typeParameters);
|
||||
}
|
||||
|
||||
this.walkMultiple(node.arguments);
|
||||
|
||||
this._createNewScope(ScopeType.Class, this._getNonClassParentScope(), () => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
this._createNewScope(
|
||||
ScopeType.Class,
|
||||
typeParamScope ?? this._getNonClassParentScope(),
|
||||
/* proxyScope */ undefined,
|
||||
() => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
|
||||
this._addImplicitSymbolToCurrentScope('__doc__', node, 'str | None');
|
||||
this._addImplicitSymbolToCurrentScope('__module__', node, 'str');
|
||||
this._addImplicitSymbolToCurrentScope('__qualname__', node, 'str');
|
||||
this._addImplicitSymbolToCurrentScope('__doc__', node, 'str | None');
|
||||
this._addImplicitSymbolToCurrentScope('__module__', node, 'str');
|
||||
this._addImplicitSymbolToCurrentScope('__qualname__', node, 'str');
|
||||
|
||||
this._dunderSlotsEntries = undefined;
|
||||
if (!this._moduleSymbolOnly) {
|
||||
// Analyze the suite.
|
||||
this.walk(node.suite);
|
||||
this._dunderSlotsEntries = undefined;
|
||||
if (!this._moduleSymbolOnly) {
|
||||
// Analyze the suite.
|
||||
this.walk(node.suite);
|
||||
}
|
||||
|
||||
if (this._dunderSlotsEntries) {
|
||||
this._addSlotsToCurrentScope(this._dunderSlotsEntries);
|
||||
}
|
||||
this._dunderSlotsEntries = undefined;
|
||||
}
|
||||
|
||||
if (this._dunderSlotsEntries) {
|
||||
this._addSlotsToCurrentScope(this._dunderSlotsEntries);
|
||||
}
|
||||
this._dunderSlotsEntries = undefined;
|
||||
});
|
||||
);
|
||||
|
||||
this._createAssignmentTargetFlowNodes(node.name, /* walkTargets */ false, /* unbound */ false);
|
||||
|
||||
if (node.typeParameters) {
|
||||
this._removeActiveTypeParameters(node.typeParameters);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -498,8 +491,10 @@ export class Binder extends ParseTreeWalker {
|
||||
}
|
||||
});
|
||||
|
||||
let typeParamScope: Scope | undefined;
|
||||
if (node.typeParameters) {
|
||||
this.walk(node.typeParameters);
|
||||
typeParamScope = AnalyzerNodeInfo.getScope(node.typeParameters);
|
||||
}
|
||||
|
||||
this.walkMultiple(node.decorators);
|
||||
@ -524,78 +519,79 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
// Don't walk the body of the function until we're done analyzing
|
||||
// the current scope.
|
||||
this._createNewScope(ScopeType.Function, this._getNonClassParentScope(), () => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
this._createNewScope(
|
||||
ScopeType.Function,
|
||||
typeParamScope ?? this._getNonClassParentScope(),
|
||||
/* proxyScope */ undefined,
|
||||
() => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
|
||||
const enclosingClass = ParseTreeUtils.getEnclosingClass(node);
|
||||
if (enclosingClass) {
|
||||
// Add the implicit "__class__" symbol described in PEP 3135.
|
||||
this._addImplicitSymbolToCurrentScope('__class__', node, 'class');
|
||||
}
|
||||
const enclosingClass = ParseTreeUtils.getEnclosingClass(node);
|
||||
if (enclosingClass) {
|
||||
// Add the implicit "__class__" symbol described in PEP 3135.
|
||||
this._addImplicitSymbolToCurrentScope('__class__', node, 'class');
|
||||
}
|
||||
|
||||
this._deferBinding(() => {
|
||||
// Create a start node for the function.
|
||||
this._currentFlowNode = this._createStartFlowNode();
|
||||
this._codeFlowComplexity = 0;
|
||||
this._deferBinding(() => {
|
||||
// Create a start node for the function.
|
||||
this._currentFlowNode = this._createStartFlowNode();
|
||||
this._codeFlowComplexity = 0;
|
||||
|
||||
node.parameters.forEach((paramNode) => {
|
||||
if (paramNode.name) {
|
||||
const symbol = this._bindNameToScope(this._currentScope, paramNode.name);
|
||||
node.parameters.forEach((paramNode) => {
|
||||
if (paramNode.name) {
|
||||
const symbol = this._bindNameToScope(this._currentScope, paramNode.name);
|
||||
|
||||
// Extract the parameter docString from the function docString
|
||||
let docString = ParseTreeUtils.getDocString(node?.suite?.statements ?? []);
|
||||
if (docString !== undefined) {
|
||||
docString = extractParameterDocumentation(docString, paramNode.name.value);
|
||||
// Extract the parameter docString from the function docString
|
||||
let docString = ParseTreeUtils.getDocString(node?.suite?.statements ?? []);
|
||||
if (docString !== undefined) {
|
||||
docString = extractParameterDocumentation(docString, paramNode.name.value);
|
||||
}
|
||||
|
||||
if (symbol) {
|
||||
const paramDeclaration: ParameterDeclaration = {
|
||||
type: DeclarationType.Parameter,
|
||||
node: paramNode,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
docString: docString,
|
||||
};
|
||||
|
||||
symbol.addDeclaration(paramDeclaration);
|
||||
AnalyzerNodeInfo.setDeclaration(paramNode.name, paramDeclaration);
|
||||
}
|
||||
|
||||
this._createFlowAssignment(paramNode.name);
|
||||
}
|
||||
});
|
||||
|
||||
if (symbol) {
|
||||
const paramDeclaration: ParameterDeclaration = {
|
||||
type: DeclarationType.Parameter,
|
||||
node: paramNode,
|
||||
uri: this._fileInfo.fileUri,
|
||||
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
|
||||
moduleName: this._fileInfo.moduleName,
|
||||
isInExceptSuite: this._isInExceptSuite,
|
||||
docString: docString,
|
||||
};
|
||||
this._targetFunctionDeclaration = functionDeclaration;
|
||||
this._currentReturnTarget = this._createBranchLabel();
|
||||
|
||||
symbol.addDeclaration(paramDeclaration);
|
||||
AnalyzerNodeInfo.setDeclaration(paramNode.name, paramDeclaration);
|
||||
}
|
||||
// Walk the statements that make up the function.
|
||||
this.walk(node.suite);
|
||||
|
||||
this._createFlowAssignment(paramNode.name);
|
||||
}
|
||||
// Associate the code flow node at the end of the suite with
|
||||
// the suite.
|
||||
AnalyzerNodeInfo.setAfterFlowNode(node.suite, this._currentFlowNode);
|
||||
|
||||
// Compute the final return flow node and associate it with
|
||||
// the function's parse node. If this node is unreachable, then
|
||||
// the function never returns.
|
||||
this._addAntecedent(this._currentReturnTarget, this._currentFlowNode);
|
||||
const returnFlowNode = this._finishFlowLabel(this._currentReturnTarget);
|
||||
|
||||
AnalyzerNodeInfo.setAfterFlowNode(node, returnFlowNode);
|
||||
|
||||
AnalyzerNodeInfo.setCodeFlowExpressions(node, this._currentScopeCodeFlowExpressions!);
|
||||
AnalyzerNodeInfo.setCodeFlowComplexity(node, this._codeFlowComplexity);
|
||||
});
|
||||
|
||||
this._targetFunctionDeclaration = functionDeclaration;
|
||||
this._currentReturnTarget = this._createBranchLabel();
|
||||
|
||||
// Walk the statements that make up the function.
|
||||
this.walk(node.suite);
|
||||
|
||||
// Associate the code flow node at the end of the suite with
|
||||
// the suite.
|
||||
AnalyzerNodeInfo.setAfterFlowNode(node.suite, this._currentFlowNode);
|
||||
|
||||
// Compute the final return flow node and associate it with
|
||||
// the function's parse node. If this node is unreachable, then
|
||||
// the function never returns.
|
||||
this._addAntecedent(this._currentReturnTarget, this._currentFlowNode);
|
||||
const returnFlowNode = this._finishFlowLabel(this._currentReturnTarget);
|
||||
|
||||
AnalyzerNodeInfo.setAfterFlowNode(node, returnFlowNode);
|
||||
|
||||
AnalyzerNodeInfo.setCodeFlowExpressions(node, this._currentScopeCodeFlowExpressions!);
|
||||
AnalyzerNodeInfo.setCodeFlowComplexity(node, this._codeFlowComplexity);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this._createAssignmentTargetFlowNodes(node.name, /* walkTargets */ false, /* unbound */ false);
|
||||
|
||||
if (node.typeParameters) {
|
||||
this._removeActiveTypeParameters(node.typeParameters);
|
||||
}
|
||||
|
||||
// We'll walk the child nodes in a deferred manner, so don't walk them now.
|
||||
return false;
|
||||
}
|
||||
@ -612,7 +608,7 @@ export class Binder extends ParseTreeWalker {
|
||||
}
|
||||
});
|
||||
|
||||
this._createNewScope(ScopeType.Function, this._getNonClassParentScope(), () => {
|
||||
this._createNewScope(ScopeType.Function, this._getNonClassParentScope(), /* proxyScope */ undefined, () => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
|
||||
this._deferBinding(() => {
|
||||
@ -766,15 +762,19 @@ export class Binder extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
override visitTypeParameterList(node: TypeParameterListNode): boolean {
|
||||
const typeParamScope = new Scope(ScopeType.TypeParameter, this._getNonClassParentScope(), this._currentScope);
|
||||
|
||||
node.parameters.forEach((param) => {
|
||||
if (param.boundExpression) {
|
||||
this.walk(param.boundExpression);
|
||||
}
|
||||
});
|
||||
|
||||
const typeParamsSeen = new Set<string>();
|
||||
|
||||
node.parameters.forEach((param) => {
|
||||
const name = param.name;
|
||||
const symbol = new Symbol(SymbolFlags.None);
|
||||
const symbol = typeParamScope.addSymbol(name.value, SymbolFlags.None);
|
||||
const paramDeclaration: TypeParameterDeclaration = {
|
||||
type: DeclarationType.TypeParameter,
|
||||
node: param,
|
||||
@ -786,15 +786,14 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
symbol.addDeclaration(paramDeclaration);
|
||||
AnalyzerNodeInfo.setDeclaration(name, paramDeclaration);
|
||||
AnalyzerNodeInfo.setTypeParameterSymbol(name, symbol);
|
||||
|
||||
if (this._activeTypeParams.has(name.value)) {
|
||||
if (typeParamsSeen.has(name.value)) {
|
||||
this._addSyntaxError(
|
||||
LocMessage.typeParameterExistingTypeParameter().format({ name: name.value }),
|
||||
name
|
||||
);
|
||||
} else {
|
||||
this._activeTypeParams.set(name.value, { symbol, scope: this._currentScope });
|
||||
typeParamsSeen.add(name.value);
|
||||
}
|
||||
});
|
||||
|
||||
@ -804,6 +803,8 @@ export class Binder extends ParseTreeWalker {
|
||||
}
|
||||
});
|
||||
|
||||
AnalyzerNodeInfo.setScope(node, typeParamScope);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -812,8 +813,10 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
this.walk(node.name);
|
||||
|
||||
let typeParamScope: Scope | undefined;
|
||||
if (node.typeParameters) {
|
||||
this.walk(node.typeParameters);
|
||||
typeParamScope = AnalyzerNodeInfo.getScope(node.typeParameters);
|
||||
}
|
||||
|
||||
const typeAliasDeclaration: TypeAliasDeclaration = {
|
||||
@ -836,11 +839,10 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
this._createAssignmentTargetFlowNodes(node.name, /* walkTargets */ true, /* unbound */ false);
|
||||
|
||||
const prevScope = this._currentScope;
|
||||
this._currentScope = typeParamScope ?? this._currentScope;
|
||||
this.walk(node.expression);
|
||||
|
||||
if (node.typeParameters) {
|
||||
this._removeActiveTypeParameters(node.typeParameters);
|
||||
}
|
||||
this._currentScope = prevScope;
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -1285,22 +1287,6 @@ export class Binder extends ParseTreeWalker {
|
||||
|
||||
override visitName(node: NameNode): boolean {
|
||||
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode!);
|
||||
|
||||
const typeParamSymbol = this._activeTypeParams.get(node.value);
|
||||
if (typeParamSymbol) {
|
||||
const bindingType = this._currentScope.getBindingType(node.value);
|
||||
|
||||
if (bindingType !== NameBindingType.Global) {
|
||||
// See if the type parameter symbol has been shadowed by a
|
||||
// variable within an inner scope.
|
||||
const nameSymbolWithScope = this._currentScope.lookUpSymbolRecursive(node.value);
|
||||
if (!nameSymbolWithScope || !isScopeContainedWithin(nameSymbolWithScope.scope, typeParamSymbol.scope)) {
|
||||
AnalyzerNodeInfo.setTypeParameterSymbol(node, typeParamSymbol.symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name nodes have no children.
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2170,61 +2156,66 @@ export class Binder extends ParseTreeWalker {
|
||||
this.walk(node.forIfNodes[0].iterableExpression);
|
||||
}
|
||||
|
||||
this._createNewScope(ScopeType.ListComprehension, this._getNonClassParentScope(), () => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
this._createNewScope(
|
||||
ScopeType.ListComprehension,
|
||||
this._getNonClassParentScope(),
|
||||
/* proxyScope */ undefined,
|
||||
() => {
|
||||
AnalyzerNodeInfo.setScope(node, this._currentScope);
|
||||
|
||||
const falseLabel = this._createBranchLabel();
|
||||
const falseLabel = this._createBranchLabel();
|
||||
|
||||
// We'll walk the forIfNodes list twice. The first time we'll
|
||||
// bind targets of for statements. The second time we'll walk
|
||||
// expressions and create the control flow graph.
|
||||
for (let i = 0; i < node.forIfNodes.length; i++) {
|
||||
const compr = node.forIfNodes[i];
|
||||
const addedSymbols = new Map<string, Symbol>();
|
||||
if (compr.nodeType === ParseNodeType.ListComprehensionFor) {
|
||||
this._bindPossibleTupleNamedTarget(compr.targetExpression, addedSymbols);
|
||||
this._addInferredTypeAssignmentForVariable(compr.targetExpression, compr);
|
||||
// We'll walk the forIfNodes list twice. The first time we'll
|
||||
// bind targets of for statements. The second time we'll walk
|
||||
// expressions and create the control flow graph.
|
||||
for (let i = 0; i < node.forIfNodes.length; i++) {
|
||||
const compr = node.forIfNodes[i];
|
||||
const addedSymbols = new Map<string, Symbol>();
|
||||
if (compr.nodeType === ParseNodeType.ListComprehensionFor) {
|
||||
this._bindPossibleTupleNamedTarget(compr.targetExpression, addedSymbols);
|
||||
this._addInferredTypeAssignmentForVariable(compr.targetExpression, compr);
|
||||
|
||||
// Async for is not allowed outside of an async function
|
||||
// unless we're in ipython mode.
|
||||
if (compr.asyncToken && !this._fileInfo.ipythonMode) {
|
||||
if (!enclosingFunction || !enclosingFunction.isAsync) {
|
||||
// Allow if it's within a generator expression. Execution of
|
||||
// generator expressions is deferred and therefore can be
|
||||
// run within the context of an async function later.
|
||||
if (node.parent?.nodeType === ParseNodeType.List) {
|
||||
this._addSyntaxError(LocMessage.asyncNotInAsyncFunction(), compr.asyncToken);
|
||||
// Async for is not allowed outside of an async function
|
||||
// unless we're in ipython mode.
|
||||
if (compr.asyncToken && !this._fileInfo.ipythonMode) {
|
||||
if (!enclosingFunction || !enclosingFunction.isAsync) {
|
||||
// Allow if it's within a generator expression. Execution of
|
||||
// generator expressions is deferred and therefore can be
|
||||
// run within the context of an async function later.
|
||||
if (node.parent?.nodeType === ParseNodeType.List) {
|
||||
this._addSyntaxError(LocMessage.asyncNotInAsyncFunction(), compr.asyncToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < node.forIfNodes.length; i++) {
|
||||
const compr = node.forIfNodes[i];
|
||||
if (compr.nodeType === ParseNodeType.ListComprehensionFor) {
|
||||
// We already walked the first iterable expression above,
|
||||
// so skip it here.
|
||||
if (i !== 0) {
|
||||
this.walk(compr.iterableExpression);
|
||||
for (let i = 0; i < node.forIfNodes.length; i++) {
|
||||
const compr = node.forIfNodes[i];
|
||||
if (compr.nodeType === ParseNodeType.ListComprehensionFor) {
|
||||
// We already walked the first iterable expression above,
|
||||
// so skip it here.
|
||||
if (i !== 0) {
|
||||
this.walk(compr.iterableExpression);
|
||||
}
|
||||
|
||||
this._createAssignmentTargetFlowNodes(
|
||||
compr.targetExpression,
|
||||
/* walkTargets */ true,
|
||||
/* unbound */ false
|
||||
);
|
||||
} else {
|
||||
const trueLabel = this._createBranchLabel();
|
||||
this._bindConditional(compr.testExpression, trueLabel, falseLabel);
|
||||
this._currentFlowNode = this._finishFlowLabel(trueLabel);
|
||||
}
|
||||
|
||||
this._createAssignmentTargetFlowNodes(
|
||||
compr.targetExpression,
|
||||
/* walkTargets */ true,
|
||||
/* unbound */ false
|
||||
);
|
||||
} else {
|
||||
const trueLabel = this._createBranchLabel();
|
||||
this._bindConditional(compr.testExpression, trueLabel, falseLabel);
|
||||
this._currentFlowNode = this._finishFlowLabel(trueLabel);
|
||||
}
|
||||
}
|
||||
|
||||
this.walk(node.expression);
|
||||
this._addAntecedent(falseLabel, this._currentFlowNode!);
|
||||
this._currentFlowNode = this._finishFlowLabel(falseLabel);
|
||||
});
|
||||
this.walk(node.expression);
|
||||
this._addAntecedent(falseLabel, this._currentFlowNode!);
|
||||
this._currentFlowNode = this._finishFlowLabel(falseLabel);
|
||||
}
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -2357,20 +2348,6 @@ export class Binder extends ParseTreeWalker {
|
||||
return '.'.repeat(node.leadingDots) + node.nameParts.map((part) => part.value).join('.');
|
||||
}
|
||||
|
||||
private _removeActiveTypeParameters(node: TypeParameterListNode) {
|
||||
node.parameters.forEach((typeParamNode) => {
|
||||
const entry = this._activeTypeParams.get(typeParamNode.name.value);
|
||||
if (entry) {
|
||||
const decls = entry.symbol.getDeclarations();
|
||||
assert(decls && decls.length === 1 && decls[0].type === DeclarationType.TypeParameter);
|
||||
|
||||
if (decls[0].node === typeParamNode) {
|
||||
this._activeTypeParams.delete(typeParamNode.name.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getNonClassParentScope() {
|
||||
// We may not be able to use the current scope if it's a class scope.
|
||||
// Walk up until we find a non-class scope instead.
|
||||
@ -3579,9 +3556,14 @@ export class Binder extends ParseTreeWalker {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private _createNewScope(scopeType: ScopeType, parentScope: Scope | undefined, callback: () => void) {
|
||||
private _createNewScope(
|
||||
scopeType: ScopeType,
|
||||
parentScope: Scope | undefined,
|
||||
proxyScope: Scope | undefined,
|
||||
callback: () => void
|
||||
) {
|
||||
const prevScope = this._currentScope;
|
||||
const newScope = new Scope(scopeType, parentScope);
|
||||
const newScope = new Scope(scopeType, parentScope, proxyScope);
|
||||
this._currentScope = newScope;
|
||||
|
||||
// If this scope is an execution scope, allocate a new reference map.
|
||||
@ -4178,7 +4160,6 @@ export class Binder extends ParseTreeWalker {
|
||||
this._deferredBindingTasks.push({
|
||||
scope: this._currentScope,
|
||||
codeFlowExpressions: this._currentScopeCodeFlowExpressions!,
|
||||
activeTypeParams: new Map(this._activeTypeParams),
|
||||
callback,
|
||||
});
|
||||
}
|
||||
@ -4190,7 +4171,6 @@ export class Binder extends ParseTreeWalker {
|
||||
// Reset the state
|
||||
this._currentScope = nextItem.scope;
|
||||
this._currentScopeCodeFlowExpressions = nextItem.codeFlowExpressions;
|
||||
this._activeTypeParams = nextItem.activeTypeParams;
|
||||
|
||||
nextItem.callback();
|
||||
}
|
||||
|
@ -1637,6 +1637,44 @@ export class Checker extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
override visitTypeParameter(node: TypeParameterNode): boolean {
|
||||
// Verify that there are no live type variables with the same
|
||||
// name in outer scopes.
|
||||
let curNode: ParseNode | undefined = node.parent?.parent?.parent;
|
||||
let foundDuplicate = false;
|
||||
|
||||
while (curNode) {
|
||||
const typeVarScopeNode = ParseTreeUtils.getTypeVarScopeNode(curNode);
|
||||
if (!typeVarScopeNode) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeVarScopeNode.nodeType === ParseNodeType.Class) {
|
||||
const classType = this._evaluator.getTypeOfClass(typeVarScopeNode)?.classType;
|
||||
|
||||
if (classType?.details.typeParameters.some((param) => param.details.name === node.name.value)) {
|
||||
foundDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} else if (typeVarScopeNode.nodeType === ParseNodeType.Function) {
|
||||
const functionType = this._evaluator.getTypeOfFunction(typeVarScopeNode)?.functionType;
|
||||
|
||||
if (functionType?.details.typeParameters.some((param) => param.details.name === node.name.value)) {
|
||||
foundDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
curNode = typeVarScopeNode.parent;
|
||||
}
|
||||
|
||||
if (foundDuplicate) {
|
||||
this._evaluator.addDiagnostic(
|
||||
DiagnosticRule.reportGeneralTypeIssues,
|
||||
LocMessage.typeVarUsedByOuterScope().format({ name: node.name.value }),
|
||||
node.name
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2995,8 +3033,10 @@ export class Checker extends ParseTreeWalker {
|
||||
// Report unaccessed type parameters.
|
||||
const accessedSymbolSet = this._fileInfo.accessedSymbolSet;
|
||||
for (const paramList of this._typeParameterLists) {
|
||||
const typeParamScope = AnalyzerNodeInfo.getScope(paramList);
|
||||
|
||||
for (const param of paramList.parameters) {
|
||||
const symbol = AnalyzerNodeInfo.getTypeParameterSymbol(param.name);
|
||||
const symbol = typeParamScope?.symbolTable.get(param.name.value);
|
||||
if (!symbol) {
|
||||
// This can happen if the code is unreachable.
|
||||
return;
|
||||
|
@ -62,6 +62,11 @@ export const enum PrintExpressionFlags {
|
||||
DoNotLimitStringLength = 1 << 1,
|
||||
}
|
||||
|
||||
export interface EvaluationScopeInfo {
|
||||
node: EvaluationScopeNode;
|
||||
useProxyScope?: boolean;
|
||||
}
|
||||
|
||||
// Returns the depth of the node as measured from the root
|
||||
// of the parse tree.
|
||||
export function getNodeDepth(node: ParseNode): number {
|
||||
@ -726,7 +731,7 @@ export function getEnclosingFunction(node: ParseNode): FunctionNode | undefined
|
||||
// (for example), it will be considered part of its parent node rather than
|
||||
// the class node.
|
||||
export function getEnclosingFunctionEvaluationScope(node: ParseNode): FunctionNode | undefined {
|
||||
let curNode = getEvaluationScopeNode(node);
|
||||
let curNode = getEvaluationScopeNode(node).node;
|
||||
|
||||
while (curNode) {
|
||||
if (curNode.nodeType === ParseNodeType.Function) {
|
||||
@ -737,7 +742,7 @@ export function getEnclosingFunctionEvaluationScope(node: ParseNode): FunctionNo
|
||||
return undefined;
|
||||
}
|
||||
|
||||
curNode = getEvaluationScopeNode(curNode.parent);
|
||||
curNode = getEvaluationScopeNode(curNode.parent).node;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@ -817,7 +822,7 @@ export function getEvaluationNodeForAssignmentExpression(
|
||||
// target within a list comprehension is contained within a lambda,
|
||||
// function or module, but not a class.
|
||||
let sawListComprehension = false;
|
||||
let curNode: ParseNode | undefined = getEvaluationScopeNode(node);
|
||||
let curNode: ParseNode | undefined = getEvaluationScopeNode(node).node;
|
||||
|
||||
while (curNode !== undefined) {
|
||||
switch (curNode.nodeType) {
|
||||
@ -831,7 +836,7 @@ export function getEvaluationNodeForAssignmentExpression(
|
||||
|
||||
case ParseNodeType.ListComprehension:
|
||||
sawListComprehension = true;
|
||||
curNode = getEvaluationScopeNode(curNode.parent!);
|
||||
curNode = getEvaluationScopeNode(curNode.parent!).node;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -844,32 +849,68 @@ export function getEvaluationNodeForAssignmentExpression(
|
||||
|
||||
// Returns the parse node corresponding to the scope that is used to evaluate
|
||||
// a symbol referenced in the specified node.
|
||||
export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeNode {
|
||||
export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeInfo {
|
||||
let prevNode: ParseNode | undefined;
|
||||
let prevPrevNode: ParseNode | undefined;
|
||||
let curNode: ParseNode | undefined = node;
|
||||
let isParamNameNode = false;
|
||||
let isParamDefaultNode = false;
|
||||
|
||||
while (curNode) {
|
||||
if (curNode.nodeType === ParseNodeType.Parameter && prevNode === curNode.name) {
|
||||
// Note that we passed through a parameter name node.
|
||||
isParamNameNode = true;
|
||||
if (curNode.nodeType === ParseNodeType.Parameter) {
|
||||
if (prevNode === curNode.name) {
|
||||
// Note that we passed through a parameter name node.
|
||||
isParamNameNode = true;
|
||||
} else if (prevNode === curNode.defaultValue) {
|
||||
// Note that we passed through a parameter default value node.
|
||||
isParamDefaultNode = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We found a scope associated with this node. In most cases,
|
||||
// we'll return this scope, but in a few cases we need to return
|
||||
// the enclosing scope instead.
|
||||
switch (curNode.nodeType) {
|
||||
case ParseNodeType.TypeParameterList: {
|
||||
return { node: curNode, useProxyScope: true };
|
||||
}
|
||||
|
||||
case ParseNodeType.Function: {
|
||||
if (!prevNode) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Decorators are always evaluated outside of the function scope.
|
||||
if (curNode.decorators.some((decorator) => decorator === prevNode)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (curNode.parameters.some((param) => param === prevNode)) {
|
||||
// Default argument expressions are evaluated outside of the function scope.
|
||||
if (isParamDefaultNode) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (isParamNameNode) {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
} else if (prevNode === curNode.suite) {
|
||||
}
|
||||
|
||||
if (prevNode === curNode.suite) {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
|
||||
// All other nodes in the function are evaluated in the context
|
||||
// of the type parameter scope if it's present. Otherwise,
|
||||
// they are evaluated within the function's parent scope.
|
||||
if (curNode.typeParameters) {
|
||||
const scopeNode = curNode.typeParameters;
|
||||
if (getScope(scopeNode) !== undefined) {
|
||||
return { node: scopeNode, useProxyScope: true };
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -879,21 +920,40 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeNode {
|
||||
if (curNode.parameters.some((param) => param === prevNode)) {
|
||||
if (isParamNameNode) {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
} else if (!prevNode || prevNode === curNode.expression) {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseNodeType.Class: {
|
||||
if (!prevNode) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Decorators are always evaluated outside of the class scope.
|
||||
if (curNode.decorators.some((decorator) => decorator === prevNode)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prevNode === curNode.suite) {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
|
||||
// All other nodes in the class are evaluated in the context
|
||||
// of the type parameter scope if it's present. Otherwise,
|
||||
// they are evaluated within the class' parent scope.
|
||||
if (curNode.typeParameters) {
|
||||
const scopeNode = curNode.typeParameters;
|
||||
if (getScope(scopeNode) !== undefined) {
|
||||
return { node: scopeNode, useProxyScope: true };
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -909,7 +969,17 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeNode {
|
||||
curNode.forIfNodes[0].iterableExpression === prevPrevNode;
|
||||
|
||||
if (!isFirstIterableExpr) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseNodeType.TypeAlias: {
|
||||
if (prevNode === curNode.expression && curNode.typeParameters) {
|
||||
const scopeNode = curNode.typeParameters;
|
||||
if (getScope(scopeNode) !== undefined) {
|
||||
return { node: scopeNode };
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -917,7 +987,7 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeNode {
|
||||
|
||||
case ParseNodeType.Module: {
|
||||
if (getScope(curNode) !== undefined) {
|
||||
return curNode;
|
||||
return { node: curNode };
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -933,8 +1003,8 @@ export function getEvaluationScopeNode(node: ParseNode): EvaluationScopeNode {
|
||||
}
|
||||
|
||||
// Returns the parse node corresponding to the function, class, or type alias
|
||||
// that contains the specified typeVar reference.
|
||||
export function getTypeVarScopeNode(node: ParseNode): TypeParameterScopeNode {
|
||||
// that potentially provides the scope for a type parameter.
|
||||
export function getTypeVarScopeNode(node: ParseNode): TypeParameterScopeNode | undefined {
|
||||
let prevNode: ParseNode | undefined;
|
||||
let curNode: ParseNode | undefined = node;
|
||||
|
||||
@ -963,13 +1033,13 @@ export function getTypeVarScopeNode(node: ParseNode): TypeParameterScopeNode {
|
||||
curNode = curNode.parent;
|
||||
}
|
||||
|
||||
return undefined!;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Returns the parse node corresponding to the scope that is used
|
||||
// for executing the code referenced in the specified node.
|
||||
export function getExecutionScopeNode(node: ParseNode): ExecutionScopeNode {
|
||||
let evaluationScope = getEvaluationScopeNode(node);
|
||||
let evaluationScope = getEvaluationScopeNode(node).node;
|
||||
|
||||
// Classes are not considered execution scope because they are executed
|
||||
// within the context of their containing module or function. Likewise, list
|
||||
@ -978,7 +1048,7 @@ export function getExecutionScopeNode(node: ParseNode): ExecutionScopeNode {
|
||||
evaluationScope.nodeType === ParseNodeType.Class ||
|
||||
evaluationScope.nodeType === ParseNodeType.ListComprehension
|
||||
) {
|
||||
evaluationScope = getEvaluationScopeNode(evaluationScope.parent!);
|
||||
evaluationScope = getEvaluationScopeNode(evaluationScope.parent!).node;
|
||||
}
|
||||
|
||||
return evaluationScope;
|
||||
|
@ -14,6 +14,9 @@ import { DeclarationType } from './declaration';
|
||||
import { Symbol, SymbolFlags, SymbolTable } from './symbol';
|
||||
|
||||
export const enum ScopeType {
|
||||
// Used for PEP 695-style type parameters.
|
||||
TypeParameter,
|
||||
|
||||
// Used for list comprehension nodes.
|
||||
ListComprehension,
|
||||
|
||||
@ -66,6 +69,12 @@ export interface GlobalScopeResult {
|
||||
isBeyondExecutionScope: boolean;
|
||||
}
|
||||
|
||||
export interface LookupSymbolOptions {
|
||||
isOutsideCallerModule?: boolean;
|
||||
isBeyondExecutionScope?: boolean;
|
||||
useProxyScope?: boolean;
|
||||
}
|
||||
|
||||
export class Scope {
|
||||
// The scope type, as defined in the enumeration.
|
||||
readonly type: ScopeType;
|
||||
@ -74,6 +83,10 @@ export class Scope {
|
||||
// top-most scope.
|
||||
readonly parent: Scope | undefined;
|
||||
|
||||
// An alternate parent scope that can be used to resolve symbols
|
||||
// in certain contexts. Used for TypeParameter scopes.
|
||||
readonly proxy: Scope | undefined;
|
||||
|
||||
// Association between names and symbols.
|
||||
readonly symbolTable: SymbolTable = new Map<string, Symbol>();
|
||||
|
||||
@ -85,9 +98,10 @@ export class Scope {
|
||||
// for class scopes).
|
||||
slotsNames: string[] | undefined;
|
||||
|
||||
constructor(type: ScopeType, parent?: Scope) {
|
||||
constructor(type: ScopeType, parent?: Scope, proxy?: Scope) {
|
||||
this.type = type;
|
||||
this.parent = parent;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
getGlobalScope(): GlobalScopeResult {
|
||||
@ -121,17 +135,17 @@ export class Scope {
|
||||
return this.symbolTable.get(name);
|
||||
}
|
||||
|
||||
lookUpSymbolRecursive(
|
||||
name: string,
|
||||
isOutsideCallerModule = false,
|
||||
isBeyondExecutionScope = false
|
||||
): SymbolWithScope | undefined {
|
||||
const symbol = this.symbolTable.get(name);
|
||||
lookUpSymbolRecursive(name: string, options?: LookupSymbolOptions): SymbolWithScope | undefined {
|
||||
let symbol = this.symbolTable.get(name);
|
||||
|
||||
if (!symbol && options?.useProxyScope && this.proxy) {
|
||||
symbol = this.proxy.symbolTable.get(name);
|
||||
}
|
||||
|
||||
if (symbol) {
|
||||
// If we're searching outside of the original caller's module (global) scope,
|
||||
// hide any names that are not meant to be visible to importers.
|
||||
if (isOutsideCallerModule && symbol.isExternallyHidden()) {
|
||||
if (options?.isOutsideCallerModule && symbol.isExternallyHidden()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -144,15 +158,15 @@ export class Scope {
|
||||
) {
|
||||
return {
|
||||
symbol,
|
||||
isOutsideCallerModule,
|
||||
isBeyondExecutionScope,
|
||||
isOutsideCallerModule: !!options?.isOutsideCallerModule,
|
||||
isBeyondExecutionScope: !!options?.isBeyondExecutionScope,
|
||||
scope: this,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let parentScope: Scope | undefined;
|
||||
let isNextScopeBeyondExecutionScope = isBeyondExecutionScope || this.isIndependentlyExecutable();
|
||||
let isNextScopeBeyondExecutionScope = options?.isBeyondExecutionScope || this.isIndependentlyExecutable();
|
||||
|
||||
if (this.notLocalBindings.get(name) === NameBindingType.Global) {
|
||||
const globalScopeResult = this.getGlobalScope();
|
||||
@ -170,11 +184,10 @@ export class Scope {
|
||||
// If our recursion is about to take us outside the scope of the current
|
||||
// module (i.e. into a built-in scope), indicate as such with the second
|
||||
// parameter.
|
||||
return parentScope.lookUpSymbolRecursive(
|
||||
name,
|
||||
isOutsideCallerModule || this.type === ScopeType.Module,
|
||||
isNextScopeBeyondExecutionScope
|
||||
);
|
||||
return parentScope.lookUpSymbolRecursive(name, {
|
||||
isOutsideCallerModule: !!options?.isOutsideCallerModule || this.type === ScopeType.Module,
|
||||
isBeyondExecutionScope: isNextScopeBeyondExecutionScope,
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -8,7 +8,7 @@
|
||||
* symbol tables.
|
||||
*/
|
||||
|
||||
import { ParseNode } from '../parser/parseNodes';
|
||||
import { EvaluationScopeNode, ParseNode } from '../parser/parseNodes';
|
||||
import { getScope } from './analyzerNodeInfo';
|
||||
import { getEvaluationScopeNode } from './parseTreeUtils';
|
||||
import { Scope, ScopeType } from './scope';
|
||||
@ -27,7 +27,7 @@ export function getBuiltInScope(currentScope: Scope): Scope {
|
||||
|
||||
// Locates the evaluation scope associated with the specified parse node.
|
||||
export function getScopeForNode(node: ParseNode): Scope | undefined {
|
||||
const scopeNode = getEvaluationScopeNode(node);
|
||||
const scopeNode = getEvaluationScopeNode(node).node;
|
||||
return getScope(scopeNode);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export function getScopeHierarchy(node: ParseNode, stopScope?: Scope): Scope[] |
|
||||
let curNode: ParseNode | undefined = node;
|
||||
|
||||
while (curNode) {
|
||||
const scopeNode = getEvaluationScopeNode(curNode);
|
||||
const scopeNode: EvaluationScopeNode = getEvaluationScopeNode(curNode).node;
|
||||
const curScope = getScope(scopeNode);
|
||||
|
||||
if (!curScope) {
|
||||
|
@ -4379,196 +4379,182 @@ export function createTypeEvaluator(
|
||||
let isIncomplete = false;
|
||||
const allowForwardReferences = (flags & EvaluatorFlags.AllowForwardReferences) !== 0 || fileInfo.isStubFile;
|
||||
|
||||
// Does this name refer to a PEP 695-style type parameter?
|
||||
const typeParamSymbol = AnalyzerNodeInfo.getTypeParameterSymbol(node);
|
||||
if (typeParamSymbol) {
|
||||
symbol = typeParamSymbol;
|
||||
assert(symbol.getDeclarations().length === 1);
|
||||
const decl = getLastTypedDeclarationForSymbol(symbol);
|
||||
assert(decl?.type === DeclarationType.TypeParameter);
|
||||
type = getTypeOfTypeParameter(decl.node);
|
||||
setSymbolAccessed(fileInfo, symbol, node);
|
||||
} else {
|
||||
// Look for the scope that contains the value definition and
|
||||
// see if it has a declared type.
|
||||
let symbolWithScope = lookUpSymbolRecursive(
|
||||
node,
|
||||
name,
|
||||
!allowForwardReferences,
|
||||
allowForwardReferences && (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0
|
||||
);
|
||||
// Look for the scope that contains the value definition and
|
||||
// see if it has a declared type.
|
||||
let symbolWithScope = lookUpSymbolRecursive(
|
||||
node,
|
||||
name,
|
||||
!allowForwardReferences,
|
||||
allowForwardReferences && (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0
|
||||
);
|
||||
|
||||
if (!symbolWithScope) {
|
||||
// If the node is part of a "from X import Y as Z" statement and the node
|
||||
// is the "Y" (non-aliased) name, we need to look up the alias symbol
|
||||
// since the non-aliased name is not in the symbol table.
|
||||
const alias = getAliasFromImport(node);
|
||||
if (alias) {
|
||||
symbolWithScope = lookUpSymbolRecursive(
|
||||
alias,
|
||||
alias.value,
|
||||
!allowForwardReferences,
|
||||
allowForwardReferences && (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0
|
||||
);
|
||||
if (!symbolWithScope) {
|
||||
// If the node is part of a "from X import Y as Z" statement and the node
|
||||
// is the "Y" (non-aliased) name, we need to look up the alias symbol
|
||||
// since the non-aliased name is not in the symbol table.
|
||||
const alias = getAliasFromImport(node);
|
||||
if (alias) {
|
||||
symbolWithScope = lookUpSymbolRecursive(
|
||||
alias,
|
||||
alias.value,
|
||||
!allowForwardReferences,
|
||||
allowForwardReferences && (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (symbolWithScope) {
|
||||
let useCodeFlowAnalysis = !allowForwardReferences;
|
||||
|
||||
// If the symbol is implicitly imported from the builtin
|
||||
// scope, there's no need to use code flow analysis.
|
||||
if (symbolWithScope.scope.type === ScopeType.Builtin) {
|
||||
useCodeFlowAnalysis = false;
|
||||
}
|
||||
|
||||
symbol = symbolWithScope.symbol;
|
||||
setSymbolAccessed(fileInfo, symbol, node);
|
||||
|
||||
// If we're not supposed to be analyzing this function, skip the remaining work
|
||||
// to determine the name's type. Simply evaluate its type as Any.
|
||||
if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
|
||||
const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
|
||||
if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) {
|
||||
return {
|
||||
type: AnyType.create(),
|
||||
isIncomplete: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (symbolWithScope) {
|
||||
let useCodeFlowAnalysis = !allowForwardReferences;
|
||||
// Get the effective type (either the declared type or the inferred type).
|
||||
// If we're using code flow analysis, pass the usage node so we consider
|
||||
// only the assignment nodes that are reachable from this usage.
|
||||
const effectiveTypeInfo = getEffectiveTypeOfSymbolForUsage(symbol, useCodeFlowAnalysis ? node : undefined);
|
||||
let effectiveType = transformPossibleRecursiveTypeAlias(effectiveTypeInfo.type);
|
||||
|
||||
// If the symbol is implicitly imported from the builtin
|
||||
// scope, there's no need to use code flow analysis.
|
||||
if (symbolWithScope.scope.type === ScopeType.Builtin) {
|
||||
useCodeFlowAnalysis = false;
|
||||
if (effectiveTypeInfo.isIncomplete) {
|
||||
if (isUnbound(effectiveType)) {
|
||||
effectiveType = UnknownType.create(/* isIncomplete */ true);
|
||||
}
|
||||
isIncomplete = true;
|
||||
}
|
||||
|
||||
symbol = symbolWithScope.symbol;
|
||||
setSymbolAccessed(fileInfo, symbol, node);
|
||||
|
||||
// If we're not supposed to be analyzing this function, skip the remaining work
|
||||
// to determine the name's type. Simply evaluate its type as Any.
|
||||
if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) {
|
||||
const containingFunction = ParseTreeUtils.getEnclosingFunction(node);
|
||||
if (containingFunction && ParseTreeUtils.isUnannotatedFunction(containingFunction)) {
|
||||
return {
|
||||
type: AnyType.create(),
|
||||
isIncomplete: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get the effective type (either the declared type or the inferred type).
|
||||
// If we're using code flow analysis, pass the usage node so we consider
|
||||
// only the assignment nodes that are reachable from this usage.
|
||||
const effectiveTypeInfo = getEffectiveTypeOfSymbolForUsage(
|
||||
symbol,
|
||||
useCodeFlowAnalysis ? node : undefined
|
||||
if (effectiveTypeInfo.isRecursiveDefinition && isNodeReachable(node)) {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportGeneralTypeIssues,
|
||||
LocMessage.recursiveDefinition().format({ name }),
|
||||
node
|
||||
);
|
||||
let effectiveType = transformPossibleRecursiveTypeAlias(effectiveTypeInfo.type);
|
||||
}
|
||||
|
||||
if (effectiveTypeInfo.isIncomplete) {
|
||||
if (isUnbound(effectiveType)) {
|
||||
effectiveType = UnknownType.create(/* isIncomplete */ true);
|
||||
const isSpecialBuiltIn =
|
||||
!!effectiveType && isInstantiableClass(effectiveType) && ClassType.isSpecialBuiltIn(effectiveType);
|
||||
|
||||
type = effectiveType;
|
||||
if (useCodeFlowAnalysis && !isSpecialBuiltIn) {
|
||||
// See if code flow analysis can tell us anything more about the type.
|
||||
// If the symbol is declared outside of our execution scope, use its effective
|
||||
// type. If it's declared inside our execution scope, it generally starts
|
||||
// as unbound at the start of the code flow.
|
||||
let typeAtStart = effectiveType;
|
||||
let isTypeAtStartIncomplete = false;
|
||||
|
||||
if (!symbolWithScope.isBeyondExecutionScope && symbol.isInitiallyUnbound()) {
|
||||
typeAtStart = UnboundType.create();
|
||||
|
||||
// Is this a module-level scope? If so, see if it's an alias of a builtin.
|
||||
if (symbolWithScope.scope.type === ScopeType.Module) {
|
||||
assert(symbolWithScope.scope.parent);
|
||||
const builtInSymbol = symbolWithScope.scope.parent.lookUpSymbol(name);
|
||||
if (builtInSymbol) {
|
||||
const builtInEffectiveType = getEffectiveTypeOfSymbolForUsage(builtInSymbol);
|
||||
typeAtStart = builtInEffectiveType.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (symbolWithScope.isBeyondExecutionScope) {
|
||||
const outerScopeTypeResult = getCodeFlowTypeForCapturedVariable(
|
||||
node,
|
||||
symbolWithScope,
|
||||
effectiveType
|
||||
);
|
||||
|
||||
if (outerScopeTypeResult?.type) {
|
||||
type = outerScopeTypeResult.type;
|
||||
typeAtStart = type;
|
||||
isTypeAtStartIncomplete = !!outerScopeTypeResult.isIncomplete;
|
||||
}
|
||||
}
|
||||
|
||||
const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, {
|
||||
targetSymbolId: symbol.id,
|
||||
typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete },
|
||||
skipConditionalNarrowing: (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0,
|
||||
});
|
||||
|
||||
if (codeFlowTypeResult.type) {
|
||||
type = codeFlowTypeResult.type;
|
||||
}
|
||||
|
||||
if (codeFlowTypeResult.isIncomplete) {
|
||||
isIncomplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveTypeInfo.isRecursiveDefinition && isNodeReachable(node)) {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportGeneralTypeIssues,
|
||||
LocMessage.recursiveDefinition().format({ name }),
|
||||
node
|
||||
);
|
||||
}
|
||||
// Detect, report, and fill in missing type arguments if appropriate.
|
||||
type = reportMissingTypeArguments(node, type, flags);
|
||||
|
||||
const isSpecialBuiltIn =
|
||||
!!effectiveType && isInstantiableClass(effectiveType) && ClassType.isSpecialBuiltIn(effectiveType);
|
||||
if ((flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0) {
|
||||
// Verify that the name does not refer to a (non type alias) variable.
|
||||
if (effectiveTypeInfo.includesVariableDecl && !type.typeAliasInfo) {
|
||||
let isAllowedTypeForVariable = isTypeVar(type) || isTypeAliasPlaceholder(type);
|
||||
|
||||
type = effectiveType;
|
||||
if (useCodeFlowAnalysis && !isSpecialBuiltIn) {
|
||||
// See if code flow analysis can tell us anything more about the type.
|
||||
// If the symbol is declared outside of our execution scope, use its effective
|
||||
// type. If it's declared inside our execution scope, it generally starts
|
||||
// as unbound at the start of the code flow.
|
||||
let typeAtStart = effectiveType;
|
||||
let isTypeAtStartIncomplete = false;
|
||||
|
||||
if (!symbolWithScope.isBeyondExecutionScope && symbol.isInitiallyUnbound()) {
|
||||
typeAtStart = UnboundType.create();
|
||||
|
||||
// Is this a module-level scope? If so, see if it's an alias of a builtin.
|
||||
if (symbolWithScope.scope.type === ScopeType.Module) {
|
||||
assert(symbolWithScope.scope.parent);
|
||||
const builtInSymbol = symbolWithScope.scope.parent.lookUpSymbol(name);
|
||||
if (builtInSymbol) {
|
||||
const builtInEffectiveType = getEffectiveTypeOfSymbolForUsage(builtInSymbol);
|
||||
typeAtStart = builtInEffectiveType.type;
|
||||
}
|
||||
}
|
||||
if (
|
||||
isClass(type) &&
|
||||
!type.includeSubclasses &&
|
||||
!symbol.hasTypedDeclarations() &&
|
||||
ClassType.isValidTypeAliasClass(type)
|
||||
) {
|
||||
// This check exempts class types that are created by calling
|
||||
// NewType, NamedTuple, etc.
|
||||
isAllowedTypeForVariable = true;
|
||||
}
|
||||
|
||||
if (symbolWithScope.isBeyondExecutionScope) {
|
||||
const outerScopeTypeResult = getCodeFlowTypeForCapturedVariable(
|
||||
node,
|
||||
symbolWithScope,
|
||||
effectiveType
|
||||
);
|
||||
|
||||
if (outerScopeTypeResult?.type) {
|
||||
type = outerScopeTypeResult.type;
|
||||
typeAtStart = type;
|
||||
isTypeAtStartIncomplete = !!outerScopeTypeResult.isIncomplete;
|
||||
}
|
||||
}
|
||||
|
||||
const codeFlowTypeResult = getFlowTypeOfReference(node, /* startNode */ undefined, {
|
||||
targetSymbolId: symbol.id,
|
||||
typeAtStart: { type: typeAtStart, isIncomplete: isTypeAtStartIncomplete },
|
||||
skipConditionalNarrowing: (flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0,
|
||||
});
|
||||
|
||||
if (codeFlowTypeResult.type) {
|
||||
type = codeFlowTypeResult.type;
|
||||
}
|
||||
|
||||
if (codeFlowTypeResult.isIncomplete) {
|
||||
isIncomplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect, report, and fill in missing type arguments if appropriate.
|
||||
type = reportMissingTypeArguments(node, type, flags);
|
||||
|
||||
if ((flags & EvaluatorFlags.ExpectingTypeAnnotation) !== 0) {
|
||||
// Verify that the name does not refer to a (non type alias) variable.
|
||||
if (effectiveTypeInfo.includesVariableDecl && !type.typeAliasInfo) {
|
||||
let isAllowedTypeForVariable = isTypeVar(type) || isTypeAliasPlaceholder(type);
|
||||
|
||||
// Disable for assignments in the typings.pyi file, since it defines special forms.
|
||||
if (!isAllowedTypeForVariable && !fileInfo.isTypingStubFile) {
|
||||
// This might be a union that was previously a type alias
|
||||
// but was reconstituted in such a way that we lost the
|
||||
// typeAliasInfo. Avoid the false positive error by suppressing
|
||||
// the error when it looks like a plausible type alias type.
|
||||
if (
|
||||
isClass(type) &&
|
||||
!type.includeSubclasses &&
|
||||
!symbol.hasTypedDeclarations() &&
|
||||
ClassType.isValidTypeAliasClass(type)
|
||||
effectiveTypeInfo.includesIllegalTypeAliasDecl ||
|
||||
!TypeBase.isInstantiable(type) ||
|
||||
(flags & EvaluatorFlags.DoNotSpecialize) !== 0
|
||||
) {
|
||||
// This check exempts class types that are created by calling
|
||||
// NewType, NamedTuple, etc.
|
||||
isAllowedTypeForVariable = true;
|
||||
}
|
||||
|
||||
// Disable for assignments in the typings.pyi file, since it defines special forms.
|
||||
if (!isAllowedTypeForVariable && !fileInfo.isTypingStubFile) {
|
||||
// This might be a union that was previously a type alias
|
||||
// but was reconstituted in such a way that we lost the
|
||||
// typeAliasInfo. Avoid the false positive error by suppressing
|
||||
// the error when it looks like a plausible type alias type.
|
||||
if (
|
||||
effectiveTypeInfo.includesIllegalTypeAliasDecl ||
|
||||
!TypeBase.isInstantiable(type) ||
|
||||
(flags & EvaluatorFlags.DoNotSpecialize) !== 0
|
||||
) {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportInvalidTypeForm,
|
||||
LocMessage.typeAnnotationVariable(),
|
||||
node
|
||||
);
|
||||
type = UnknownType.create();
|
||||
}
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportInvalidTypeForm,
|
||||
LocMessage.typeAnnotationVariable(),
|
||||
node
|
||||
);
|
||||
type = UnknownType.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle the special case of "reveal_type" and "reveal_locals".
|
||||
if (name === 'reveal_type' || name === 'reveal_locals') {
|
||||
type = AnyType.create();
|
||||
} else {
|
||||
// Handle the special case of "reveal_type" and "reveal_locals".
|
||||
if (name === 'reveal_type' || name === 'reveal_locals') {
|
||||
type = AnyType.create();
|
||||
} else {
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportUndefinedVariable,
|
||||
LocMessage.symbolIsUndefined().format({ name }),
|
||||
node
|
||||
);
|
||||
addDiagnostic(
|
||||
DiagnosticRule.reportUndefinedVariable,
|
||||
LocMessage.symbolIsUndefined().format({ name }),
|
||||
node
|
||||
);
|
||||
|
||||
type = UnknownType.create();
|
||||
}
|
||||
type = UnknownType.create();
|
||||
}
|
||||
}
|
||||
|
||||
@ -17135,9 +17121,11 @@ export function createTypeEvaluator(
|
||||
|
||||
function evaluateTypeParameterList(node: TypeParameterListNode): TypeVarType[] {
|
||||
const paramTypes: TypeVarType[] = [];
|
||||
const typeParamScope = AnalyzerNodeInfo.getScope(node);
|
||||
assert(typeParamScope !== undefined);
|
||||
|
||||
node.parameters.forEach((param) => {
|
||||
const paramSymbol = AnalyzerNodeInfo.getTypeParameterSymbol(param.name);
|
||||
const paramSymbol = typeParamScope.symbolTable.get(param.name.value);
|
||||
if (!paramSymbol) {
|
||||
// This can happen if the code is unreachable.
|
||||
return;
|
||||
@ -20352,14 +20340,19 @@ export function createTypeEvaluator(
|
||||
honorCodeFlow: boolean,
|
||||
preferGlobalScope = false
|
||||
): SymbolWithScope | undefined {
|
||||
const scope = ScopeUtils.getScopeForNode(node);
|
||||
let symbolWithScope = scope?.lookUpSymbolRecursive(name);
|
||||
const scopeNodeInfo = ParseTreeUtils.getEvaluationScopeNode(node);
|
||||
const scope = AnalyzerNodeInfo.getScope(scopeNodeInfo.node);
|
||||
|
||||
let symbolWithScope = scope?.lookUpSymbolRecursive(name, { useProxyScope: !!scopeNodeInfo.useProxyScope });
|
||||
const scopeType = scope?.type ?? ScopeType.Module;
|
||||
|
||||
// Functions and list comprehensions don't allow access to implicitly
|
||||
// aliased symbols in outer scopes if they haven't yet been assigned
|
||||
// within the local scope.
|
||||
const scopeTypeHonorsCodeFlow = scopeType !== ScopeType.Function && scopeType !== ScopeType.ListComprehension;
|
||||
// within the local scope. Same with type parameter scopes.
|
||||
const scopeTypeHonorsCodeFlow =
|
||||
scopeType !== ScopeType.Function &&
|
||||
scopeType !== ScopeType.ListComprehension &&
|
||||
scopeType !== ScopeType.TypeParameter;
|
||||
|
||||
if (symbolWithScope && honorCodeFlow && scopeTypeHonorsCodeFlow) {
|
||||
// Filter the declarations based on flow reachability.
|
||||
@ -20416,11 +20409,10 @@ export function createTypeEvaluator(
|
||||
}
|
||||
|
||||
if (nextScopeToSearch) {
|
||||
symbolWithScope = nextScopeToSearch.lookUpSymbolRecursive(
|
||||
name,
|
||||
symbolWithScope = nextScopeToSearch.lookUpSymbolRecursive(name, {
|
||||
isOutsideCallerModule,
|
||||
isBeyondExecutionScope
|
||||
);
|
||||
isBeyondExecutionScope,
|
||||
});
|
||||
} else {
|
||||
symbolWithScope = undefined;
|
||||
}
|
||||
@ -20438,13 +20430,15 @@ export function createTypeEvaluator(
|
||||
while (
|
||||
curSymbolWithScope.scope.type !== ScopeType.Module &&
|
||||
curSymbolWithScope.scope.type !== ScopeType.Builtin &&
|
||||
curSymbolWithScope.scope.type !== ScopeType.TypeParameter &&
|
||||
curSymbolWithScope.scope.parent
|
||||
) {
|
||||
curSymbolWithScope = curSymbolWithScope.scope.parent.lookUpSymbolRecursive(
|
||||
name,
|
||||
curSymbolWithScope.isOutsideCallerModule,
|
||||
curSymbolWithScope.isBeyondExecutionScope || curSymbolWithScope.scope.isIndependentlyExecutable()
|
||||
);
|
||||
curSymbolWithScope = curSymbolWithScope.scope.parent.lookUpSymbolRecursive(name, {
|
||||
isOutsideCallerModule: curSymbolWithScope.isOutsideCallerModule,
|
||||
isBeyondExecutionScope:
|
||||
curSymbolWithScope.isBeyondExecutionScope ||
|
||||
curSymbolWithScope.scope.isIndependentlyExecutable(),
|
||||
});
|
||||
if (!curSymbolWithScope) {
|
||||
break;
|
||||
}
|
||||
@ -20767,23 +20761,15 @@ export function createTypeEvaluator(
|
||||
const isWithinTypeAliasStatement = !!ParseTreeUtils.getParentNodeOfType(node, ParseNodeType.TypeAlias);
|
||||
const allowForwardReferences = isWithinTypeAnnotation || isWithinTypeAliasStatement || fileInfo.isStubFile;
|
||||
|
||||
let symbol: Symbol | undefined;
|
||||
const typeParamSymbol = AnalyzerNodeInfo.getTypeParameterSymbol(node);
|
||||
if (typeParamSymbol) {
|
||||
symbol = typeParamSymbol;
|
||||
} else {
|
||||
const symbolWithScope = lookUpSymbolRecursive(
|
||||
node,
|
||||
node.value,
|
||||
!allowForwardReferences,
|
||||
isWithinTypeAnnotation
|
||||
);
|
||||
const symbolWithScope = lookUpSymbolRecursive(
|
||||
node,
|
||||
node.value,
|
||||
!allowForwardReferences,
|
||||
isWithinTypeAnnotation
|
||||
);
|
||||
|
||||
symbol = symbolWithScope?.symbol;
|
||||
}
|
||||
|
||||
if (symbol) {
|
||||
appendArray(declarations, symbol.getDeclarations());
|
||||
if (symbolWithScope) {
|
||||
appendArray(declarations, symbolWithScope.symbol.getDeclarations());
|
||||
}
|
||||
}
|
||||
|
||||
@ -21120,6 +21106,9 @@ export function createTypeEvaluator(
|
||||
);
|
||||
}
|
||||
|
||||
writeTypeCache(node, { type: typeVar }, /* flags */ undefined);
|
||||
writeTypeCache(node.name, { type: typeVar }, /* flags */ undefined);
|
||||
|
||||
return typeVar;
|
||||
}
|
||||
|
||||
|
@ -552,7 +552,11 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker {
|
||||
}
|
||||
|
||||
private _addIncomingCallForDeclaration(nameNode: NameNode) {
|
||||
const executionNode = ParseTreeUtils.getExecutionScopeNode(nameNode);
|
||||
let executionNode = ParseTreeUtils.getExecutionScopeNode(nameNode);
|
||||
while (executionNode && executionNode.nodeType === ParseNodeType.TypeParameterList) {
|
||||
executionNode = ParseTreeUtils.getExecutionScopeNode(executionNode);
|
||||
}
|
||||
|
||||
if (!executionNode) {
|
||||
return;
|
||||
}
|
||||
|
@ -2195,7 +2195,7 @@ export class CompletionProvider {
|
||||
|
||||
let startingNode: ParseNode = indexNode.baseExpression;
|
||||
if (declaration.node) {
|
||||
const scopeRoot = ParseTreeUtils.getEvaluationScopeNode(declaration.node);
|
||||
const scopeRoot = ParseTreeUtils.getEvaluationScopeNode(declaration.node).node;
|
||||
|
||||
// Find the lowest tree to search the symbol.
|
||||
if (
|
||||
|
@ -405,7 +405,7 @@ function isVisibleOutside(evaluator: TypeEvaluator, currentUri: Uri, node: NameN
|
||||
return true;
|
||||
}
|
||||
|
||||
const evalScope = ParseTreeUtils.getEvaluationScopeNode(decl.node);
|
||||
const evalScope = ParseTreeUtils.getEvaluationScopeNode(decl.node).node;
|
||||
|
||||
// If the declaration is at the module level or a class level, it can be seen
|
||||
// outside of the current module, so a global search is needed.
|
||||
@ -473,7 +473,8 @@ function isVisibleOutside(evaluator: TypeEvaluator, currentUri: Uri, node: NameN
|
||||
// Return true if the scope that contains the specified node is visible
|
||||
// outside of the current module, false if not.
|
||||
function isContainerExternallyVisible(node: NameNode, recursionCount: number) {
|
||||
const scopingNode = ParseTreeUtils.getEvaluationScopeNode(node);
|
||||
const scopingNode = ParseTreeUtils.getEvaluationScopeNode(node).node;
|
||||
|
||||
switch (scopingNode.nodeType) {
|
||||
case ParseNodeType.Class:
|
||||
case ParseNodeType.Function: {
|
||||
@ -484,6 +485,7 @@ function isVisibleOutside(evaluator: TypeEvaluator, currentUri: Uri, node: NameN
|
||||
|
||||
case ParseNodeType.Lambda:
|
||||
case ParseNodeType.ListComprehension:
|
||||
case ParseNodeType.TypeParameterList:
|
||||
// Symbols in this scope can't be visible outside.
|
||||
return false;
|
||||
|
||||
|
@ -2501,6 +2501,12 @@ export type ParseNode =
|
||||
| YieldNode
|
||||
| YieldFromNode;
|
||||
|
||||
export type EvaluationScopeNode = LambdaNode | FunctionNode | ModuleNode | ClassNode | ListComprehensionNode;
|
||||
export type ExecutionScopeNode = LambdaNode | FunctionNode | ModuleNode;
|
||||
export type EvaluationScopeNode =
|
||||
| LambdaNode
|
||||
| FunctionNode
|
||||
| ModuleNode
|
||||
| ClassNode
|
||||
| ListComprehensionNode
|
||||
| TypeParameterListNode;
|
||||
export type ExecutionScopeNode = LambdaNode | FunctionNode | ModuleNode | TypeParameterListNode;
|
||||
export type TypeParameterScopeNode = FunctionNode | ClassNode | TypeAliasNode;
|
||||
|
@ -23,6 +23,26 @@ def func2[T2](): ...
|
||||
class ClassC[T3, S1, T3]: ...
|
||||
|
||||
|
||||
class ClassD:
|
||||
class ClassE: ...
|
||||
|
||||
class ClassF:
|
||||
class A[T]: ...
|
||||
|
||||
int_alias = int
|
||||
|
||||
class B(A[int_alias]):
|
||||
pass
|
||||
|
||||
# This should genreate an error because ClassE is out of scope.
|
||||
class C(A[ClassE]):
|
||||
pass
|
||||
|
||||
|
||||
class ClassG[T](list["T"]):
|
||||
pass
|
||||
|
||||
|
||||
# This should generate an error because T3 is duplicated.
|
||||
def func3[T3, S1, T3](): ...
|
||||
|
||||
|
@ -18,7 +18,7 @@ test('TypeParams1', () => {
|
||||
configOptions.defaultPythonVersion = pythonVersion3_12;
|
||||
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeParams1.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 4);
|
||||
TestUtils.validateResults(analysisResults, 5);
|
||||
});
|
||||
|
||||
test('TypeParams2', () => {
|
||||
@ -232,7 +232,7 @@ test('TypeVarDefaultClass3', () => {
|
||||
configOptions.defaultPythonVersion = pythonVersion3_13;
|
||||
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeVarDefaultClass3.py'], configOptions);
|
||||
TestUtils.validateResults(analysisResults, 10);
|
||||
TestUtils.validateResults(analysisResults, 9);
|
||||
});
|
||||
|
||||
test('TypeVarDefaultClass4', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user