88import os
99import sys
1010
11+ from warnings import warn
12+
1113PY2 = sys .version_info < (3 , 0 )
1214PY32 = sys .version_info < (3 , 3 ) # Python 2.5 to 3.2
1315PY33 = sys .version_info < (3 , 4 ) # Python 2.5 to 3.3
@@ -141,6 +143,18 @@ def redefines(self, other):
141143 return isinstance (other , Definition ) and self .name == other .name
142144
143145
146+ class FutureImportation (Importation ):
147+ """
148+ A binding created by a from `__future__` import statement.
149+
150+ `__future__` imports are implicitly used.
151+ """
152+
153+ def __init__ (self , name , source , scope ):
154+ super (FutureImportation , self ).__init__ (name , source )
155+ self .used = (scope , source )
156+
157+
144158class Argument (Binding ):
145159 """
146160 Represents binding a name as an argument.
@@ -237,11 +251,12 @@ class GeneratorScope(Scope):
237251
238252
239253class ModuleScope (Scope ):
240- pass
254+ """Scope for a module."""
255+ _futures_allowed = True
241256
242257
243258class DoctestScope (ModuleScope ):
244- pass
259+ """Scope for a doctest."""
245260
246261
247262# Globally defined names which are not attributes of the builtins module, or
@@ -293,7 +308,6 @@ def __init__(self, tree, filename='(none)', builtins=None,
293308 self .withDoctest = withDoctest
294309 self .scopeStack = [ModuleScope ()]
295310 self .exceptHandlers = [()]
296- self .futuresAllowed = True
297311 self .root = tree
298312 self .handleChildren (tree )
299313 self .runDeferred (self ._deferredFunctions )
@@ -308,6 +322,26 @@ def __init__(self, tree, filename='(none)', builtins=None,
308322 self .popScope ()
309323 self .checkDeadScopes ()
310324
325+ @property
326+ def _module_scopes (self ):
327+ """Return tuple of module scope and doctest scope if it exists."""
328+ return tuple (scope for scope in self .scopeStack
329+ if isinstance (scope , ModuleScope ))
330+
331+ @property
332+ def futuresAllowed (self ):
333+ """Return whether `__future__` are permitted in the current context."""
334+ warn ('Checker.futuresAllowed is deprecated' , DeprecationWarning , 2 )
335+ if not isinstance (self .scope , ModuleScope ):
336+ return False
337+ return self .scope ._futures_allowed
338+
339+ @futuresAllowed .setter
340+ def futuresAllowed (self , value ):
341+ """Disable permitting `__future__` in the current context."""
342+ warn ('Checker.futuresAllowed is deprecated' , DeprecationWarning , 2 )
343+ self ._module_scopes [- 1 ]._futures_allowed = value
344+
311345 def deferFunction (self , callable ):
312346 """
313347 Schedule a function handler to be called just before completion.
@@ -602,9 +636,13 @@ def handleNode(self, node, parent):
602636 node .col_offset += self .offset [1 ]
603637 if self .traceTree :
604638 print (' ' * self .nodeDepth + node .__class__ .__name__ )
605- if self .futuresAllowed and not (isinstance (node , ast .ImportFrom ) or
606- self .isDocstring (node )):
607- self .futuresAllowed = False
639+
640+ if (isinstance (self .scope , ModuleScope ) and
641+ self .scope ._futures_allowed and
642+ not (isinstance (node , ast .ImportFrom ) or
643+ self .isDocstring (node ))):
644+ self .scope ._futures_allowed = False
645+
608646 self .nodeDepth += 1
609647 node .depth = self .nodeDepth
610648 node .parent = parent
@@ -948,21 +986,27 @@ def IMPORT(self, node):
948986
949987 def IMPORTFROM (self , node ):
950988 if node .module == '__future__' :
951- if not self .futuresAllowed :
989+ # __future__ can only appear in module/doctest scope and
990+ # should not have been disabled already in handleNode and
991+ # the scope must not have any other type of binding.
992+ if (not isinstance (self .scope , ModuleScope ) or
993+ not self .scope ._futures_allowed or
994+ any (not isinstance (binding , FutureImportation )
995+ for binding in self .scope .values ())):
996+ self .scope ._futures_allowed = False
952997 self .report (messages .LateFutureImport ,
953998 node , [n .name for n in node .names ])
954- else :
955- self .futuresAllowed = False
956999
9571000 for alias in node .names :
9581001 if alias .name == '*' :
9591002 self .scope .importStarred = True
9601003 self .report (messages .ImportStarUsed , node , node .module )
9611004 continue
9621005 name = alias .asname or alias .name
963- importation = Importation (name , node )
9641006 if node .module == '__future__' :
965- importation .used = (self .scope , node )
1007+ importation = FutureImportation (name , node , self .scope )
1008+ else :
1009+ importation = Importation (name , node )
9661010 self .addBinding (node , importation )
9671011
9681012 def TRY (self , node ):
0 commit comments