Author: Carl Friedrich Bolz-Tereick <[email protected]>
Branch: py3.6
Changeset: r97506:535b4f6bdd4d
Date: 2019-09-17 11:27 +0200
http://bitbucket.org/pypy/pypy/changeset/535b4f6bdd4d/

Log:    somewhat messy code to be able to give the function name in
        TypeErrors produced by argument parsing more consistently.

        There is still a case where we can't do that, CPython cheats there
        peeking around in the stack in the BUILD_MAP_UNPACK_WITH_CALL
        bytecode, but that's a huge mess.

diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -2,13 +2,20 @@
 Arguments objects.
 """
 from rpython.rlib.debug import make_sure_not_resized
-from rpython.rlib.objectmodel import not_rpython
+from rpython.rlib.objectmodel import not_rpython, specialize
 from rpython.rlib import jit
 from rpython.rlib.objectmodel import enforceargs
 from rpython.rlib.rstring import StringBuilder
 
 from pypy.interpreter.error import OperationError, oefmt
 
[email protected](1)
+def raise_type_error(space, fnname_parens, msg, *args):
+    if fnname_parens is None:
+        raise oefmt(space.w_TypeError, msg, *args)
+    msg = "%s " + msg
+    raise oefmt(space.w_TypeError, msg, fnname_parens, *args)
+
 
 class Arguments(object):
     """
@@ -21,11 +28,9 @@
     semantics are complex, but calls occur everywhere.
     """
 
-    ###  Construction  ###
-    #@enforceargs(keywords=[unicode])
     def __init__(self, space, args_w, keywords=None, keywords_w=None,
                  w_stararg=None, w_starstararg=None, keyword_names_w=None,
-                 methodcall=False):
+                 methodcall=False, fnname_parens=None):
         self.space = space
         assert isinstance(args_w, list)
         self.arguments_w = args_w
@@ -41,7 +46,7 @@
             make_sure_not_resized(self.keywords_w)
 
         make_sure_not_resized(self.arguments_w)
-        self._combine_wrapped(w_stararg, w_starstararg)
+        self._combine_wrapped(w_stararg, w_starstararg, fnname_parens)
         # a flag that specifies whether the JIT can unroll loops that operate
         # on the keywords
         self._jit_few_keywords = self.keywords is None or 
jit.isconstant(len(self.keywords))
@@ -79,14 +84,14 @@
         "Return a new Arguments with a new argument inserted first."
         return self.replace_arguments([w_firstarg] + self.arguments_w)
 
-    def _combine_wrapped(self, w_stararg, w_starstararg):
+    def _combine_wrapped(self, w_stararg, w_starstararg, fnname_parens=None):
         "unpack the *arg and **kwd into arguments_w and keywords_w"
         if w_stararg is not None:
-            self._combine_starargs_wrapped(w_stararg)
+            self._combine_starargs_wrapped(w_stararg, fnname_parens)
         if w_starstararg is not None:
-            self._combine_starstarargs_wrapped(w_starstararg)
+            self._combine_starstarargs_wrapped(w_starstararg, fnname_parens)
 
-    def _combine_starargs_wrapped(self, w_stararg):
+    def _combine_starargs_wrapped(self, w_stararg, fnname_parens=None):
         # unpack the * arguments
         space = self.space
         try:
@@ -94,13 +99,13 @@
         except OperationError as e:
             if (e.match(space, space.w_TypeError) and
                     not space.is_iterable(w_stararg)):
-                raise oefmt(space.w_TypeError,
+                raise_type_error(space, fnname_parens,
                             "argument after * must be an iterable, not %T",
                             w_stararg)
             raise
         self.arguments_w = self.arguments_w + args_w
 
-    def _combine_starstarargs_wrapped(self, w_starstararg):
+    def _combine_starstarargs_wrapped(self, w_starstararg, fnname_parens=None):
         # unpack the ** arguments
         space = self.space
         keywords, values_w = space.view_as_kwargs(w_starstararg)
@@ -110,7 +115,8 @@
                 self.keywords_w = values_w
             else:
                 _check_not_duplicate_kwargs(
-                    self.space, self.keywords, keywords, values_w)
+                    self.space, self.keywords, keywords, values_w,
+                    fnname_parens)
                 self.keywords = self.keywords + keywords
                 self.keywords_w = self.keywords_w + values_w
             return
@@ -123,7 +129,7 @@
                 w_keys = space.call_method(w_starstararg, "keys")
             except OperationError as e:
                 if e.match(space, space.w_AttributeError):
-                    raise oefmt(space.w_TypeError,
+                    raise_type_error(space, fnname_parens,
                                 "argument after ** must be a mapping, not %T",
                                 w_starstararg)
                 raise
@@ -132,7 +138,7 @@
         keywords = [None] * len(keys_w)
         _do_combine_starstarargs_wrapped(
             space, keys_w, w_starstararg, keywords, keywords_w, self.keywords,
-            is_dict)
+            is_dict, fnname_parens)
         self.keyword_names_w = keys_w
         if self.keywords is None:
             self.keywords = keywords
@@ -349,8 +355,8 @@
     def parse_obj(self, w_firstarg,
                   fnname, signature, defaults_w=None, w_kw_defs=None,
                   blindargs=0):
-        """Parse args and kwargs to initialize a frame
-        according to the signature of code object.
+        """Parse args and kwargs into a list according to the signature of a
+        code object.
         """
         try:
             return self._parse(w_firstarg, signature, defaults_w, w_kw_defs,
@@ -387,28 +393,28 @@
 # look at. They should not get a self arguments, which makes the amount of
 # arguments annoying :-(
 
[email protected]_inside_iff(lambda space, existingkeywords, keywords, keywords_w:
[email protected]_inside_iff(lambda space, existingkeywords, keywords, keywords_w, 
fnname_parens:
         jit.isconstant(len(keywords) and
         jit.isconstant(existingkeywords)))
-def _check_not_duplicate_kwargs(space, existingkeywords, keywords, keywords_w):
+def _check_not_duplicate_kwargs(space, existingkeywords, keywords, keywords_w, 
fnname_parens):
     # looks quadratic, but the JIT should remove all of it nicely.
     # Also, all the lists should be small
     for key in keywords:
         for otherkey in existingkeywords:
             if otherkey == key:
-                raise oefmt(space.w_TypeError,
+                raise_type_error(space, fnname_parens,
                             "got multiple values for keyword argument '%s'",
                             key)
 
 def _do_combine_starstarargs_wrapped(space, keys_w, w_starstararg, keywords,
-        keywords_w, existingkeywords, is_dict):
+        keywords_w, existingkeywords, is_dict, fnname_parens):
     i = 0
     for w_key in keys_w:
         try:
             key = space.text_w(w_key)
         except OperationError as e:
             if e.match(space, space.w_TypeError):
-                raise oefmt(space.w_TypeError,
+                raise_type_error(space, fnname_parens,
                             "keywords must be strings, not '%T'",
                             w_key)
             if e.match(space, space.w_UnicodeEncodeError):
@@ -418,7 +424,7 @@
                 raise
         else:
             if existingkeywords and key in existingkeywords:
-                raise oefmt(space.w_TypeError,
+                raise_type_error(space, fnname_parens,
                             "got multiple values for keyword argument '%s'",
                             key)
         keywords[i] = key
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1180,7 +1180,7 @@
         if frame.get_is_being_profiled() and is_builtin_code(w_func):
             # XXX: this code is copied&pasted :-( from the slow path below
             # call_valuestack().
-            args = frame.make_arguments(nargs)
+            args = frame.make_arguments(nargs, w_function=w_func)
             return self.call_args_and_c_profile(frame, w_func, args)
 
         if not self.config.objspace.disable_call_speedhacks:
@@ -1197,7 +1197,7 @@
                         nargs, frame, methodcall=methodcall)
             # end of hack for performance
 
-        args = frame.make_arguments(nargs)
+        args = frame.make_arguments(nargs, w_function=w_func)
         return self.call_args(w_func, args)
 
     def call_args_and_c_profile(self, frame, w_func, args):
diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py
--- a/pypy/interpreter/function.py
+++ b/pypy/interpreter/function.py
@@ -172,10 +172,10 @@
         elif fast_natural_arity == Code.PASSTHROUGHARGS1 and nargs >= 1:
             assert isinstance(code, gateway.BuiltinCodePassThroughArguments1)
             w_obj = frame.peekvalue(nargs-1)
-            args = frame.make_arguments(nargs-1)
+            args = frame.make_arguments(nargs-1, w_function=self)
             return code.funcrun_obj(self, w_obj, args)
 
-        args = frame.make_arguments(nargs, methodcall=methodcall)
+        args = frame.make_arguments(nargs, methodcall=methodcall, 
w_function=self)
         return self.call_args(args)
 
     @jit.unroll_safe
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -490,14 +490,32 @@
             depth -= 1
         self.valuestackdepth = finaldepth
 
-    def make_arguments(self, nargs, methodcall=False):
+    def _guess_function_name_parens(self, fnname=None, w_function=None):
+        """ Returns 'funcname()' from either a function name fnname or a
+        wrapped callable w_function. If it's not a function or a method, 
returns
+        'Classname object'"""
+        # CPython has a similar function, PyEval_GetFuncName
+        from pypy.interpreter.function import Function, Method
+        if fnname is not None:
+            return fnname + '()'
+        if w_function is None:
+            return None
+        if isinstance(w_function, Function):
+            return w_function.name + '()'
+        if isinstance(w_function, Method):
+            return self._guess_function_name_parens(None, 
w_function.w_function)
+        return w_function.getname(self.space) + ' object'
+
+    def make_arguments(self, nargs, methodcall=False, w_function=None, 
fnname=None):
+        fnname_parens = self._guess_function_name_parens(fnname, w_function)
         return Arguments(
-                self.space, self.peekvalues(nargs), methodcall=methodcall)
+                self.space, self.peekvalues(nargs), methodcall=methodcall, 
fnname_parens=fnname_parens)
 
-    def argument_factory(self, arguments, keywords, keywords_w, w_star, 
w_starstar, methodcall=False):
+    def argument_factory(self, arguments, keywords, keywords_w, w_star, 
w_starstar, methodcall=False, w_function=None, fnname=None):
+        fnname_parens = self._guess_function_name_parens(fnname, w_function)
         return Arguments(
                 self.space, arguments, keywords, keywords_w, w_star,
-                w_starstar, methodcall=methodcall)
+                w_starstar, methodcall=methodcall, fnname_parens=fnname_parens)
 
     def hide(self):
         return self.pycode.hidden_applevel
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -1307,9 +1307,9 @@
         else:
             w_star = None
         arguments = self.popvalues(n_arguments)
+        w_function  = self.popvalue()
         args = self.argument_factory(arguments, keywords, keywords_w, w_star,
-                                     w_starstar)
-        w_function  = self.popvalue()
+                                     w_starstar, w_function=w_function)
         if self.get_is_being_profiled() and 
function.is_builtin_code(w_function):
             w_result = self.space.call_args_and_c_profile(self, w_function,
                                                           args)
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
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import py
+import pytest
 from pypy.interpreter.argument import (Arguments, ArgErr, ArgErrUnknownKwds,
         ArgErrMultipleValues, ArgErrMissing, ArgErrTooMany, 
ArgErrTooManyMethod)
 from pypy.interpreter.signature import Signature
@@ -138,6 +139,7 @@
         class Type:
             def getname(self, space):
                 return type(obj).__name__
+            name = type(obj).__name__
         return Type()
 
 
@@ -332,15 +334,17 @@
 
     def test_duplicate_kwds(self):
         space = DummySpace()
-        excinfo = py.test.raises(OperationError, Arguments, space, [], ["a"],
-                                 [1], w_starstararg={"a": 2})
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"], [1], w_starstararg={"a": 2}, 
fnname_parens="foo()")
         assert excinfo.value.w_type is TypeError
+        assert excinfo.value.get_w_value(space) == "foo() got multiple values 
for keyword argument 'a'"
 
     def test_starstararg_wrong_type(self):
         space = DummySpace()
-        excinfo = py.test.raises(OperationError, Arguments, space, [], ["a"],
-                                 [1], w_starstararg="hello")
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"], [1], w_starstararg="hello", 
fnname_parens="bar()")
         assert excinfo.value.w_type is TypeError
+        assert excinfo.value.get_w_value(space) == "bar() argument after ** 
must be a mapping, not str"
 
     def test_unwrap_error(self):
         space = DummySpace()
@@ -353,12 +357,12 @@
             return bytes(w, 'utf-8')
         space.utf8_w = utf8_w
         space.text_w = utf8_w
-        excinfo = py.test.raises(OperationError, Arguments, space, [],
-                                 ["a"], [1], w_starstararg={None: 1})
+        with py.test.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"], [1], w_starstararg={None: 1}, 
fnname_parens="f1()")
         assert excinfo.value.w_type is TypeError
         assert excinfo.value._w_value is None
-        excinfo = py.test.raises(OperationError, Arguments, space, [],
-                                 ["a"], [1], w_starstararg={valuedummy: 1})
+        with py.test.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"], [1], w_starstararg={valuedummy: 1}, 
fnname_parens="f2()")
         assert excinfo.value.w_type is ValueError
         assert excinfo.value._w_value is None
 
@@ -435,9 +439,9 @@
             raise FakeArgErr()
         args._match_signature = _match_signature
 
-
-        excinfo = py.test.raises(OperationError, args.parse_obj, "obj", "foo",
-                       Signature(["a", "b"], None, None))
+        with pytest.raises(OperationError) as excinfo:
+            args.parse_obj("obj", "foo",
+                           Signature(["a", "b"], None, None))
         assert excinfo.value.w_type is TypeError
         assert excinfo.value.get_w_value(space) == "foo() msg"
 
@@ -491,9 +495,9 @@
         args._match_signature = _match_signature
 
 
-        excinfo = py.test.raises(OperationError, args.parse_into_scope,
-                                 "obj", [None, None], "foo",
-                                 Signature(["a", "b"], None, None))
+        with pytest.raises(OperationError) as excinfo:
+            args.parse_into_scope("obj", [None, None], "foo",
+                                  Signature(["a", "b"], None, None))
         assert excinfo.value.w_type is TypeError
         assert excinfo.value.get_w_value(space) == "foo() msg"
 
@@ -568,9 +572,17 @@
             l = [None, None, None]
             args._match_signature(None, l, Signature(["a", "b"], None, "**"))
             assert l == [1, 2, {'c': 3}]
-        excinfo = py.test.raises(OperationError, Arguments, space, [], ["a"],
-                                 [1], w_starstararg=kwargs(["a"], [2]))
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"],
+                      [1], w_starstararg=kwargs(["a"], [2]))
         assert excinfo.value.w_type is TypeError
+        assert excinfo.value.get_w_value(space) == "got multiple values for 
keyword argument 'a'"
+
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], ["a"],
+                      [1], w_starstararg=kwargs(["a"], [2]), 
fnname_parens="foo()")
+        assert excinfo.value.w_type is TypeError
+        assert excinfo.value.get_w_value(space) == "foo() got multiple values 
for keyword argument 'a'"
 
 
 
@@ -671,20 +683,14 @@
 
     def test_bad_type_for_star(self):
         space = self.space
-        try:
-            Arguments(space, [], w_stararg=space.wrap(42))
-        except OperationError as e:
-            msg = space.text_w(space.str(e.get_w_value(space)))
-            assert msg == "argument after * must be an iterable, not int"
-        else:
-            assert 0, "did not raise"
-        try:
-            Arguments(space, [], w_starstararg=space.wrap(42))
-        except OperationError as e:
-            msg = space.text_w(space.str(e.get_w_value(space)))
-            assert msg == "argument after ** must be a mapping, not int"
-        else:
-            assert 0, "did not raise"
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], w_stararg=space.wrap(42), 
fnname_parens="f1()")
+        msg = space.text_w(excinfo.value.get_w_value(space))
+        assert msg == "f1() argument after * must be an iterable, not int"
+        with pytest.raises(OperationError) as excinfo:
+            Arguments(space, [], w_starstararg=space.wrap(42), 
fnname_parens="f2()")
+        msg = space.text_w(excinfo.value.get_w_value(space))
+        assert msg == "f2() argument after ** must be a mapping, not int"
 
     def test_dont_count_default_arguments(self):
         space = self.space
@@ -889,7 +895,7 @@
             pass
         e = raises(TypeError, "f(*42)")
         assert str(e.value).endswith(
-            "argument after * must be an iterable, not int")
+            "f() argument after * must be an iterable, not int")
         e = raises(TypeError, "f(*X())")
         assert str(e.value) == "myerror"
 
@@ -904,8 +910,10 @@
         def f(x, y):
             pass
         e = raises(TypeError, "f(y=2, **{3: 5}, x=6)")
-        assert "keywords must be strings" in str(e.value)
+        assert "f() keywords must be strings" in str(e.value)
         e = raises(TypeError, "f(y=2, **{'x': 5}, x=6)")
+        # CPython figures out the name here, by peeking around in the stack in
+        # BUILD_MAP_UNPACK_WITH_CALL. we don't, too messy
         assert "got multiple values for keyword argument 'x'" in str(e.value)
 
     def test_dict_subclass_with_weird_getitem(self):
diff --git a/pypy/objspace/std/callmethod.py b/pypy/objspace/std/callmethod.py
--- a/pypy/objspace/std/callmethod.py
+++ b/pypy/objspace/std/callmethod.py
@@ -111,12 +111,12 @@
             keywords_w[n_kwargs] = w_value
 
         arguments = f.popvalues(n)    # includes w_self if it is not None
-        args = f.argument_factory(
-                arguments, keywords, keywords_w, None, None,
-                methodcall=w_self is not None)
         if w_self is None:
             f.popvalue_maybe_none()    # removes w_self, which is None
         w_callable = f.popvalue()
+        args = f.argument_factory(
+                arguments, keywords, keywords_w, None, None,
+                methodcall=w_self is not None, w_function=w_callable)
         if f.get_is_being_profiled() and function.is_builtin_code(w_callable):
             w_result = f.space.call_args_and_c_profile(f, w_callable, args)
         else:
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to