Skip to content

Commit c3273c5

Browse files
authored
handle deferred checking as a queue (#754)
1 parent 4158a45 commit c3273c5

File tree

2 files changed

+28
-20
lines changed

2 files changed

+28
-20
lines changed

pyflakes/checker.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import __future__
88
import builtins
99
import ast
10+
import collections
1011
import contextlib
1112
import doctest
1213
import functools
@@ -735,7 +736,6 @@ class Checker:
735736
nodeDepth = 0
736737
offset = None
737738
_in_annotation = AnnotationState.NONE
738-
_in_deferred = False
739739

740740
builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
741741
_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
@@ -746,7 +746,7 @@ class Checker:
746746
def __init__(self, tree, filename='(none)', builtins=None,
747747
withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()):
748748
self._nodeHandlers = {}
749-
self._deferredFunctions = []
749+
self._deferred = collections.deque()
750750
self.deadScopes = []
751751
self.messages = []
752752
self.filename = filename
@@ -762,12 +762,8 @@ def __init__(self, tree, filename='(none)', builtins=None,
762762
for builtin in self.builtIns:
763763
self.addBinding(None, Builtin(builtin))
764764
self.handleChildren(tree)
765-
self._in_deferred = True
766-
self.runDeferred(self._deferredFunctions)
767-
# Set _deferredFunctions to None so that deferFunction will fail
768-
# noisily if called after we've run through the deferred functions.
769-
self._deferredFunctions = None
770-
del self.scopeStack[1:]
765+
766+
self._run_deferred()
771767

772768
self.popScope()
773769
self.checkDeadScopes()
@@ -787,17 +783,18 @@ def deferFunction(self, callable):
787783
`callable` is called, the scope at the time this is called will be
788784
restored, however it will contain any new bindings added to it.
789785
"""
790-
self._deferredFunctions.append((callable, self.scopeStack[:], self.offset))
786+
self._deferred.append((callable, self.scopeStack[:], self.offset))
791787

792-
def runDeferred(self, deferred):
793-
"""
794-
Run the callables in C{deferred} using their associated scope stack.
795-
"""
796-
for handler, scope, offset in deferred:
797-
self.scopeStack = scope
798-
self.offset = offset
788+
def _run_deferred(self):
789+
orig = (self.scopeStack, self.offset)
790+
791+
while self._deferred:
792+
handler, scope, offset = self._deferred.popleft()
793+
self.scopeStack, self.offset = scope, offset
799794
handler()
800795

796+
self.scopeStack, self.offset = orig
797+
801798
def _in_doctest(self):
802799
return (len(self.scopeStack) >= 2 and
803800
isinstance(self.scopeStack[1], DoctestScope))
@@ -1696,10 +1693,7 @@ def STR(self, node):
16961693
node.col_offset,
16971694
messages.ForwardAnnotationSyntaxError,
16981695
)
1699-
if self._in_deferred:
1700-
fn()
1701-
else:
1702-
self.deferFunction(fn)
1696+
self.deferFunction(fn)
17031697

17041698
def CONSTANT(self, node):
17051699
if isinstance(node.value, str):

pyflakes/test/test_type_annotations.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,20 @@ def f() -> Optional['Queue[str]']:
594594
return None
595595
""")
596596

597+
def test_forward_annotations_for_classes_in_scope(self):
598+
# see #749
599+
self.flakes("""
600+
from typing import Optional
601+
602+
def f():
603+
class C:
604+
a: "D"
605+
b: Optional["D"]
606+
c: "Optional[D]"
607+
608+
class D: pass
609+
""")
610+
597611
def test_idomiatic_typing_guards(self):
598612
# typing.TYPE_CHECKING: python3.5.3+
599613
self.flakes("""

0 commit comments

Comments
 (0)