Skip to content

Commit

Permalink
Allow some builtins to be deleted
Browse files Browse the repository at this point in the history
__builtins__, __file__ and __debug__ can be deleted.

Adds a test runner that ensures defined builtins can be
loaded, and can be deleted when the python runtime allows it.
  • Loading branch information
jayvdb committed Aug 3, 2018
1 parent fc2325b commit 60f5cad
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 1 deletion.
13 changes: 12 additions & 1 deletion pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ class Builtin(Definition):

def __init__(self, name):
super(Builtin, self).__init__(name, None)
self.can_delete = False
# __file__ can only be deleted on Python 3
if not PY2 and name == '__file__':
self.can_delete = True
# __builtins__ can always be deleted.
# __debug__ can be deleted sometimes and not deleted other times.
# Safest course of action is to assume it can be deleted, in
# order that no error is reported by pyflakes
elif name in ('__builtins__'):
self.can_delete = True

def __repr__(self):
return '<%s object %r at 0x%x>' % (self.__class__.__name__,
Expand Down Expand Up @@ -865,7 +875,8 @@ def on_conditional_branch():
self.scope.globals.remove(name)
else:
binding = self.scope.get(name, None)
if not binding or isinstance(binding, Builtin):
if not binding or (
isinstance(binding, Builtin) and not binding.can_delete):
self.report(messages.UndefinedName, node, name)
return

Expand Down
10 changes: 10 additions & 0 deletions pyflakes/test/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ class TestCase(unittest.TestCase):

withDoctest = False

def pythonException(self, input, *expectedOutputs, **kw):
try:
compile(textwrap.dedent(input), '<test>', 'exec', PyCF_ONLY_AST)
except BaseException as e:
return e
try:
exec(textwrap.dedent(input), {})
except BaseException as e:
return e

def flakes(self, input, *expectedOutputs, **kw):
tree = compile(textwrap.dedent(input), "<test>", "exec", PyCF_ONLY_AST)
if kw.get('is_segment'):
Expand Down
55 changes: 55 additions & 0 deletions pyflakes/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@
from sys import version_info

from pyflakes import messages as m
from pyflakes.checker import Checker
from pyflakes.test.harness import TestCase, skipIf

try:
WindowsError
WIN = True
except NameError:
WIN = False


class TestBuiltins(TestCase):

Expand Down Expand Up @@ -39,3 +46,51 @@ def f():
f()
''', m.UndefinedLocal)


class TestLiveBuiltins(TestCase):

def test_exists(self):
for name in sorted(Checker.builtIns):
# __file__ does exist in this test harness
if name == '__file__':
continue

if name == 'WindowsError' and not WIN:
continue

source = '''
%s
''' % name
e = self.pythonException(source)
self.assertIsNone(e)

def test_del(self):
for name in sorted(Checker.builtIns):
# __file__ does exist in this test harness
if name == '__file__':
continue

# __debug__ can be deleted sometimes and not deleted other times.
# Safest course of action is to assume it can be deleted, in
# order that no error is reported by pyflakes
if name == '__debug__':
continue

source = '''
del %s
''' % name

e = self.pythonException(source)

if isinstance(e, SyntaxError):
if version_info < (3,):
# SyntaxError: invalid syntax
self.assertIn(name, ('print'))
else:
# SyntaxError: can't delete keyword
self.assertIn(name, ('None', 'True', 'False'))
elif isinstance(e, NameError):
self.flakes(source, m.UndefinedName)
else:
self.flakes(source)

0 comments on commit 60f5cad

Please sign in to comment.