Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for deferred annotation evaluation for Annotated type… #5472

Merged
merged 2 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions packages/pyright-internal/src/analyzer/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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 {
Expand Down
39 changes: 28 additions & 11 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -7532,7 +7546,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) => {
Expand Down
18 changes: 18 additions & 0 deletions packages/pyright-internal/src/tests/samples/annotated2.py
Original file line number Diff line number Diff line change
@@ -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():
...
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);

Expand Down
Loading