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