[pypy-commit] pypy py3.5: Tweak/fix the logic for multiple '**' arguments. Also makes the error messages equal

arigo pypy.commits at gmail.com
Fri Oct 14 09:20:35 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.5
Changeset: r87786:a72722e30393
Date: 2016-10-14 15:19 +0200
http://bitbucket.org/pypy/pypy/changeset/a72722e30393/

Log:	Tweak/fix the logic for multiple '**' arguments. Also makes the
	error messages equal to the ones we get from argument.py. Now all
	messages are missing the 'funcname()' prefix that CPython gives
	them, but it's at least consistent---and harder to fix.

diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -403,7 +403,9 @@
             key = space.identifier_w(w_key)
         except OperationError as e:
             if e.match(space, space.w_TypeError):
-                raise oefmt(space.w_TypeError, "keywords must be strings")
+                raise oefmt(space.w_TypeError,
+                            "keywords must be strings, not '%T'",
+                            w_key)
             if e.match(space, space.w_UnicodeEncodeError):
                 # Allow this to pass through
                 key = None
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -46,14 +46,6 @@
 
     return func_with_new_name(opimpl, "opcode_impl_for_%s" % operationname)
 
-def get_func_desc(space, func):
-    if isinstance(func,function.Function):
-        return "()"
-    elif isinstance(func, function.Method):
-        return "()"
-    else:
-        return " object";
-
 
 opcodedesc = bytecode_spec.opcodedesc
 HAVE_ARGUMENT = bytecode_spec.HAVE_ARGUMENT
@@ -1389,54 +1381,50 @@
         w_sum = self.list_unpack_helper(itemcount)
         self.pushvalue(w_sum)
 
+    def BUILD_MAP_UNPACK(self, itemcount, next_instr):
+        self._build_map_unpack(itemcount, with_call=False)
+
+    def BUILD_MAP_UNPACK_WITH_CALL(self, oparg, next_instr):
+        num_maps = oparg & 0xff
+        self._build_map_unpack(num_maps, with_call=True)
+
     @jit.unroll_safe
-    def BUILD_MAP_UNPACK_WITH_CALL(self, itemcount, next_instr):
+    def _build_map_unpack(self, itemcount, with_call):
         space = self.space
-        num_maps = itemcount & 0xff
-        function_location = (itemcount>>8) & 0xff
         w_dict = space.newdict()
-        for i in range(num_maps, 0, -1):
-            w_item = self.peekvalue(i-1)
+        expected_length = 0
+        for i in range(itemcount-1, -1, -1):
+            w_item = self.peekvalue(i)
             if not space.ismapping_w(w_item):
                 raise oefmt(space.w_TypeError,
-                        "'%T' object is not a mapping", w_item)
-            iterator = w_item.iterkeys()
-            while True:
-                w_key = iterator.next_key()
-                if w_key is None:
-                    break
-                if not space.isinstance_w(w_key, space.w_unicode):
-                    err_fun = self.peekvalue(num_maps + function_location-1)
-                    raise oefmt(space.w_TypeError,
-                        "%N%s keywords must be strings", err_fun,
-                                                         get_func_desc(space, err_fun))
-                if space.is_true(space.contains(w_dict,w_key)):
-                    err_fun = self.peekvalue(num_maps + function_location-1)
-                    err_arg = w_key
-                    raise oefmt(space.w_TypeError,
-                        "%N%s got multiple values for keyword argument '%s'",
-                          err_fun, get_func_desc(space, err_fun), space.str_w(err_arg))
+                            "'%T' object is not a mapping", w_item)
+            if with_call:
+                expected_length += space.len_w(w_item)
             space.call_method(w_dict, 'update', w_item)
-        while num_maps != 0:
-            self.popvalue()
-            num_maps -= 1
-        self.pushvalue(w_dict)
-
-    @jit.unroll_safe
-    def BUILD_MAP_UNPACK(self, itemcount, next_instr):
-        space = self.space
-        w_dict = space.newdict()
-        for i in range(itemcount, 0, -1):
-            w_item = self.peekvalue(i-1)
-            if not space.ismapping_w(w_item):
-                raise oefmt(self.space.w_TypeError,
-                        "'%T' object is not a mapping", w_item)
-            space.call_method(w_dict, 'update', w_item)
-        while itemcount != 0:
+        if with_call and space.len_w(w_dict) < expected_length:
+            self._build_map_unpack_error(itemcount)
+        while itemcount > 0:
             self.popvalue()
             itemcount -= 1
         self.pushvalue(w_dict)
 
+    @jit.dont_look_inside
+    def _build_map_unpack_error(self, itemcount):
+        space = self.space
+        w_set = space.newset()
+        for i in range(itemcount-1, -1, -1):
+            w_item = self.peekvalue(i)
+            w_inter = space.call_method(w_set, 'intersection', w_item)
+            if space.is_true(w_inter):
+                w_key = space.next(space.iter(w_inter))
+                if not space.isinstance_w(w_key, space.w_unicode):
+                    raise oefmt(space.w_TypeError,
+                            "keywords must be strings, not '%T'", w_key)
+                raise oefmt(space.w_TypeError,
+                    "got multiple values for keyword argument %R",
+                    w_key)
+            space.call_method(w_set, 'update', w_item)
+
     def GET_YIELD_FROM_ITER(self, oparg, next_instr):
         from pypy.interpreter.astcompiler import consts
         from pypy.interpreter.generator import GeneratorIterator, Coroutine
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
@@ -878,3 +878,11 @@
             return (x, y)
         assert f(**{'x': 5}, y=6) == (5, 6)
         """
+
+    def test_error_message_kwargs(self):
+        def f(x, y):
+            pass
+        e = raises(TypeError, "f(y=2, **{3: 5}, x=6)")
+        assert "keywords must be strings" in str(e.value)
+        e = raises(TypeError, "f(y=2, **{'x': 5}, x=6)")
+        assert "got multiple values for keyword argument 'x'" in str(e.value)


More information about the pypy-commit mailing list