Skip to content

Commit

Permalink
handle deferred checking as a queue (#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
asottile authored Nov 27, 2022
1 parent 4158a45 commit c3273c5
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 20 deletions.
34 changes: 14 additions & 20 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import __future__
import builtins
import ast
import collections
import contextlib
import doctest
import functools
Expand Down Expand Up @@ -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')
Expand All @@ -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
Expand All @@ -762,12 +762,8 @@ def __init__(self, tree, filename='(none)', builtins=None,
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()
Expand All @@ -787,17 +783,18 @@ def deferFunction(self, callable):
`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))
Expand Down Expand Up @@ -1696,10 +1693,7 @@ def STR(self, node):
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):
Expand Down
14 changes: 14 additions & 0 deletions pyflakes/test/test_type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,20 @@ def f() -> Optional['Queue[str]']:
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("""
Expand Down

0 comments on commit c3273c5

Please sign in to comment.