Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
9 changes: 5 additions & 4 deletions byterun/pyobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import collections
import inspect
import re
import types

import six
Expand All @@ -23,7 +24,7 @@ def make_cell(value):
class Function(object):
__slots__ = [
'func_code', 'func_name', 'func_defaults', 'func_globals',
'func_locals', 'func_dict', 'func_closure',
'func_dict', 'func_closure',
'__name__', '__dict__', '__doc__',
'_vm', '_func',
]
Expand All @@ -34,7 +35,6 @@ def __init__(self, name, code, globs, defaults, closure, vm):
self.func_name = self.__name__ = name or code.co_name
self.func_defaults = tuple(defaults)
self.func_globals = globs
self.func_locals = self._vm.frame.f_locals
self.__dict__ = {}
self.func_closure = closure
self.__doc__ = code.co_consts[0] if code.co_consts else None
Expand All @@ -61,11 +61,12 @@ def __get__(self, instance, owner):
return self

def __call__(self, *args, **kwargs):
if PY2 and self.func_name in ["<setcomp>", "<dictcomp>", "<genexpr>"]:
if re.search(r'<(?:listcomp|setcomp|dictcomp|genexpr)>$', self.func_name):
# D'oh! http://bugs.python.org/issue19611 Py2 doesn't know how to
# inspect set comprehensions, dict comprehensions, or generator
# expressions properly. They are always functions of one argument,
# so just do the right thing.
# so just do the right thing. Py3.4 also would fail without this
# hack, for list comprehensions too. (Haven't checked for other 3.x.)
assert len(args) == 1 and not kwargs, "Surprising comprehension!"
callargs = {".0": args[0]}
else:
Expand Down
26 changes: 24 additions & 2 deletions byterun/pyvm2.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,10 @@ def byte_LOAD_GLOBAL(self, name):
elif name in f.f_builtins:
val = f.f_builtins[name]
else:
raise NameError("global name '%s' is not defined" % name)
if PY2:
raise NameError("global name '%s' is not defined" % name)
elif PY3:
raise NameError("name '%s' is not defined" % name)
self.push(val)

def byte_LOAD_DEREF(self, name):
Expand Down Expand Up @@ -1029,11 +1032,30 @@ def byte_BUILD_CLASS(self):
elif PY3:
def byte_LOAD_BUILD_CLASS(self):
# New in py3
self.push(__build_class__)
self.push(build_class)

def byte_STORE_LOCALS(self):
self.frame.f_locals = self.pop()

if 0: # Not in py2.7
def byte_SET_LINENO(self, lineno):
self.frame.f_lineno = lineno

if PY3:
def build_class(func, name, *bases, **kwds):
"Simplified (i.e., wrong) version of __build_class__."
assert isinstance(func, Function)
assert isinstance(name, str)
# This is simplified in that we don't yet handle metaclasses. So we do
# make sure there is no metaclass, before proceeding.
assert not kwds # No explicit metaclass or keyword arguments to the metaclass.
for base in bases:
assert type(base) == type # No implicit metaclass from the bases.
# OK, no metaclass; we may proceed.
# XXX What about func.func_closure? vm.make_frame() gives us no way to pass it in.
# We'll come back to fix this; for now, just make sure this case doesn't come up.
assert not func.func_closure
ns = {}
frame = func._vm.make_frame(func.func_code, f_globals=func.func_globals, f_locals=ns)
func._vm.run_frame(frame)
return type(name, bases, ns)
12 changes: 12 additions & 0 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@ def f4(g):
assert answer == 54
""")

def test_closure_vars_from_static_parent(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@darius I like this test. I've added it in my fork here rocky@e4782c0

What I like about this kind of test is that it simplifies testing. Instead of comparing output in the test runner, all the test runner has to do is run the program and the execution of the program tests itself.

And that way you can simply run the interpeter without any test framework to see if the issue is resoved. Real simple.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gotten around to studying why this test fails and it uncovers a fundamental flaw with how opcode arguments are passed in the current design, at least for "freeops" I think. Lacking a better place, I'll note it here.

It may be a while before I fix it in x-python.

The "deref" opcodes like LOAD_DEREF, also known as opcodes in the hasfree[] opcodes list, are passed a string name, but in the Python reference library of course, the operand is really an integer index.

In this inerpreter, the frame cells are a dictionary whereas in CPython frame cells are an array.

Normally, everything is fine because names are distinct. For example, you can't have two local variables called a. But in the cells array, names don't have to be distinct.

In particular in this test, the variable xs appears in two scopes. So when a LOAD_DEREF does its lookup into a dictionary, it finds the wrong value since the dictonary can only have one key with value xs.

self.assert_ok("""\
def f(xs):
return lambda: xs[0]

def g(h):
xs = 5
lambda: xs
return h()

assert g(f([42])) == 42
""")

class TestGenerators(vmtest.VmTestCase):
def test_first(self):
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# and then run "tox" from this directory.

[tox]
envlist = py27, py33
envlist = py27, py33, py34

[testenv]
commands =
Expand Down