[pypy-commit] pypy py3k: (antocuni, romain): implement keyword-only arguments in the astcompiler and in the interpreter

antocuni noreply at buildbot.pypy.org
Thu Jan 19 23:20:22 CET 2012


Author: Antonio Cuni <anto.cuni at gmail.com>
Branch: py3k
Changeset: r51503:243e484588fb
Date: 2012-01-19 23:20 +0100
http://bitbucket.org/pypy/pypy/changeset/243e484588fb/

Log:	(antocuni, romain): implement keyword-only arguments in the
	astcompiler and in the interpreter

diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -10,19 +10,27 @@
 class Signature(object):
     _immutable_ = True
     _immutable_fields_ = ["argnames[*]"]
-    __slots__ = ("argnames", "varargname", "kwargname")
+    __slots__ = ("argnames", "kwonlyargnames", "varargname", "kwargname")
 
-    def __init__(self, argnames, varargname=None, kwargname=None):
+    def __init__(self, argnames, varargname=None, kwargname=None, kwonlyargnames=None):
         self.argnames = argnames
         self.varargname = varargname
         self.kwargname = kwargname
+        if kwonlyargnames is None:
+            kwonlyargnames = []
+        self.kwonlyargnames = kwonlyargnames
 
     @jit.elidable
     def find_argname(self, name):
         try:
             return self.argnames.index(name)
         except ValueError:
-            return -1
+            pass
+        try:
+            return len(self.argnames) + self.kwonlyargnames.index(name)
+        except ValueError:
+            pass
+        return -1
 
     def num_argnames(self):
         return len(self.argnames)
@@ -43,20 +51,22 @@
         argnames = self.argnames
         if self.varargname is not None:
             argnames = argnames + [self.varargname]
+        argnames += self.kwonlyargnames
         if self.kwargname is not None:
             argnames = argnames + [self.kwargname]
         return argnames
 
     def __repr__(self):
-        return "Signature(%r, %r, %r)" % (
-                self.argnames, self.varargname, self.kwargname)
+        return "Signature(%r, %r, %r, %r)" % (
+                self.argnames, self.varargname, self.kwargname, self.kwonlyargnames)
 
     def __eq__(self, other):
         if not isinstance(other, Signature):
             return NotImplemented
         return (self.argnames == other.argnames and
                 self.varargname == other.varargname and
-                self.kwargname == other.kwargname)
+                self.kwargname == other.kwargname and
+                self.kwonlyargnames == other.kwonlyargnames)
 
     def __ne__(self, other):
         if not isinstance(other, Signature):
diff --git a/pypy/interpreter/astcompiler/assemble.py b/pypy/interpreter/astcompiler/assemble.py
--- a/pypy/interpreter/astcompiler/assemble.py
+++ b/pypy/interpreter/astcompiler/assemble.py
@@ -147,6 +147,7 @@
         self.free_vars = _list_to_dict(scope.free_vars, len(self.cell_vars))
         self.w_consts = space.newdict()
         self.argcount = 0
+        self.kwonlyargcount = 0
         self.lineno_set = False
         self.lineno = 0
         self.add_none_to_final_return = True
@@ -459,6 +460,7 @@
         bytecode = ''.join([block.get_code() for block in blocks])
         return pycode.PyCode(self.space,
                              self.argcount,
+                             self.kwonlyargcount,
                              len(self.var_names),
                              stack_depth,
                              flags,
diff --git a/pypy/interpreter/astcompiler/codegen.py b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -1173,6 +1173,9 @@
         if args.args:
             self._handle_nested_args(args.args)
             self.argcount = len(args.args)
+        if args.kwonlyargs:
+            self._handle_nested_args(args.kwonlyargs)
+            self.kwonlyargcount = len(args.kwonlyargs)
         if func.body:
             for i in range(start, len(func.body)):
                 func.body[i].walkabout(self)
diff --git a/pypy/interpreter/astcompiler/symtable.py b/pypy/interpreter/astcompiler/symtable.py
--- a/pypy/interpreter/astcompiler/symtable.py
+++ b/pypy/interpreter/astcompiler/symtable.py
@@ -495,6 +495,8 @@
         assert isinstance(scope, FunctionScope) # Annotator hint.
         if arguments.args:
             self._handle_params(arguments.args, True)
+        if arguments.kwonlyargs:
+            self._handle_params(arguments.kwonlyargs, True)
         if arguments.vararg:
             self.note_symbol(arguments.vararg, SYM_PARAM)
             scope.note_variable_arg(arguments.vararg)
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -5,6 +5,7 @@
 from pypy.interpreter.pyparser.test import expressions
 from pypy.interpreter.pycode import PyCode
 from pypy.interpreter.pyparser.error import SyntaxError, IndentationError
+from pypy.interpreter.error import OperationError
 from pypy.tool import stdlib_opcode as ops
 
 def compile_with_astcompiler(expr, mode, space):
@@ -41,9 +42,11 @@
         source = str(py.code.Source(source))
         space = self.space
         code = compile_with_astcompiler(source, 'exec', space)
-        # 2.7 bytecode is too different, the standard `dis` module crashes
+        # 3.2 bytecode is too different, the standard `dis` module crashes
         # on older cpython versions
-        if sys.version_info >= (2, 7):
+        if sys.version_info >= (3, 2):
+            # this will only (maybe) work in the far future, when we run pypy
+            # on top of Python 3. For now, it's just disabled
             print
             code.dump()
         w_dict = space.newdict()
@@ -242,7 +245,9 @@
                 return a, b
         """)
         decl = str(decl) + '\n'
-        yield self.st, decl + "x=f(1, b=2)", "x", (1, 2)
+        self.st(decl + "x=f(1, b=2)", "x", (1, 2))
+        operr = py.test.raises(OperationError, 'self.st(decl + "x=f(1, 2)", "x", (1, 2))')
+        assert operr.value.w_type is self.space.w_TypeError
 
     def test_listmakers(self):
         yield (self.st,
diff --git a/pypy/interpreter/astcompiler/test/test_symtable.py b/pypy/interpreter/astcompiler/test/test_symtable.py
--- a/pypy/interpreter/astcompiler/test/test_symtable.py
+++ b/pypy/interpreter/astcompiler/test/test_symtable.py
@@ -131,6 +131,11 @@
         for name in ("a", "b", "c"):
             assert scp.lookup(name) == symtable.SCOPE_LOCAL
 
+    def test_arguments_kwonly(self):
+        scp = self.func_scope("def f(a, *, b): pass")
+        for name in ("a", "b"):
+            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
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -16,7 +16,7 @@
 from pypy.rlib.rarithmetic import intmask
 from pypy.rlib.debug import make_sure_not_resized
 from pypy.rlib import jit
-from pypy.rlib.objectmodel import compute_hash
+from pypy.rlib.objectmodel import compute_hash, we_are_translated
 from pypy.tool.stdlib_opcode import opcodedesc, HAVE_ARGUMENT
 
 # helper
@@ -42,8 +42,16 @@
 def cpython_code_signature(code):
     "([list-of-arg-names], vararg-name-or-None, kwarg-name-or-None)."
     argcount = code.co_argcount
+    if we_are_translated():
+        kwonlyargcount = code.co_kwonlyargcount
+    else:
+        # for compatibility with CPython 2.7 code objects
+        kwonlyargcount = getattr(code, 'co_kwonlyargcount', 0)
     assert argcount >= 0     # annotator hint
+    assert kwonlyargcount >= 0
     argnames = list(code.co_varnames[:argcount])
+    n = argcount + kwonlyargcount
+    kwonlyargs = list(code.co_varnames[argcount:n])
     if code.co_flags & CO_VARARGS:
         varargname = code.co_varnames[argcount]
         argcount += 1
@@ -54,7 +62,7 @@
         argcount += 1
     else:
         kwargname = None
-    return Signature(argnames, varargname, kwargname)
+    return Signature(argnames, varargname, kwargname, kwonlyargs)
 
 class PyCode(eval.Code):
     "CPython-style code objects."
@@ -62,7 +70,7 @@
     _immutable_fields_ = ["co_consts_w[*]", "co_names_w[*]", "co_varnames[*]",
                           "co_freevars[*]", "co_cellvars[*]"]
 
-    def __init__(self, space,  argcount, nlocals, stacksize, flags,
+    def __init__(self, space,  argcount, kwonlyargcount, nlocals, stacksize, flags,
                      code, consts, names, varnames, filename,
                      name, firstlineno, lnotab, freevars, cellvars,
                      hidden_applevel=False, magic = default_magic):
@@ -72,6 +80,7 @@
         eval.Code.__init__(self, name)
         assert nlocals >= 0
         self.co_argcount = argcount
+        self.co_kwonlyargcount = kwonlyargcount
         self.co_nlocals = nlocals
         self.co_stacksize = stacksize
         self.co_flags = flags
@@ -175,6 +184,7 @@
         # stick the underlying CPython magic value, if the code object
         # comes from there
         return cls(space, code.co_argcount,
+                      getattr(code, 'co_kwonlyargcount', 0),
                       code.co_nlocals,
                       code.co_stacksize,
                       code.co_flags,
@@ -250,6 +260,7 @@
                 consts[num] = self.space.unwrap(w)
             num += 1
         return new.code( self.co_argcount,
+                         self.co_kwonlyargcount,
                          self.co_nlocals,
                          self.co_stacksize,
                          self.co_flags,
@@ -297,6 +308,7 @@
             return space.w_False
         areEqual = (self.co_name == other.co_name and
                     self.co_argcount == other.co_argcount and
+                    self.co_kwonlyargcount == other.co_kwonlyargcount and
                     self.co_nlocals == other.co_nlocals and
                     self.co_flags == other.co_flags and
                     self.co_firstlineno == other.co_firstlineno and
@@ -323,6 +335,7 @@
         space = self.space
         result =  compute_hash(self.co_name)
         result ^= self.co_argcount
+        result ^= self.co_kwonlyargcount
         result ^= self.co_nlocals
         result ^= self.co_flags
         result ^= self.co_firstlineno
@@ -337,12 +350,12 @@
             w_result = space.xor(w_result, space.hash(w_const))
         return w_result
 
-    @unwrap_spec(argcount=int, nlocals=int, stacksize=int, flags=int,
+    @unwrap_spec(argcount=int, kwonlyargcount=int, nlocals=int, stacksize=int, flags=int,
                  codestring=str,
                  filename=str, name=str, firstlineno=int,
                  lnotab=str, magic=int)
     def descr_code__new__(space, w_subtype,
-                          argcount, nlocals, stacksize, flags,
+                          argcount, kwonlyargcount, nlocals, stacksize, flags,
                           codestring, w_constants, w_names,
                           w_varnames, filename, name, firstlineno,
                           lnotab, w_freevars=NoneNotWrapped,
@@ -351,6 +364,9 @@
         if argcount < 0:
             raise OperationError(space.w_ValueError,
                                  space.wrap("code: argcount must not be negative"))
+        if kwonlyargcount < 0:
+            raise OperationError(space.w_ValueError,
+                                 space.wrap("code: kwonlyargcount must not be negative"))
         if nlocals < 0:
             raise OperationError(space.w_ValueError,
                                  space.wrap("code: nlocals must not be negative"))
@@ -369,7 +385,7 @@
         else:
             cellvars = []
         code = space.allocate_instance(PyCode, w_subtype)
-        PyCode.__init__(code, space, argcount, nlocals, stacksize, flags, codestring, consts_w[:], names,
+        PyCode.__init__(code, space, argcount, kwonlyargcount, nlocals, stacksize, flags, codestring, consts_w[:], names,
                       varnames, filename, name, firstlineno, lnotab, freevars, cellvars, magic=magic)
         return space.wrap(code)
 
@@ -381,6 +397,7 @@
         w        = space.wrap
         tup      = [
             w(self.co_argcount),
+            w(self.co_kwonlyargcount),
             w(self.co_nlocals),
             w(self.co_stacksize),
             w(self.co_flags),
diff --git a/pypy/interpreter/test/test_argument.py b/pypy/interpreter/test/test_argument.py
--- a/pypy/interpreter/test/test_argument.py
+++ b/pypy/interpreter/test/test_argument.py
@@ -26,12 +26,12 @@
         assert sig.has_kwarg()
         assert sig.scope_length() == 4
         assert sig.getallvarnames() == ["a", "b", "c", "c"]
-        sig = Signature(["a", "b", "c"], "d", "c")
+        sig = Signature(["a", "b", "c"], "d", "c", ["kwonly"])
         assert sig.num_argnames() == 3
         assert sig.has_vararg()
         assert sig.has_kwarg()
         assert sig.scope_length() == 5
-        assert sig.getallvarnames() == ["a", "b", "c", "d", "c"]
+        assert sig.getallvarnames() == ["a", "b", "c", "d", "kwonly", "c"]
 
     def test_eq(self):
         sig1 = Signature(["a", "b", "c"], "d", "c")
@@ -40,11 +40,12 @@
 
 
     def test_find_argname(self):
-        sig = Signature(["a", "b", "c"], None, None)
+        sig = Signature(["a", "b", "c"], None, None, ["kwonly"])
         assert sig.find_argname("a") == 0
         assert sig.find_argname("b") == 1
         assert sig.find_argname("c") == 2
         assert sig.find_argname("d") == -1
+        assert sig.find_argname("kwonly") == 3
 
     def test_tuply(self):
         sig = Signature(["a", "b", "c"], "d", "e")
diff --git a/pypy/objspace/std/marshal_impl.py b/pypy/objspace/std/marshal_impl.py
--- a/pypy/objspace/std/marshal_impl.py
+++ b/pypy/objspace/std/marshal_impl.py
@@ -313,6 +313,7 @@
     # see pypy.interpreter.pycode for the layout
     x = space.interp_w(PyCode, w_pycode)
     m.put_int(x.co_argcount)
+    m.put_int(x.co_kwonlyargcount)
     m.put_int(x.co_nlocals)
     m.put_int(x.co_stacksize)
     m.put_int(x.co_flags)
@@ -355,6 +356,7 @@
 
 def unmarshal_pycode(space, u, tc):
     argcount    = u.get_int()
+    kwonlyargcount = u.get_int()
     nlocals     = u.get_int()
     stacksize   = u.get_int()
     flags       = u.get_int()
@@ -370,7 +372,7 @@
     name        = unmarshal_str(u)
     firstlineno = u.get_int()
     lnotab      = unmarshal_str(u)
-    code = PyCode(space, argcount, nlocals, stacksize, flags,
+    code = PyCode(space, argcount, kwonlyargcount, nlocals, stacksize, flags,
                   code, consts_w[:], names, varnames, filename,
                   name, firstlineno, lnotab, freevars, cellvars)
     return space.wrap(code)


More information about the pypy-commit mailing list