Skip to content

Commit 8b42c5a

Browse files
committed
PR #20 from Darius
1 parent 604cfc4 commit 8b42c5a

File tree

5 files changed

+109
-37
lines changed

5 files changed

+109
-37
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ Allison Kaptur
44
Laura Lindzey
55
Rahul Gopinath
66
Björn Mathis
7+
Darius Bacon

byterun/pyobj.py

+12-21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import collections
44
import inspect
5+
import re
56
import types
67
import dis
78

@@ -64,17 +65,18 @@ def __get__(self, instance, owner):
6465
return self
6566

6667
def __call__(self, *args, **kwargs):
67-
if PY2 and self.func_name in ["<setcomp>", "<dictcomp>", "<genexpr>"]:
68+
if re.search(r'<(?:listcomp|setcomp|dictcomp|genexpr)>$', self.func_name):
6869
# D'oh! http://bugs.python.org/issue19611 Py2 doesn't know how to
6970
# inspect set comprehensions, dict comprehensions, or generator
7071
# expressions properly. They are always functions of one argument,
71-
# so just do the right thing.
72+
# so just do the right thing. Py3.4 also would fail without this
73+
# hack, for list comprehensions too. (Haven't checked for other 3.x.)
7274
assert len(args) == 1 and not kwargs, "Surprising comprehension!"
7375
callargs = {".0": args[0]}
7476
else:
7577
callargs = inspect.getcallargs(self._func, *args, **kwargs)
7678
frame = self._vm.make_frame(
77-
self.func_code, callargs, self.func_globals, {}
79+
self.func_code, callargs, self.func_globals, {}, self.func_closure
7880
)
7981
CO_GENERATOR = 32 # flag for "this code uses yield"
8082
if self.func_code.co_flags & CO_GENERATOR:
@@ -138,7 +140,7 @@ def set(self, value):
138140

139141

140142
class Frame(object):
141-
def __init__(self, f_code, f_globals, f_locals, f_back):
143+
def __init__(self, f_code, f_globals, f_locals, f_closure, f_back):
142144
self.f_code = f_code
143145
self.py36_opcodes = list(dis.get_instructions(self.f_code)) \
144146
if six.PY3 and sys.version_info.minor >= 6 else None
@@ -156,24 +158,13 @@ def __init__(self, f_code, f_globals, f_locals, f_back):
156158
self.f_lineno = f_code.co_firstlineno
157159
self.f_lasti = 0
158160

159-
if f_code.co_cellvars:
160-
self.cells = {}
161-
if not f_back.cells:
162-
f_back.cells = {}
163-
for var in f_code.co_cellvars:
164-
# Make a cell for the variable in our locals, or None.
165-
cell = Cell(self.f_locals.get(var))
166-
f_back.cells[var] = self.cells[var] = cell
167-
else:
168-
self.cells = None
169-
161+
self.cells = {} if f_code.co_cellvars or f_code.co_freevars else None
162+
for var in f_code.co_cellvars:
163+
# Make a cell for the variable in our locals, or None.
164+
self.cells[var] = Cell(self.f_locals.get(var))
170165
if f_code.co_freevars:
171-
if not self.cells:
172-
self.cells = {}
173-
for var in f_code.co_freevars:
174-
assert self.cells is not None
175-
assert f_back.cells, "f_back.cells: %r" % (f_back.cells,)
176-
self.cells[var] = f_back.cells[var]
166+
assert len(f_code.co_freevars) == len(f_closure)
167+
self.cells.update(zip(f_code.co_freevars, f_closure))
177168

178169
self.block_stack = []
179170
self.generator = None

byterun/pyvm2.py

+56-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
PY3, PY2 = six.PY3, not six.PY3
1818

19-
from .pyobj import Frame, Block, Method, Function, Generator
19+
from .pyobj import Frame, Block, Method, Function, Generator, Cell
2020

2121
log = logging.getLogger(__name__)
2222

@@ -91,7 +91,7 @@ def push_block(self, type, handler=None, level=None):
9191
def pop_block(self):
9292
return self.frame.block_stack.pop()
9393

94-
def make_frame(self, code, callargs={}, f_globals=None, f_locals=None):
94+
def make_frame(self, code, callargs={}, f_globals=None, f_locals=None, f_closure=None):
9595
log.info("make_frame: code=%r, callargs=%s" % (code, repper(callargs)))
9696
if f_globals is not None:
9797
f_globals = f_globals
@@ -108,7 +108,7 @@ def make_frame(self, code, callargs={}, f_globals=None, f_locals=None):
108108
'__package__': None,
109109
}
110110
f_locals.update(callargs)
111-
frame = Frame(code, f_globals, f_locals, self.frame)
111+
frame = Frame(code, f_globals, f_locals, f_closure, self.frame)
112112
return frame
113113

114114
def push_frame(self, frame):
@@ -446,7 +446,10 @@ def byte_LOAD_GLOBAL(self, name):
446446
elif name in f.f_builtins:
447447
val = f.f_builtins[name]
448448
else:
449-
raise NameError("global name '%s' is not defined" % name)
449+
if PY2:
450+
raise NameError("global name '%s' is not defined" % name)
451+
elif PY3:
452+
raise NameError("name '%s' is not defined" % name)
450453
self.push(val)
451454

452455
def byte_STORE_GLOBAL(self, name):
@@ -1169,11 +1172,59 @@ def byte_BUILD_CLASS(self):
11691172
elif PY3:
11701173
def byte_LOAD_BUILD_CLASS(self):
11711174
# New in py3
1172-
self.push(__build_class__)
1175+
self.push(build_class)
11731176

11741177
def byte_STORE_LOCALS(self):
11751178
self.frame.f_locals = self.pop()
11761179

11771180
if 0: # Not in py2.7
11781181
def byte_SET_LINENO(self, lineno):
11791182
self.frame.f_lineno = lineno
1183+
1184+
if PY3:
1185+
def build_class(func, name, *bases, **kwds):
1186+
"Like __build_class__ in bltinmodule.c, but running in the byterun VM."
1187+
if not isinstance(func, Function):
1188+
raise TypeError("func must be a function")
1189+
if not isinstance(name, str):
1190+
raise TypeError("name is not a string")
1191+
metaclass = kwds.pop('metaclass', None)
1192+
# (We don't just write 'metaclass=None' in the signature above
1193+
# because that's a syntax error in Py2.)
1194+
if metaclass is None:
1195+
metaclass = type(bases[0]) if bases else type
1196+
if isinstance(metaclass, type):
1197+
metaclass = calculate_metaclass(metaclass, bases)
1198+
1199+
try:
1200+
prepare = metaclass.__prepare__
1201+
except AttributeError:
1202+
namespace = {}
1203+
else:
1204+
namespace = prepare(name, bases, **kwds)
1205+
1206+
# Execute the body of func. This is the step that would go wrong if
1207+
# we tried to use the built-in __build_class__, because __build_class__
1208+
# does not call func, it magically executes its body directly, as we
1209+
# do here (except we invoke our VirtualMachine instead of CPython's).
1210+
frame = func._vm.make_frame(func.func_code,
1211+
f_globals=func.func_globals,
1212+
f_locals=namespace,
1213+
f_closure=func.func_closure)
1214+
cell = func._vm.run_frame(frame)
1215+
1216+
cls = metaclass(name, bases, namespace)
1217+
if isinstance(cell, Cell):
1218+
cell.set(cls)
1219+
return cls
1220+
1221+
def calculate_metaclass(metaclass, bases):
1222+
"Determine the most derived metatype."
1223+
winner = metaclass
1224+
for base in bases:
1225+
t = type(base)
1226+
if issubclass(t, winner):
1227+
winner = t
1228+
elif not issubclass(winner, t):
1229+
raise TypeError("metaclass conflict", winner, t)
1230+
return winner

tests/test_functions.py

+12
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,18 @@ def f4(g):
207207
assert answer == 54
208208
""")
209209

210+
def test_closure_vars_from_static_parent(self):
211+
self.assert_ok("""\
212+
def f(xs):
213+
return lambda: xs[0]
214+
215+
def g(h):
216+
xs = 5
217+
lambda: xs
218+
return h()
219+
220+
assert g(f([42])) == 42
221+
""")
210222

211223
class TestGenerators(vmtest.VmTestCase):
212224
def test_first(self):

tests/vmtest.py

+28-11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ def assert_ok(self, code, raises=None):
4040
# Print the disassembly so we'll see it if the test fails.
4141
dis_code(code)
4242

43+
# Run the code through our VM and the real Python interpreter, for comparison.
44+
vm_value, vm_exc, vm_stdout = self.run_in_byterun(code)
45+
py_value, py_exc, py_stdout = self.run_in_real_python(code)
46+
47+
self.assert_same_exception(vm_exc, py_exc)
48+
self.assertEqual(vm_stdout.getvalue(), py_stdout.getvalue())
49+
self.assertEqual(vm_value, py_value)
50+
if raises:
51+
self.assertIsInstance(vm_exc, raises)
52+
else:
53+
self.assertIsNone(vm_exc)
54+
55+
def run_in_byterun(self, code):
4356
real_stdout = sys.stdout
4457

4558
# Run the code through our VM.
@@ -64,32 +77,36 @@ def assert_ok(self, code, raises=None):
6477
raise
6578
vm_exc = e
6679
finally:
80+
sys.stdout = real_stdout
6781
real_stdout.write("-- stdout ----------\n")
6882
real_stdout.write(vm_stdout.getvalue())
6983

70-
# Run the code through the real Python interpreter, for comparison.
84+
return vm_value, vm_exc, vm_stdout
85+
86+
def run_in_real_python(self, code):
87+
real_stdout = sys.stdout
7188

7289
py_stdout = six.StringIO()
7390
sys.stdout = py_stdout
7491

7592
py_value = py_exc = None
76-
globs = {}
93+
globs = {
94+
'__builtins__': __builtins__,
95+
'__name__': '__main__',
96+
'__doc__': None,
97+
'__package__': None,
98+
}
99+
77100
try:
78101
py_value = eval(code, globs, globs)
79102
except AssertionError: # pragma: no cover
80103
raise
81104
except Exception as e:
82105
py_exc = e
106+
finally:
107+
sys.stdout = real_stdout
83108

84-
sys.stdout = real_stdout
85-
86-
self.assert_same_exception(vm_exc, py_exc)
87-
self.assertEqual(vm_stdout.getvalue(), py_stdout.getvalue())
88-
self.assertEqual(vm_value, py_value)
89-
if raises:
90-
self.assertIsInstance(vm_exc, raises)
91-
else:
92-
self.assertIsNone(vm_exc)
109+
return py_value, py_exc, py_stdout
93110

94111
def assert_same_exception(self, e1, e2):
95112
"""Exceptions don't implement __eq__, check it ourselves."""

0 commit comments

Comments
 (0)