Skip to content

Commit fb1f223

Browse files
committed
checker.py: Handle UnboundLocal error for builtins
This patch ensures that UnboundLocalError is not ignored for Python builtins. Populates ModuleScope with Builtin nodes to manage them as bindings. Fixes #350
1 parent 9dd73ec commit fb1f223

File tree

3 files changed

+68
-13
lines changed

3 files changed

+68
-13
lines changed

pyflakes/checker.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ class Definition(Binding):
180180
"""
181181

182182

183+
class Builtin(Definition):
184+
"""A definition created for all Python builtins."""
185+
186+
def __init__(self, name):
187+
super(Builtin, self).__init__(name, None)
188+
189+
def __repr__(self):
190+
return '<%s object %r at 0x%x>' % (self.__class__.__name__,
191+
self.name,
192+
id(self))
193+
194+
183195
class UnhandledKeyType(object):
184196
"""
185197
A dictionary key of a type that we cannot or do not check for duplicates.
@@ -516,6 +528,8 @@ def __init__(self, tree, filename='(none)', builtins=None,
516528
raise RuntimeError('No scope implemented for the node %r' % tree)
517529
self.exceptHandlers = [()]
518530
self.root = tree
531+
for builtin in self.builtIns:
532+
self.addBinding(None, Builtin(builtin))
519533
self.handleChildren(tree)
520534
self.runDeferred(self._deferredFunctions)
521535
# Set _deferredFunctions to None so that deferFunction will fail
@@ -699,7 +713,8 @@ def addBinding(self, node, value):
699713
break
700714
existing = scope.get(value.name)
701715

702-
if existing and not self.differentForks(node, existing.source):
716+
if (existing and not isinstance(existing, Builtin) and
717+
not self.differentForks(node, existing.source)):
703718

704719
parent_stmt = self.getParent(value.source)
705720
if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For):
@@ -765,10 +780,6 @@ def handleNodeLoad(self, node):
765780
if in_generators is not False:
766781
in_generators = isinstance(scope, GeneratorScope)
767782

768-
# look in the built-ins
769-
if name in self.builtIns:
770-
return
771-
772783
if importStarred:
773784
from_list = []
774785

@@ -943,9 +954,7 @@ def handleDoctests(self, node):
943954
self.scopeStack = [self.scopeStack[0]]
944955
node_offset = self.offset or (0, 0)
945956
self.pushScope(DoctestScope)
946-
underscore_in_builtins = '_' in self.builtIns
947-
if not underscore_in_builtins:
948-
self.builtIns.add('_')
957+
self.addBinding(None, Builtin('_'))
949958
for example in examples:
950959
try:
951960
tree = compile(example.source, "<doctest>", "exec", ast.PyCF_ONLY_AST)
@@ -961,8 +970,6 @@ def handleDoctests(self, node):
961970
node_offset[1] + example.indent + 4)
962971
self.handleChildren(tree)
963972
self.offset = node_offset
964-
if not underscore_in_builtins:
965-
self.builtIns.remove('_')
966973
self.popScope()
967974
self.scopeStack = saved_stack
968975

pyflakes/messages.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,19 @@ def __init__(self, filename, loc, name):
100100

101101

102102
class UndefinedLocal(Message):
103-
message = ('local variable %r (defined in enclosing scope on line %r) '
104-
'referenced before assignment')
103+
message = 'local variable %r {0} referenced before assignment'
104+
105+
default = 'defined in enclosing scope on line %r'
106+
builtin = 'defined as a builtin'
105107

106108
def __init__(self, filename, loc, name, orig_loc):
107109
Message.__init__(self, filename, loc)
108-
self.message_args = (name, orig_loc.lineno)
110+
if orig_loc is None:
111+
self.message = self.message.format(self.builtin)
112+
self.message_args = name
113+
else:
114+
self.message = self.message.format(self.default)
115+
self.message_args = (name, orig_loc.lineno)
109116

110117

111118
class DuplicateArgument(Message):

pyflakes/test/test_builtin.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Tests for detecting redefinition of builtins.
3+
"""
4+
from sys import version_info
5+
6+
from pyflakes import messages as m
7+
from pyflakes.test.harness import TestCase, skipIf
8+
9+
10+
class TestBuiltins(TestCase):
11+
12+
def test_builtin_unbound_local(self):
13+
self.flakes('''
14+
def foo():
15+
a = range(1, 10)
16+
range = a
17+
return range
18+
19+
foo()
20+
21+
print(range)
22+
''', m.UndefinedLocal)
23+
24+
def test_global_shadowing_builtin(self):
25+
self.flakes('''
26+
def f():
27+
global range
28+
range = None
29+
print(range)
30+
31+
f()
32+
''')
33+
34+
@skipIf(version_info >= (3,), 'not an UnboundLocalError in Python 3')
35+
def test_builtin_in_comprehension(self):
36+
self.flakes('''
37+
def f():
38+
[range for range in range(1, 10)]
39+
40+
f()
41+
''', m.UndefinedLocal)

0 commit comments

Comments
 (0)