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:
Eric Traut 2024-04-23 21:05:07 -07:00 committed by GitHub
parent 3875288287
commit 400bb92da0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 546 additions and 433 deletions

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 (

View File

@ -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;

View File

@ -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;

View File

@ -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](): ...

View File

@ -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', () => {