Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ def __init__(self, name, source):
self.fullName = name


class FutureImportation(Importation):
"""
A binding created by a from `__future__` import statement.

`__future__` imports are implicitly used.
"""

def __init__(self, name, source, scope):
super(FutureImportation, self).__init__(name, source)
self.used = (scope, source)


class Argument(Binding):
"""
Represents binding a name as an argument.
Expand Down Expand Up @@ -255,6 +267,7 @@ class GeneratorScope(Scope):

class ModuleScope(Scope):
"""Scope for a module."""
_futures_allowed = True


class DoctestScope(ModuleScope):
Expand Down Expand Up @@ -310,7 +323,6 @@ def __init__(self, tree, filename='(none)', builtins=None,
self.withDoctest = withDoctest
self.scopeStack = [ModuleScope()]
self.exceptHandlers = [()]
self.futuresAllowed = True
self.root = tree
self.handleChildren(tree)
self.runDeferred(self._deferredFunctions)
Expand Down Expand Up @@ -356,6 +368,20 @@ def _in_doctest(self):
return (len(self.scopeStack) >= 2 and
isinstance(self.scopeStack[1], DoctestScope))

@property
def futuresAllowed(self):
if not all(isinstance(scope, ModuleScope)
for scope in self.scopeStack):
return False

return self.scope._futures_allowed

@futuresAllowed.setter
def futuresAllowed(self, value):
assert value is False
if isinstance(self.scope, ModuleScope):
self.scope._futures_allowed = False

@property
def scope(self):
return self.scopeStack[-1]
Expand Down Expand Up @@ -1025,7 +1051,9 @@ def IMPORTFROM(self, node):

for alias in node.names:
name = alias.asname or alias.name
if alias.name == '*':
if node.module == '__future__':
importation = FutureImportation(name, node, self.scope)
elif alias.name == '*':
# Only Python 2, local import * is a SyntaxWarning
if not PY2 and not isinstance(self.scope, ModuleScope):
self.report(messages.ImportStarNotPermitted,
Expand All @@ -1037,8 +1065,6 @@ def IMPORTFROM(self, node):
importation = StarImportation(node.module, node)
else:
importation = Importation(name, node)
if node.module == '__future__':
importation.used = (self.scope, node)
self.addBinding(node, importation)

def TRY(self, node):
Expand Down
10 changes: 2 additions & 8 deletions pyflakes/test/test_doctests.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,18 +431,12 @@ def func():


class TestOther(_DoctestMixin, TestOther):
pass
"""Run TestOther with each test wrapped in a doctest."""


class TestImports(_DoctestMixin, TestImports):

def test_futureImport(self):
"""XXX This test can't work in a doctest"""

def test_futureImportUsed(self):
"""XXX This test can't work in a doctest"""
"""Run TestImports with each test wrapped in a doctest."""


class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
"""Run TestUndefinedNames with each test wrapped in a doctest."""
pass