Commit 6fc4bd3c authored by scoder's avatar scoder

Merge pull request #65 from vitek/_T731_locals_scope

 T731 locals() scope, eval(), exec()
parents 6ae02221 b4e58e0b
...@@ -97,7 +97,7 @@ globals_utility_code = UtilityCode( ...@@ -97,7 +97,7 @@ globals_utility_code = UtilityCode(
# of Python names. Supporting cdef names in the module and write # of Python names. Supporting cdef names in the module and write
# access requires a rewrite as a dedicated class. # access requires a rewrite as a dedicated class.
proto = """ proto = """
static PyObject* __Pyx_Globals(); /*proto*/ static PyObject* __Pyx_Globals(void); /*proto*/
""", """,
impl = ''' impl = '''
static PyObject* __Pyx_Globals() { static PyObject* __Pyx_Globals() {
......
...@@ -5869,6 +5869,57 @@ class YieldExprNode(ExprNode): ...@@ -5869,6 +5869,57 @@ class YieldExprNode(ExprNode):
else: else:
code.putln(code.error_goto_if_null(Naming.sent_value_cname, self.pos)) code.putln(code.error_goto_if_null(Naming.sent_value_cname, self.pos))
class GlobalsExprNode(AtomicExprNode):
type = dict_type
is_temp = 1
def analyse_types(self, env):
env.use_utility_code(Builtin.globals_utility_code)
gil_message = "Constructing globals dict"
def generate_result_code(self, code):
code.putln('%s = __Pyx_Globals(); %s' % (
self.result(),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.result())
class FuncLocalsExprNode(DictNode):
def __init__(self, pos, env):
local_vars = [var.name for var in env.entries.values() if var.name]
items = [DictItemNode(pos, key=IdentifierStringNode(pos, value=var),
value=NameNode(pos, name=var, allow_null=True))
for var in local_vars]
DictNode.__init__(self, pos, key_value_pairs=items,
exclude_null_values=True)
class PyClassLocalsExprNode(AtomicExprNode):
def __init__(self, pos, pyclass_dict):
AtomicExprNode.__init__(self, pos)
self.pyclass_dict = pyclass_dict
def analyse_types(self, env):
self.type = self.pyclass_dict.type
self.is_tmep = 0
def result(self):
return self.pyclass_dict.result()
def generate_result_code(self, code):
pass
def LocalsExprNode(pos, scope_node, env):
if env.is_module_scope:
return GlobalsExprNode(pos)
if env.is_py_class_scope:
return PyClassLocalsExprNode(pos, scope_node.dict)
return FuncLocalsExprNode(pos, env)
#------------------------------------------------------------------- #-------------------------------------------------------------------
# #
# Unary operator nodes # Unary operator nodes
......
...@@ -2266,6 +2266,17 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2266,6 +2266,17 @@ class TransformBuiltinMethods(EnvTransform):
error(node.pos, u"'%s' not a valid cython attribute or is being used incorrectly" % attribute) error(node.pos, u"'%s' not a valid cython attribute or is being used incorrectly" % attribute)
return node return node
def visit_ExecStatNode(self, node):
lenv = self.current_env()
self.visitchildren(node)
if len(node.args) == 1:
node.args.append(ExprNodes.GlobalsExprNode(node.pos))
if not lenv.is_module_scope:
node.args.append(
ExprNodes.LocalsExprNode(
node.pos, self.current_scope_node(), lenv))
return node
def _inject_locals(self, node, func_name): def _inject_locals(self, node, func_name):
# locals()/dir()/vars() builtins # locals()/dir()/vars() builtins
lenv = self.current_env() lenv = self.current_env()
...@@ -2274,7 +2285,6 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2274,7 +2285,6 @@ class TransformBuiltinMethods(EnvTransform):
# not the builtin # not the builtin
return node return node
pos = node.pos pos = node.pos
local_names = [ var.name for var in lenv.entries.values() if var.name ]
if func_name in ('locals', 'vars'): if func_name in ('locals', 'vars'):
if func_name == 'locals' and len(node.args) > 0: if func_name == 'locals' and len(node.args) > 0:
error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d" error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d"
...@@ -2286,11 +2296,7 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2286,11 +2296,7 @@ class TransformBuiltinMethods(EnvTransform):
% len(node.args)) % len(node.args))
if len(node.args) > 0: if len(node.args) > 0:
return node # nothing to do return node # nothing to do
items = [ ExprNodes.DictItemNode(pos, return ExprNodes.LocalsExprNode(pos, self.current_scope_node(), lenv)
key=ExprNodes.IdentifierStringNode(pos, value=var),
value=ExprNodes.NameNode(pos, name=var, allow_null=True))
for var in local_names ]
return ExprNodes.DictNode(pos, key_value_pairs=items, exclude_null_values=True)
else: # dir() else: # dir()
if len(node.args) > 1: if len(node.args) > 1:
error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d" error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
...@@ -2298,16 +2304,36 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2298,16 +2304,36 @@ class TransformBuiltinMethods(EnvTransform):
if len(node.args) > 0: if len(node.args) > 0:
# optimised in Builtin.py # optimised in Builtin.py
return node return node
if lenv.is_py_class_scope or lenv.is_module_scope:
if lenv.is_py_class_scope:
pyclass = self.current_scope_node()
locals_dict = ExprNodes.CloneNode(pyclass.dict)
else:
locals_dict = ExprNodes.GlobalsExprNode(pos)
return ExprNodes.SimpleCallNode(
pos,
function=ExprNodes.AttributeNode(
pos, obj=locals_dict, attribute="keys"),
args=[])
local_names = [ var.name for var in lenv.entries.values() if var.name ]
items = [ ExprNodes.IdentifierStringNode(pos, value=var) items = [ ExprNodes.IdentifierStringNode(pos, value=var)
for var in local_names ] for var in local_names ]
return ExprNodes.ListNode(pos, args=items) return ExprNodes.ListNode(pos, args=items)
def visit_SimpleCallNode(self, node): def _inject_eval(self, node, func_name):
if isinstance(node.function, ExprNodes.NameNode): lenv = self.current_env()
func_name = node.function.name entry = lenv.lookup_here(func_name)
if func_name in ('dir', 'locals', 'vars'): if entry or len(node.args) != 1:
return self._inject_locals(node, func_name) return node
# Inject globals and locals
node.args.append(ExprNodes.GlobalsExprNode(node.pos))
if not lenv.is_module_scope:
node.args.append(
ExprNodes.LocalsExprNode(
node.pos, self.current_scope_node(), lenv))
return node
def visit_SimpleCallNode(self, node):
# cython.foo # cython.foo
function = node.function.as_cython_attribute() function = node.function.as_cython_attribute()
if function: if function:
...@@ -2360,6 +2386,13 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2360,6 +2386,13 @@ class TransformBuiltinMethods(EnvTransform):
u"'%s' not a valid cython language construct" % function) u"'%s' not a valid cython language construct" % function)
self.visitchildren(node) self.visitchildren(node)
if isinstance(node, ExprNodes.SimpleCallNode) and node.function.is_name:
func_name = node.function.name
if func_name in ('dir', 'locals', 'vars'):
return self._inject_locals(node, func_name)
if func_name == 'eval':
return self._inject_eval(node, func_name)
return node return node
......
...@@ -1136,8 +1136,6 @@ def p_exec_statement(s): ...@@ -1136,8 +1136,6 @@ def p_exec_statement(s):
if s.sy == ',': if s.sy == ',':
s.next() s.next()
args.append(p_test(s)) args.append(p_test(s))
else:
error(pos, "'exec' currently requires a target mapping (globals/locals)")
return Nodes.ExecStatNode(pos, args = args) return Nodes.ExecStatNode(pos, args = args)
def p_del_statement(s): def p_del_statement(s):
......
...@@ -319,18 +319,36 @@ class EnvTransform(CythonTransform): ...@@ -319,18 +319,36 @@ class EnvTransform(CythonTransform):
This transformation keeps a stack of the environments. This transformation keeps a stack of the environments.
""" """
def __call__(self, root): def __call__(self, root):
self.env_stack = [root.scope] self.env_stack = [(root, root.scope)]
return super(EnvTransform, self).__call__(root) return super(EnvTransform, self).__call__(root)
def current_env(self): def current_env(self):
return self.env_stack[-1] return self.env_stack[-1][1]
def current_scope_node(self):
return self.env_stack[-1][0]
def visit_FuncDefNode(self, node): def visit_FuncDefNode(self, node):
self.env_stack.append(node.local_scope) self.env_stack.append((node, node.local_scope))
self.visitchildren(node)
self.env_stack.pop()
return node
def visit_ClassDefNode(self, node):
self.env_stack.append((node, node.scope))
self.visitchildren(node) self.visitchildren(node)
self.env_stack.pop() self.env_stack.pop()
return node return node
def visit_ScopedExprNode(self, node):
if node.expr_scope:
self.env_stack.append((node, node.expr_scope))
self.visitchildren(node)
self.env_stack.pop()
else:
self.visitchildren(node)
return node
class RecursiveNodeReplacer(VisitorTransform): class RecursiveNodeReplacer(VisitorTransform):
""" """
......
...@@ -28,6 +28,7 @@ pyregr.test_socket ...@@ -28,6 +28,7 @@ pyregr.test_socket
pyregr.test_threading pyregr.test_threading
pyregr.test_sys pyregr.test_sys
pyregr.test_pep3131 pyregr.test_pep3131
pyregr.test_multiprocessing
# CPython regression tests that don't make sense # CPython regression tests that don't make sense
pyregr.test_gdb pyregr.test_gdb
......
# mode: run
# tags: eval
GLOBAL = 123
def eval_simple(local):
"""
>>> eval_simple(321)
(123, 321)
"""
return eval('GLOBAL, local')
def eval_class_scope():
"""
>>> eval_class_scope().c
3
"""
class TestClassScope:
a = 1
b = 2
c = eval('a + b')
return TestClassScope
def eval_locals(a, b):
"""
>>> eval_locals(1, 2)
(1, 2)
"""
return eval('a, b', {}, locals())
# mode: run
# tags: exec
exec "GLOBAL = 1234"
def exec_module_scope():
"""
>>> globals()['GLOBAL']
1234
"""
def exec_func_scope():
"""
>>> exec_func_scope()
{'a': 'b', 'G': 1234}
"""
d = {}
exec "d['a'] = 'b'; d['G'] = GLOBAL"
return d
def exec_pyclass_scope():
"""
>>> obj = exec_pyclass_scope()
>>> obj.a
'b'
>>> obj.G
1234
"""
class TestExec:
exec "a = 'b'; G = GLOBAL"
return TestExec
# mode: run
# ticket: 731
# tags: locals, vars, dir
LOCALS = locals()
GLOBALS = globals()
DIR_SAME = sorted(dir()) == sorted(globals().keys())
def test_module_locals_and_dir():
"""
>>> LOCALS is GLOBALS
True
>>> DIR_SAME
True
"""
def test_class_locals_and_dir():
"""
>>> klass = test_class_locals_and_dir()
>>> 'visible' in klass.locs and 'not_visible' not in klass.locs
True
>>> klass.names
['visible']
"""
not_visible = 1234
class Foo:
visible = 4321
names = dir()
locs = locals()
return Foo
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment