[pypy-svn] r66096 - in pypy/branch/parser-compiler/pypy/interpreter/astcompiler: . test

benjamin at codespeak.net benjamin at codespeak.net
Fri Jul 3 01:01:02 CEST 2009


Author: benjamin
Date: Fri Jul  3 01:01:00 2009
New Revision: 66096

Added:
   pypy/branch/parser-compiler/pypy/interpreter/astcompiler/symtable.py   (contents, props changed)
   pypy/branch/parser-compiler/pypy/interpreter/astcompiler/test/test_symtable.py   (contents, props changed)
Log:
implement symbol table builder

Added: pypy/branch/parser-compiler/pypy/interpreter/astcompiler/symtable.py
==============================================================================
--- (empty file)
+++ pypy/branch/parser-compiler/pypy/interpreter/astcompiler/symtable.py	Fri Jul  3 01:01:00 2009
@@ -0,0 +1,408 @@
+"""
+Symbol tabling building.
+"""
+
+from pypy.interpreter.astcompiler import ast2 as ast, misc
+from pypy.interpreter.pyparser.error import SyntaxError
+
+# These are for internal use only:
+SYM_BLANK = 0
+SYM_GLOBAL = 1
+SYM_ASSIGNED = 2 # Or deleted actually.
+SYM_PARAM = 2 << 1
+SYM_USED = 2 << 2
+SYM_BOUND = (SYM_PARAM | SYM_ASSIGNED)
+
+# codegen.py actually deals with these:
+SCOPE_UNKNOWN = 0
+SCOPE_GLOBAL_IMPLICIT = 1
+SCOPE_GLOBAL_EXPLICIT = 2
+SCOPE_LOCAL = 3
+SCOPE_FREE = 4
+SCOPE_CELL = 5
+
+
+class Scope(object):
+
+    def __init__(self, node, name, optimized):
+        self.node = node
+        self.parent = None
+        self.name = name
+        self.optimized = optimized
+        self.symbols = None
+        self.roles = {}
+        self.varnames = []
+        self.children = []
+        self.has_free = False
+        self.child_has_free = False
+        self.nested = False
+
+    def lookup(self, name):
+        return self.symbols.get(name, SCOPE_UNKNOWN)
+
+    def note_symbol(self, identifier, role):
+        mangled = self.mangle(identifier)
+        new_role = role
+        if identifier in self.roles:
+            old_role = self.roles[identifier]
+            if old_role & SYM_PARAM and role & SYM_PARAM:
+                err = "duplicate argument '%s' in function definition" % \
+                    (identifier,)
+                raise SyntaxError(err, self.node.lineno, self.node.col_offset)
+            new_role |= old_role
+        self.roles[mangled] = new_role
+        if role & SYM_PARAM:
+            self.varnames.append(mangled)
+        return mangled
+
+    def note_yield(self, yield_node):
+        raise SyntaxError("yield outside function", yield_node.lineno,
+                          yield_node.col_offset)
+
+    def note_return(self, ret):
+        raise SyntaxError("return outside function", ret.lineno,
+                          ret.col_offset)
+
+    def note_bare_exec(self, exc):
+        pass
+
+    def note_import_star(self, imp):
+        pass
+
+    def mangle(self, name):
+        if self.parent:
+            return self.parent.mangle(name)
+        else:
+            return name
+
+    def add_child(self, child_scope):
+        child_scope.parent = self
+        self.children.append(child_scope)
+
+    def _finalize_name(self, name, flags, local, bound, free, globs):
+        if flags & SYM_GLOBAL:
+            if flags & SYM_PARAM:
+                raise SyntaxError("name %r is both local and global" % (name,),
+                                  self.node.lineno, self.node.col_offset)
+            self.symbols[name] = SCOPE_GLOBAL_EXPLICIT
+            globs[name] = None
+            if bound:
+                try:
+                    del bound[name]
+                except KeyError:
+                    pass
+        elif flags & SYM_BOUND:
+            self.symbols[name] = SCOPE_LOCAL
+            local[name] = None
+            try:
+                del globs[name]
+            except KeyError:
+                pass
+        elif bound and name in bound:
+            self.symbols[name] = SCOPE_FREE
+            free[name] = None
+            self.has_free = True
+        elif name in globs:
+            self.symbols[name] = SCOPE_GLOBAL_IMPLICIT
+        else:
+            if self.nested:
+                self.has_free = True
+            self.symbols[name] = SCOPE_GLOBAL_IMPLICIT
+
+    def _pass_on_bindings(self, local, bound, globs, new_bound, new_globs):
+        new_globs.update(globs)
+        if bound:
+            new_bound.update(bound)
+
+    def _finalize_cells(self, free):
+        pass
+
+    def _check_optimization(self):
+        pass
+
+    _hide_bound_from_nested_scopes = False
+
+    def finalize(self, bound, free, globs):
+        self.symbols = {}
+        local = {}
+        new_globs = {}
+        new_bound = {}
+        new_free = {}
+        if self._hide_bound_from_nested_scopes:
+            self._pass_on_bindings(local, bound, globs, new_bound, new_globs)
+        for name, flags in self.roles.iteritems():
+            self._finalize_name(name, flags, local, bound, free, globs)
+        if not self._hide_bound_from_nested_scopes:
+            self._pass_on_bindings(local, bound, globs, new_bound, new_globs)
+        child_frees = {}
+        for child in self.children:
+            child_free = new_free.copy()
+            child.finalize(new_bound.copy(), child_free, new_globs.copy())
+            child_frees.update(child_free)
+            if child.has_free or child.child_has_free:
+                self.child_has_free = True
+        new_free.update(child_frees)
+        self._finalize_cells(new_free)
+        for name in new_free:
+            try:
+                role_here = self.roles[name]
+            except KeyError:
+                if name in bound:
+                    self.symbols[name] = SCOPE_FREE
+            else:
+                if role_here & (SYM_ASSIGNED | SYM_GLOBAL) and \
+                        self._hide_bound_from_nested_scopes:
+                    self.symbols[name] = SCOPE_FREE
+        self._check_optimization()
+        free.update(new_free)
+
+
+class ModuleScope(Scope):
+
+    def __init__(self, module):
+        Scope.__init__(self, module, "top", False)
+
+
+class FunctionScope(Scope):
+
+    def __init__(self, func, name):
+        Scope.__init__(self, func, name, True)
+        self.has_variable_arg = False
+        self.has_keywords_arg = False
+        self.is_generator = False
+        self.return_with_value = False
+        self.import_star = None
+        self.bare_exec = None
+
+    def note_yield(self, yield_node):
+        if self.return_with_value:
+            raise SyntaxError("return with value in generator",
+                              yield_node.lineno, yield_node.col_offset)
+        self.is_generator = True
+
+    def note_return(self, ret):
+        if self.is_generator and ret.value:
+            raise SyntaxError("return with value in generator", ret.lineno,
+                              ret.col_offset)
+        self.return_with_value = True
+
+    def note_exec(self, exc):
+        self.has_exec = True
+        if not exc.globals:
+            self.optimized = False
+            self.bare_exec = exc
+
+    def note_import_star(self, imp):
+        self.optimized = False
+        self.import_star = imp
+
+    def note_variable_arg(self, vararg):
+        self.has_variable_arg = True
+
+    def note_keywords_arg(self, kwarg):
+        self.has_keywords_arg = True
+
+    def add_child(self, child_scope):
+        Scope.add_child(self, child_scope)
+        child_scope.nested = True
+
+    def _pass_on_bindings(self, local, bound, globs, new_bound, new_globs):
+        new_bound.update(local)
+        Scope._pass_on_bindings(self, local, bound, globs, new_bound, new_globs)
+
+    def _finalize_cells(self, free):
+        for name, role in self.symbols.iteritems():
+            if role == SCOPE_LOCAL and name in free:
+                self.symbols[name] = SCOPE_CELL
+                del free[name]
+
+    def _check_optimization(self):
+        if (self.has_free or self.child_has_free) and not self.optimized:
+            err = None
+            if self.import_star:
+                node = self.import_star
+                if self.bare_exec:
+                    err = "function %r uses import * and bare exec, " \
+                        "which are illegal because it %s"
+                else:
+                    err = "import * is not allowed in function %r because it %s"
+            elif self.bare_exec:
+                node = self.bare_exec
+                err = "unqualified exec is not allowed in function %r " \
+                    "because it %s"
+            else:
+                raise AssertionError("unkown reason for unoptimization")
+            if self.child_has_free:
+                trailer = "contains a nested function with free variables"
+            else:
+                trailer = "is a nested function"
+            raise SyntaxError(err % (self.name, trailer), node.lineno,
+                              node.col_offset)
+
+
+class ClassScope(Scope):
+
+    _hide_bound_from_nested_scopes = True
+
+    def __init__(self, clsdef):
+        Scope.__init__(self, clsdef, clsdef.name, False)
+
+    def mangle(self, name):
+        return misc.mangle(name, self.name)
+
+
+class SymtableBuilder(ast.GenericASTVisitor):
+
+    def __init__(self, space, module):
+        self.space = space
+        self.module = module
+        self.scopes = {}
+        self.scope = None
+        self.stack = []
+        top = ModuleScope(module)
+        self.globs = top.roles
+        self.push_scope(top)
+        module.walkabout(self)
+        top.finalize(None, {}, {})
+        self.pop_scope()
+        assert not self.stack
+
+    def push_scope(self, scope):
+        if self.stack:
+            self.stack[-1].add_child(scope)
+        self.stack.append(scope)
+        self.scopes[scope.node] = scope
+        # Convenience
+        self.scope = scope
+
+    def pop_scope(self):
+        self.stack.pop()
+        if self.stack:
+            self.scope = self.stack[-1]
+        else:
+            self.scope = None
+
+    def find_scope(self, scope_node):
+        return self.scopes[scope_node]
+
+    def implicit_arg(self, pos):
+        name = ".%i" % (pos,)
+        self.note_symbol(name, SYM_PARAM)
+
+    def note_symbol(self, identifier, role):
+        mangled = self.scope.note_symbol(identifier, role)
+        if role & SYM_GLOBAL:
+            if identifier in self.globs:
+                role |= self.globs[mangled]
+            self.globs[mangled] = role
+
+    def visit_FunctionDef(self, func):
+        self.note_symbol(func.name, SYM_ASSIGNED)
+        if func.args.defaults:
+            self.visit_sequence(func.args.defaults)
+        if func.decorators:
+            self.visit_sequence(func.decorators)
+        self.push_scope(FunctionScope(func, func.name))
+        func.args.walkabout(self)
+        self.visit_sequence(func.body)
+        self.pop_scope()
+
+    def visit_Return(self, ret):
+        self.scope.note_return(ret)
+        ast.GenericASTVisitor.visit_Return(self, ret)
+
+    def visit_ClassDef(self, clsdef):
+        self.note_symbol(clsdef.name, SYM_ASSIGNED)
+        if clsdef.bases:
+            self.visit_sequence(clsdef.bases)
+        self.push_scope(ClassScope(clsdef))
+        self.visit_sequence(clsdef.body)
+        self.pop_scope()
+
+    def visit_ImportFrom(self, imp):
+        for alias in imp.names:
+            if self.visit_alias(alias):
+                self.scope.note_import_star(imp)
+
+    def visit_alias(self, alias):
+        if alias.asname:
+            store_name = alias.asname
+        else:
+            store_name = alias.name
+            if store_name == "*":
+                return True
+            dot = store_name.find(".")
+            if dot != -1:
+                store_name = store_name[:dot]
+        self.note_symbol(store_name, SYM_ASSIGNED)
+        return False
+
+    def visit_Exec(self, exc):
+        self.scope.note_exec(exc)
+        ast.GenericASTVisitor.visit_Exec(self, exc)
+
+    def visit_Yield(self, yie):
+        self.scope.note_yield(yie)
+        ast.GenericASTVisitor.visit_Yield(self, yie)
+
+    def visit_Global(self, glob):
+        for name in glob.names:
+            self.note_symbol(name, SYM_GLOBAL)
+
+    def visit_Lambda(self, lamb):
+        if lamb.args.defaults:
+            self.visit_sequence(lamb.defaults)
+        self.push_scope(FunctionScope(lamb, "lambda"))
+        lamb.args.walkabout(self)
+        lamb.body.walkabout(self)
+        self.pop_scope()
+
+    def visit_GeneratorExp(self, genexp):
+        outer = genexp.generators[0]
+        outer.iter.walkabout(self)
+        self.push_scope(FunctionScope(genexp, "genexp"))
+        self.implicit_arg(0)
+        outer.target.walkabout(self)
+        if outer.ifs:
+            self.visit_sequence(outer.ifs)
+        self.visit_sequence(genexp.generators[1:])
+        genexp.elt.walkabout(self)
+        self.pop_scope()
+
+    def visit_arguments(self, arguments):
+        assert isinstance(self.scope, FunctionScope) # Annotator hint.
+        if arguments.args:
+            self._handle_params(arguments.args, True)
+        if arguments.vararg:
+            self.note_symbol(arguments.vararg, SYM_PARAM)
+            self.scope.note_variable_arg(arguments.vararg)
+        if arguments.kwarg:
+            self.note_symbol(arguments.kwarg, SYM_PARAM)
+            self.scope.note_keywords_arg(arguments.kwarg)
+        if arguments.args:
+            self._handle_nested_params(arguments.args)
+
+    def _handle_params(self, params, is_toplevel):
+        for i in range(len(params)):
+            arg = params[i]
+            if isinstance(arg, ast.Name):
+                self.note_symbol(arg.id, SYM_PARAM)
+            elif isinstance(arg, ast.Tuple):
+                if is_toplevel:
+                    self.implicit_arg(i)
+            else:
+                raise AssertionError("unkown parameter type")
+        if not is_toplevel:
+            self._handle_nested_params(params)
+
+    def _handle_nested_params(self, params):
+        for param in params:
+            if isinstance(param, ast.Tuple):
+                self._handle_params(param.elts, False)
+
+    def visit_Name(self, name):
+        if name.ctx is ast.Load:
+            role = SYM_USED
+        else:
+            role = SYM_ASSIGNED
+        self.note_symbol(name.id, role)

Added: pypy/branch/parser-compiler/pypy/interpreter/astcompiler/test/test_symtable.py
==============================================================================
--- (empty file)
+++ pypy/branch/parser-compiler/pypy/interpreter/astcompiler/test/test_symtable.py	Fri Jul  3 01:01:00 2009
@@ -0,0 +1,290 @@
+import string
+import py
+from pypy.interpreter.astcompiler import ast2 as ast, astbuilder, symtable
+from pypy.interpreter.pyparser import pyparse
+from pypy.interpreter.pyparser.error import SyntaxError
+
+
+class TestSymbolTable:
+
+    def setup_class(cls):
+        cls.parser = pyparse.PythonParser(cls.space)
+
+    def mod_scope(self, source, mode="exec"):
+        tree = self.parser.parse_source(source)
+        module = astbuilder.ast_from_node(self.space, tree)
+        builder = symtable.SymtableBuilder(self.space, module)
+        scope = builder.find_scope(module)
+        assert isinstance(scope, symtable.ModuleScope)
+        return scope
+
+    def func_scope(self, func_code):
+        mod_scope = self.mod_scope(func_code)
+        assert len(mod_scope.children) == 1
+        func_name = mod_scope.lookup("f")
+        assert func_name == symtable.SCOPE_LOCAL
+        func_scope = mod_scope.children[0]
+        assert isinstance(func_scope, symtable.FunctionScope)
+        return func_scope
+
+    def class_scope(self, class_code):
+        mod_scope = self.mod_scope(class_code)
+        assert len(mod_scope.children) == 1
+        class_name = mod_scope.lookup("x")
+        assert class_name == symtable.SCOPE_LOCAL
+        class_scope = mod_scope.children[0]
+        assert isinstance(class_scope, symtable.ClassScope)
+        return class_scope
+
+    def gen_scope(self, gen_code):
+        mod_scope = self.mod_scope(gen_code)
+        assert len(mod_scope.children) == 1
+        gen_scope = mod_scope.children[0]
+        assert isinstance(gen_scope, symtable.FunctionScope)
+        assert not gen_scope.children
+        assert gen_scope.name == "genexp"
+        return mod_scope, gen_scope
+
+    def check_unknown(self, scp, *names):
+        for name in names:
+            assert scp.lookup(name) == symtable.SCOPE_UNKNOWN
+
+    def test_toplevel(self):
+        scp = self.mod_scope("x = 4")
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        assert not scp.optimized
+        scp = self.mod_scope("x = 4", "single")
+        assert not scp.optimized
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.mod_scope("x*4*6", "eval")
+        assert not scp.optimized
+        assert scp.lookup("x") == symtable.SCOPE_GLOBAL_IMPLICIT
+
+    def test_duplicate_argument(self):
+        input = "def f(x, x): pass"
+        exc = py.test.raises(SyntaxError, self.mod_scope, input).value
+        assert exc.msg == "duplicate argument 'x' in function definition"
+
+    def test_function_defaults(self):
+        scp = self.mod_scope("y = 4\ndef f(x=y): return x")
+        self.check_unknown(scp, "x")
+        assert scp.lookup("y") == symtable.SCOPE_LOCAL
+        scp = scp.children[0]
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        self.check_unknown(scp, "y")
+
+    def test_genexp(self):
+        scp, gscp = self.gen_scope("(y[1] for y in z)")
+        assert scp.lookup("z") == symtable.SCOPE_GLOBAL_IMPLICIT
+        self.check_unknown(scp, "y", "x")
+        self.check_unknown(gscp, "z")
+        assert gscp.lookup("y") == symtable.SCOPE_LOCAL
+        scp, gscp = self.gen_scope("(x for x in z if x)")
+        self.check_unknown(scp, "x")
+        assert gscp.lookup("x") == symtable.SCOPE_LOCAL
+        scp, gscp = self.gen_scope("(x for y in g for f in n if f[h])")
+        self.check_unknown(scp, "f")
+        assert gscp.lookup("f") == symtable.SCOPE_LOCAL
+
+    def test_arguments(self):
+        scp = self.func_scope("def f(): pass")
+        assert not scp.children
+        self.check_unknown(scp, "x", "y")
+        assert not scp.symbols
+        assert not scp.roles
+        scp = self.func_scope("def f(x): pass")
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.func_scope("def f(*x): pass")
+        assert scp.has_variable_arg
+        assert not scp.has_keywords_arg
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.func_scope("def f(**x): pass")
+        assert scp.has_keywords_arg
+        assert not scp.has_variable_arg
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.func_scope("def f((x, y), a): pass")
+        for name in ("x", "y", "a"):
+            assert scp.lookup(name) == symtable.SCOPE_LOCAL
+        scp = self.func_scope("def f(((a, b), c)): pass")
+        for name in ("a", "b", "c"):
+            assert scp.lookup(name) == symtable.SCOPE_LOCAL
+
+    def test_function(self):
+        scp = self.func_scope("def f(): x = 4")
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.func_scope("def f(): x")
+        assert scp.lookup("x") == symtable.SCOPE_GLOBAL_IMPLICIT
+
+    def test_nested_scopes(self):
+        def nested_scope(*bodies):
+            names = enumerate("f" + string.ascii_letters)
+            lines = []
+            for body, (level, name) in zip(bodies, names):
+                lines.append(" " * level + "def %s():\n" % (name,))
+                if body:
+                    if isinstance(body, str):
+                        body = [body]
+                    lines.extend(" " * (level + 1) + line + "\n"
+                                 for line in body)
+            return self.func_scope("".join(lines))
+        scp = nested_scope("x = 1", "return x")
+        assert not scp.has_free
+        assert scp.child_has_free
+        assert scp.lookup("x") == symtable.SCOPE_CELL
+        child = scp.children[0]
+        assert child.has_free
+        assert child.lookup("x") == symtable.SCOPE_FREE
+        scp = nested_scope("x = 1", None, "return x")
+        assert not scp.has_free
+        assert scp.child_has_free
+        assert scp.lookup("x") == symtable.SCOPE_CELL
+        child = scp.children[0]
+        assert not child.has_free
+        assert child.child_has_free
+        assert child.lookup("x") == symtable.SCOPE_FREE
+        child = child.children[0]
+        assert child.has_free
+        assert not child.child_has_free
+        assert child.lookup("x") == symtable.SCOPE_FREE
+        scp = nested_scope("x = 1", "x = 3", "return x")
+        assert scp.child_has_free
+        assert not scp.has_free
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        child = scp.children[0]
+        assert child.child_has_free
+        assert not child.has_free
+        assert child.lookup("x") == symtable.SCOPE_CELL
+        child = child.children[0]
+        assert child.has_free
+        assert child.lookup("x") == symtable.SCOPE_FREE
+
+    def test_class(self):
+        scp = self.mod_scope("class x(A, B): pass")
+        cscp = scp.children[0]
+        for name in ("A", "B"):
+            assert scp.lookup(name) == symtable.SCOPE_GLOBAL_IMPLICIT
+            self.check_unknown(cscp, name)
+        scp = self.func_scope("""def f(x):
+    class X:
+         def n():
+              return x
+         a = x
+    return X()""")
+        self.check_unknown(scp, "a")
+        assert scp.lookup("x") == symtable.SCOPE_CELL
+        assert scp.lookup("X") == symtable.SCOPE_LOCAL
+        cscp = scp.children[0]
+        assert cscp.lookup("a") == symtable.SCOPE_LOCAL
+        assert cscp.lookup("x") == symtable.SCOPE_FREE
+        fscp = cscp.children[0]
+        assert fscp.lookup("x") == symtable.SCOPE_FREE
+        self.check_unknown(fscp, "a")
+
+    def test_lambda(self):
+        scp = self.mod_scope("lambda x: y")
+        self.check_unknown(scp, "x", "y")
+        assert len(scp.children) == 1
+        lscp = scp.children[0]
+        assert isinstance(lscp, symtable.FunctionScope)
+        assert lscp.name == "lambda"
+        assert lscp.lookup("x") == symtable.SCOPE_LOCAL
+        assert lscp.lookup("y") == symtable.SCOPE_GLOBAL_IMPLICIT
+
+    def test_import(self):
+        scp = self.mod_scope("import x")
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        scp = self.mod_scope("import x as y")
+        assert scp.lookup("y") == symtable.SCOPE_LOCAL
+        self.check_unknown(scp, "x")
+        scp = self.mod_scope("import x.y")
+        assert scp.lookup("x") == symtable.SCOPE_LOCAL
+        self.check_unknown(scp, "y")
+
+    def test_from_import(self):
+        scp = self.mod_scope("from x import y")
+        self.check_unknown("x")
+        assert scp.lookup("y") == symtable.SCOPE_LOCAL
+        scp = self.mod_scope("from a import b as y")
+        assert scp.lookup("y") == symtable.SCOPE_LOCAL
+        self.check_unknown(scp, "a", "b")
+        scp = self.mod_scope("from x import *")
+        self.check_unknown("x")
+        scp = self.func_scope("def f(): from x import *")
+        self.check_unknown(scp, "x")
+        assert not scp.optimized
+        assert scp.import_star
+
+    def test_global(self):
+        scp = self.func_scope("def f():\n   global x\n   x = 4")
+        assert scp.lookup("x") == symtable.SCOPE_GLOBAL_EXPLICIT
+        input = "def f(x):\n   global x"
+        scp = self.func_scope("""def f():
+    y = 3
+    def x():
+        global y
+        y = 4
+    def z():
+        return y""")
+        assert scp.lookup("y") == symtable.SCOPE_CELL
+        xscp, zscp = scp.children
+        assert xscp.lookup("y") == symtable.SCOPE_GLOBAL_EXPLICIT
+        assert zscp.lookup("y") == symtable.SCOPE_FREE
+        exc = py.test.raises(SyntaxError, self.func_scope, input).value
+        assert exc.msg == "name 'x' is both local and global"
+
+    def test_optimization(self):
+        assert not self.mod_scope("").optimized
+        assert not self.class_scope("class x: pass").optimized
+        assert self.func_scope("def f(): pass").optimized
+
+    def test_unoptimization_with_nested_scopes(self):
+        table = (
+            ("from x import *; exec 'hi'", "function 'f' uses import * " \
+                 "and bare exec, which are illegal because it"),
+            ("from x import *", "import * is not allowed in function 'f' " \
+                 "because it"),
+            ("exec 'hi'", "unqualified exec is not allowed in function 'f' " \
+                 "because it")
+         )
+        for line, error in table:
+            input = """def n():
+    x = 4
+    def f():
+         %s
+         return x""" % (line,)
+            exc = py.test.raises(SyntaxError, self.mod_scope, input).value
+            assert exc.msg == error + " is a nested function"
+            input = """def f():
+     %s
+     x = 4
+     def n():
+         return x""" % (line,)
+            exc = py.test.raises(SyntaxError, self.mod_scope, input).value
+            assert exc.msg == error + " contains a nested function with free variables"
+
+    def test_exec(self):
+        scp = self.func_scope("def f(): exec 'hi'")
+        assert not scp.optimized
+        assert isinstance(scp.bare_exec, ast.Exec)
+        assert scp.has_exec
+        for line in ("exec 'hi' in g", "exec 'hi' in g, h"):
+            scp = self.func_scope("def f(): " + line)
+            assert scp.optimized
+            assert scp.bare_exec is None
+            assert scp.has_exec
+
+    def test_yield(self):
+        scp = self.func_scope("def f(): yield x")
+        assert scp.is_generator
+        for input in ("yield x", "class y: yield x"):
+            exc = py.test.raises(SyntaxError, self.mod_scope, "yield x").value
+            assert exc.msg == "yield outside function"
+        for input in ("yield\n    return x", "return x\n    yield"):
+            input = "def f():\n    " + input
+            exc = py.test.raises(SyntaxError, self.func_scope, input).value
+            assert exc.msg == "return with value in generator"
+
+    def test_return(self):
+        for input in ("class x: return", "return"):
+            exc = py.test.raises(SyntaxError, self.func_scope, input).value
+            assert exc.msg == "return outside function"



More information about the Pypy-commit mailing list