Added support for deferred annotation evaluation for Annotated type arguments beyond the first one. This addresses https://github.com/microsoft/pylance-release/issues/4565. (#5472)

Co-authored-by: Eric Traut <erictr@microsoft.com>
This commit is contained in:
Eric Traut 2023-07-12 00:24:14 +02:00 committed by GitHub
parent f9796dd5cd
commit 8b38b5dbf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 13 deletions

View File

@ -229,6 +229,9 @@ export class Binder extends ParseTreeWalker {
// Are we currently binding code located within an except block?
private _isInExceptSuite = false;
// Are we currently walking the type arguments to an Annotated type annotation?
private _isInAnnotatedAnnotation = false;
// A list of names assigned to __slots__ within a class.
private _dunderSlotsEntries: StringListNode[] | undefined;
@ -651,7 +654,12 @@ export class Binder extends ParseTreeWalker {
// and this can lead to a performance issue when walking the control
// flow graph if we need to evaluate every decorator.
if (!ParseTreeUtils.isNodeContainedWithinNodeType(node, ParseNodeType.Decorator)) {
this._createCallFlowNode(node);
// Skip if we're in an 'Annotated' annotation because this creates
// problems for "No Return" return type analysis when annotation
// evaluation is deferred.
if (!this._isInAnnotatedAnnotation) {
this._createCallFlowNode(node);
}
}
// Is this an manipulation of dunder all?
@ -1269,7 +1277,22 @@ export class Binder extends ParseTreeWalker {
override visitIndex(node: IndexNode): boolean {
AnalyzerNodeInfo.setFlowNode(node, this._currentFlowNode!);
return true;
this.walk(node.baseExpression);
// If we're within an 'Annotated' type annotation, set the flag.
const wasInAnnotatedAnnotation = this._isInAnnotatedAnnotation;
if (this._isTypingAnnotation(node.baseExpression, 'Annotated')) {
this._isInAnnotatedAnnotation = true;
}
node.items.forEach((argNode) => {
this.walk(argNode);
});
this._isInAnnotatedAnnotation = wasInAnnotatedAnnotation;
return false;
}
override visitIf(node: IfNode): boolean {

View File

@ -7171,17 +7171,31 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
let typeResult: TypeResultWithNode;
// If it's a custom __class_getitem__, none of the arguments should be
// treated as types. If it's an Annotated[a, b, c], only the first index
// should be treated as a type. The others can be regular (non-type) objects.
if (options?.hasCustomClassGetItem || (options?.isAnnotatedClass && argIndex > 0)) {
// treated as types.
if (options?.hasCustomClassGetItem) {
adjFlags =
EvaluatorFlags.DisallowParamSpec |
EvaluatorFlags.DisallowTypeVarTuple |
EvaluatorFlags.DoNotSpecialize |
EvaluatorFlags.DisallowClassVar;
typeResult = {
...getTypeOfExpression(
expr,
EvaluatorFlags.DisallowParamSpec |
EvaluatorFlags.DisallowTypeVarTuple |
EvaluatorFlags.DoNotSpecialize |
EvaluatorFlags.DisallowClassVar
),
...getTypeOfExpression(expr, adjFlags),
node: expr,
};
} else if (options?.isAnnotatedClass && argIndex > 0) {
// If it's an Annotated[a, b, c], only the first index should be
// treated as a type.The others can be regular(non - type) objects.
adjFlags =
EvaluatorFlags.DisallowParamSpec |
EvaluatorFlags.DisallowTypeVarTuple |
EvaluatorFlags.DoNotSpecialize |
EvaluatorFlags.DisallowClassVar;
if (isAnnotationEvaluationPostponed(AnalyzerNodeInfo.getFileInfo(node))) {
adjFlags |= EvaluatorFlags.AllowForwardReferences;
}
typeResult = {
...getTypeOfExpression(expr, adjFlags),
node: expr,
};
} else {
@ -7564,7 +7578,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
if (node.leftExpression.nodeType === ParseNodeType.Lambda) {
baseTypeResult = getTypeOfLambdaForCall(node, inferenceContext);
} else {
baseTypeResult = getTypeOfExpression(node.leftExpression, EvaluatorFlags.DoNotSpecialize);
baseTypeResult = getTypeOfExpression(
node.leftExpression,
EvaluatorFlags.DoNotSpecialize | (flags & EvaluatorFlags.AllowForwardReferences)
);
}
const argList = node.arguments.map((arg) => {

View File

@ -0,0 +1,18 @@
# This sample tests the case where Annotated is used with deferred
# annotation evaluation.
from __future__ import annotations
from typing import Annotated
v1: Annotated[str, ClassA, func1(), v2[0]] = ""
v2 = [1, 2, 3]
class ClassA:
...
def func1():
...

View File

@ -1146,6 +1146,12 @@ test('Annotated1', () => {
TestUtils.validateResults(analysisResults39, 3);
});
test('Annotated2', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['annotated2.py']);
TestUtils.validateResults(analysisResults, 0);
});
test('Circular1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['circular1.py']);