summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Sottile <asottile@umich.edu>2022-11-27 12:56:55 -0800
committerGitHub <noreply@github.com>2022-11-27 15:56:55 -0500
commitc3273c54c94cb96927d730c6bf5d12383c85777f (patch)
treecf5c48ff78d241640ad07ddba405dc574074faff
parent4158a4537773ec3da41aed5afdea46c121082804 (diff)
downloadpyflakes-c3273c54c94cb96927d730c6bf5d12383c85777f.tar.gz
handle deferred checking as a queue (#754)
-rw-r--r--pyflakes/checker.py34
-rw-r--r--pyflakes/test/test_type_annotations.py14
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("""