Skip to content

Commit

Permalink
Fixed a bug that leads to a false positive when a function is decorat…
Browse files Browse the repository at this point in the history
…ed and has no explicit return type annotation and the body references the decorated function in a loop. This addresses #9492.
  • Loading branch information
erictraut committed Nov 29, 2024
1 parent 546f70f commit ee78648
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 2 deletions.
12 changes: 11 additions & 1 deletion packages/pyright-internal/src/analyzer/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,17 @@ export function applyFunctionDecorator(
}
}

let returnType = getTypeOfDecorator(evaluator, decoratorNode, inputFunctionType);
// Clear the PartiallyEvaluated flag in the input if it's set so
// it doesn't propagate to the decorated type.
const decoratorArg =
isFunction(inputFunctionType) && FunctionType.isPartiallyEvaluated(inputFunctionType)
? FunctionType.cloneWithNewFlags(
inputFunctionType,
inputFunctionType.shared.flags & ~FunctionTypeFlags.PartiallyEvaluated
)
: inputFunctionType;

let returnType = getTypeOfDecorator(evaluator, decoratorNode, decoratorArg);

// Check for some built-in decorator types with known semantics.
if (isFunction(decoratorType)) {
Expand Down
5 changes: 4 additions & 1 deletion packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18354,7 +18354,6 @@ export function createTypeEvaluator(
// Set the "partially evaluated" flag around this logic to detect recursion.
functionType.shared.flags |= FunctionTypeFlags.PartiallyEvaluated;
const preDecoratedType = node.d.isAsync ? createAsyncFunction(node, functionType) : functionType;
functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated;

// Apply all of the decorators in reverse order.
decoratedType = preDecoratedType;
Expand Down Expand Up @@ -18402,6 +18401,10 @@ export function createTypeEvaluator(

writeTypeCache(node, { type: decoratedType }, EvalFlags.None);

// Now that the decorator has been applied, we can clear the
// "partially evaluated" flag.
functionType.shared.flags &= ~FunctionTypeFlags.PartiallyEvaluated;

return { functionType, decoratedType };
}

Expand Down
13 changes: 13 additions & 0 deletions packages/pyright-internal/src/tests/samples/loop52.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This sample tests the case where a function accesses its own decorated
# form within a loop.

from contextlib import contextmanager


@contextmanager
def func1():
yield

for _ in ():
with func1():
return
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@ test('Loop51', () => {
TestUtils.validateResults(analysisResults, 0);
});

test('Loop52', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['loop52.py']);

TestUtils.validateResults(analysisResults, 0);
});

test('ForLoop1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['forLoop1.py']);

Expand Down

0 comments on commit ee78648

Please sign in to comment.