Skip to content

Commit 51da74d

Browse files
committed
Allow __future__ in doctest
Deprecate Checker.futuresAllowed as it is now unused, and is typically unnecessary. The deprecated futuresAllowed refers only to the module scope. Add test_checker to ensure backwards compatibility for Checker.futuresAllowed.
1 parent f048360 commit 51da74d

File tree

2 files changed

+39
-20
lines changed

2 files changed

+39
-20
lines changed

pyflakes/checker.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,18 @@ def redefines(self, other):
141141
return isinstance(other, Definition) and self.name == other.name
142142

143143

144+
class FutureImportation(Importation):
145+
"""
146+
A binding created by a from `__future__` import statement.
147+
148+
`__future__` imports are implicitly used.
149+
"""
150+
151+
def __init__(self, name, source, scope):
152+
super(FutureImportation, self).__init__(name, source)
153+
self.used = (scope, source)
154+
155+
144156
class Argument(Binding):
145157
"""
146158
Represents binding a name as an argument.
@@ -237,11 +249,12 @@ class GeneratorScope(Scope):
237249

238250

239251
class ModuleScope(Scope):
240-
pass
252+
"""Scope for a module."""
253+
_futures_allowed = True
241254

242255

243256
class DoctestScope(ModuleScope):
244-
pass
257+
"""Scope for a doctest."""
245258

246259

247260
# Globally defined names which are not attributes of the builtins module, or
@@ -293,7 +306,6 @@ def __init__(self, tree, filename='(none)', builtins=None,
293306
self.withDoctest = withDoctest
294307
self.scopeStack = [ModuleScope()]
295308
self.exceptHandlers = [()]
296-
self.futuresAllowed = True
297309
self.root = tree
298310
self.handleChildren(tree)
299311
self.runDeferred(self._deferredFunctions)
@@ -606,9 +618,13 @@ def handleNode(self, node, parent):
606618
node.col_offset += self.offset[1]
607619
if self.traceTree:
608620
print(' ' * self.nodeDepth + node.__class__.__name__)
609-
if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or
610-
self.isDocstring(node)):
611-
self.futuresAllowed = False
621+
622+
if (isinstance(self.scope, ModuleScope) and
623+
self.scope._futures_allowed and
624+
not (isinstance(node, ast.ImportFrom) or
625+
self.isDocstring(node))):
626+
self.scope._futures_allowed = False
627+
612628
self.nodeDepth += 1
613629
node.depth = self.nodeDepth
614630
node.parent = parent
@@ -952,21 +968,27 @@ def IMPORT(self, node):
952968

953969
def IMPORTFROM(self, node):
954970
if node.module == '__future__':
955-
if not self.futuresAllowed:
971+
# __future__ can only appear in module/doctest scope and
972+
# should not have been disabled already in handleNode and
973+
# the scope must not have any other type of binding.
974+
if (not isinstance(self.scope, ModuleScope) or
975+
not self.scope._futures_allowed or
976+
any(not isinstance(binding, FutureImportation)
977+
for binding in self.scope.values())):
978+
self.scope._futures_allowed = False
956979
self.report(messages.LateFutureImport,
957980
node, [n.name for n in node.names])
958-
else:
959-
self.futuresAllowed = False
960981

961982
for alias in node.names:
962-
if alias.name == '*':
983+
name = alias.asname or alias.name
984+
if node.module == '__future__':
985+
importation = FutureImportation(name, node, self.scope)
986+
elif alias.name == '*':
963987
self.scope.importStarred = True
964988
self.report(messages.ImportStarUsed, node, node.module)
965989
continue
966-
name = alias.asname or alias.name
967-
importation = Importation(name, node)
968-
if node.module == '__future__':
969-
importation.used = (self.scope, node)
990+
else:
991+
importation = Importation(name, node)
970992
self.addBinding(node, importation)
971993

972994
def TRY(self, node):

pyflakes/test/test_doctests.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,16 +363,13 @@ def func():
363363

364364

365365
class TestOther(_DoctestMixin, TestOther):
366+
"""Run TestOther with each test wrapped in a doctest."""
366367
pass
367368

368369

369370
class TestImports(_DoctestMixin, TestImports):
370-
371-
def test_futureImport(self):
372-
"""XXX This test can't work in a doctest"""
373-
374-
def test_futureImportUsed(self):
375-
"""XXX This test can't work in a doctest"""
371+
"""Run TestImports with each test wrapped in a doctest."""
372+
pass
376373

377374

378375
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):

0 commit comments

Comments
 (0)