From c3273c54c94cb96927d730c6bf5d12383c85777f Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 27 Nov 2022 12:56:55 -0800 Subject: handle deferred checking as a queue (#754) --- pyflakes/checker.py | 34 ++++++++++++++-------------------- pyflakes/test/test_type_annotations.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 919e1bb..7ee6a1d 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -7,6 +7,7 @@ Also, it models the Bindings and Scopes. import __future__ import builtins import ast +import collections import contextlib import doctest import functools @@ -735,7 +736,6 @@ class Checker: nodeDepth = 0 offset = None _in_annotation = AnnotationState.NONE - _in_deferred = False builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') @@ -746,7 +746,7 @@ class Checker: def __init__(self, tree, filename='(none)', builtins=None, withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): self._nodeHandlers = {} - self._deferredFunctions = [] + self._deferred = collections.deque() self.deadScopes = [] self.messages = [] self.filename = filename @@ -762,12 +762,8 @@ class Checker: for builtin in self.builtIns: self.addBinding(None, Builtin(builtin)) self.handleChildren(tree) - self._in_deferred = True - self.runDeferred(self._deferredFunctions) - # Set _deferredFunctions to None so that deferFunction will fail - # noisily if called after we've run through the deferred functions. - self._deferredFunctions = None - del self.scopeStack[1:] + + self._run_deferred() self.popScope() self.checkDeadScopes() @@ -787,17 +783,18 @@ class Checker: `callable` is called, the scope at the time this is called will be restored, however it will contain any new bindings added to it. """ - self._deferredFunctions.append((callable, self.scopeStack[:], self.offset)) + self._deferred.append((callable, self.scopeStack[:], self.offset)) - def runDeferred(self, deferred): - """ - Run the callables in C{deferred} using their associated scope stack. - """ - for handler, scope, offset in deferred: - self.scopeStack = scope - self.offset = offset + def _run_deferred(self): + orig = (self.scopeStack, self.offset) + + while self._deferred: + handler, scope, offset = self._deferred.popleft() + self.scopeStack, self.offset = scope, offset handler() + self.scopeStack, self.offset = orig + def _in_doctest(self): return (len(self.scopeStack) >= 2 and isinstance(self.scopeStack[1], DoctestScope)) @@ -1696,10 +1693,7 @@ class Checker: node.col_offset, messages.ForwardAnnotationSyntaxError, ) - if self._in_deferred: - fn() - else: - self.deferFunction(fn) + self.deferFunction(fn) def CONSTANT(self, node): if isinstance(node.value, str): diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py index 2f27b06..f0fd3b9 100644 --- a/pyflakes/test/test_type_annotations.py +++ b/pyflakes/test/test_type_annotations.py @@ -594,6 +594,20 @@ class TestTypeAnnotations(TestCase): return None """) + def test_forward_annotations_for_classes_in_scope(self): + # see #749 + self.flakes(""" + from typing import Optional + + def f(): + class C: + a: "D" + b: Optional["D"] + c: "Optional[D]" + + class D: pass + """) + def test_idomiatic_typing_guards(self): # typing.TYPE_CHECKING: python3.5.3+ self.flakes(""" -- cgit v1.2.1