Author: Manuel Jacob <[email protected]>
Branch: py3.3
Changeset: r78587:cb29adb88801
Date: 2015-07-17 18:10 +0200
http://bitbucket.org/pypy/pypy/changeset/cb29adb88801/
Log: Implement improved function call exception messages.
diff --git a/lib-python/3/test/test_extcall.py
b/lib-python/3/test/test_extcall.py
--- a/lib-python/3/test/test_extcall.py
+++ b/lib-python/3/test/test_extcall.py
@@ -89,19 +89,19 @@
>>> class Nothing: pass
...
- >>> g(*Nothing())
+ >>> g(*Nothing()) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: g() argument after * must be a sequence, not Nothing
+ TypeError: ...argument after * must be a sequence, not Nothing
>>> class Nothing:
... def __len__(self): return 5
...
- >>> g(*Nothing())
+ >>> g(*Nothing()) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: g() argument after * must be a sequence, not Nothing
+ TypeError: ...argument after * must be a sequence, not Nothing
>>> class Nothing():
... def __len__(self): return 5
@@ -153,52 +153,50 @@
...
TypeError: g() got multiple values for argument 'x'
- >>> f(**{1:2})
+ >>> f(**{1:2}) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: f() keywords must be strings
+ TypeError: ...keywords must be strings
>>> h(**{'e': 2})
Traceback (most recent call last):
...
TypeError: h() got an unexpected keyword argument 'e'
- >>> h(*h)
+ >>> h(*h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: h() argument after * must be a sequence, not function
+ TypeError: ...argument after * must be a sequence, not function
- >>> dir(*h)
+ >>> dir(*h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: dir() argument after * must be a sequence, not function
+ TypeError: ...argument after * must be a sequence, not function
- >>> None(*h)
+ >>> None(*h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: NoneType object argument after * must be a sequence, \
-not function
+ TypeError: ...argument after * must be a sequence, not function
- >>> h(**h)
+ >>> h(**h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: h() argument after ** must be a mapping, not function
+ TypeError: ...argument after ** must be a mapping, not function
- >>> dir(**h)
+ >>> dir(**h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: dir() argument after ** must be a mapping, not function
+ TypeError: ...argument after ** must be a mapping, not function
- >>> None(**h)
+ >>> None(**h) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: NoneType object argument after ** must be a mapping, \
-not function
+ TypeError: ...argument after ** must be a mapping, not function
- >>> dir(b=1, **{'b': 1})
+ >>> dir(b=1, **{'b': 1}) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: dir() got multiple values for keyword argument 'b'
+ TypeError: ...got multiple values for keyword argument 'b'
Another helper function
@@ -239,10 +237,10 @@
... False
True
- >>> id(1, **{'foo': 1})
+ >>> id(1, **{'foo': 1}) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
- TypeError: id() takes no keyword arguments
+ TypeError: id() ... keyword argument...
A corner case of keyword dictionary items being deleted during
the function call setup. See <http://bugs.python.org/issue2016>.
diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -4,6 +4,7 @@
from rpython.rlib.debug import make_sure_not_resized
from rpython.rlib import jit
from rpython.rlib.objectmodel import enforceargs
+from rpython.rlib.rstring import StringBuilder
from pypy.interpreter.error import OperationError, oefmt
@@ -210,7 +211,13 @@
loc = co_argcount + co_kwonlyargcount
scope_w[loc] = self.space.newtuple(starargs_w)
elif avail > co_argcount:
- raise ArgErrCount(avail, num_kwds, signature, defaults_w,
w_kw_defs, 0)
+ kwonly_given = 0
+ for i in range(co_argcount, co_argcount + co_kwonlyargcount):
+ if scope_w[i] is None:
+ kwonly_given += 1
+ raise ArgErrTooMany(signature.num_argnames(),
+ 0 if defaults_w is None else len(defaults_w),
+ avail, kwonly_given)
# if a **kwargs argument is needed, create the dict
w_kwds = None
@@ -243,16 +250,13 @@
self.space, keywords, keywords_w, w_kwds,
kwds_mapping, self.keyword_names_w,
self._jit_few_keywords)
else:
- if co_argcount == 0:
- raise ArgErrCount(avail, num_kwds, signature,
defaults_w,
- w_kw_defs, 0)
-
raise ArgErrUnknownKwds(self.space, num_remainingkwds,
keywords,
kwds_mapping, self.keyword_names_w)
# check for missing arguments and fill them from the kwds,
# or with defaults, if available
- missing = 0
+ missing_positional = []
+ missing_kwonly = []
if input_argcount < co_argcount + co_kwonlyargcount:
def_first = co_argcount - (0 if defaults_w is None else
len(defaults_w))
j = 0
@@ -273,25 +277,26 @@
if defnum >= 0:
scope_w[i] = defaults_w[defnum]
else:
- missing += 1
+ missing_positional.append(signature.argnames[i])
# finally, fill kwonly arguments with w_kw_defs (if needed)
for i in range(co_argcount, co_argcount + co_kwonlyargcount):
if scope_w[i] is not None:
continue
- elif w_kw_defs is None:
- missing += 1
+ name = signature.kwonlyargnames[i - co_argcount]
+ if w_kw_defs is None:
+ missing_kwonly.append(name)
continue
- name = signature.kwonlyargnames[i - co_argcount]
w_def = self.space.finditem_str(w_kw_defs, name)
if w_def is not None:
scope_w[i] = w_def
else:
- missing += 1
+ missing_kwonly.append(name)
- if missing:
- raise ArgErrCount(avail, num_kwds, signature,
- defaults_w, w_kw_defs, missing)
+ if missing_positional:
+ raise ArgErrMissing(missing_positional, True)
+ if missing_kwonly:
+ raise ArgErrMissing(missing_kwonly, False)
def parse_into_scope(self, w_firstarg,
@@ -461,61 +466,68 @@
def getmsg(self):
raise NotImplementedError
-class ArgErrCount(ArgErr):
- def __init__(self, got_nargs, nkwds, signature,
- defaults_w, w_kw_defs, missing_args):
- self.signature = signature
- self.num_defaults = 0 if defaults_w is None else len(defaults_w)
- self.missing_args = missing_args
- self.num_args = got_nargs
- self.num_kwds = nkwds
+class ArgErrMissing(ArgErr):
+ def __init__(self, missing, positional):
+ self.missing = missing
+ self.positional = positional # keyword-only otherwise
def getmsg(self):
- n = self.signature.num_argnames()
- if n == 0:
- msg = "takes no arguments (%d given)" % (
- self.num_args + self.num_kwds)
+ arguments_str = StringBuilder()
+ for i, arg in enumerate(self.missing):
+ if i == 0:
+ pass
+ elif i == len(self.missing) - 1:
+ if len(self.missing) == 2:
+ arguments_str.append(" and ")
+ else:
+ arguments_str.append(", and ")
+ else:
+ arguments_str.append(", ")
+ arguments_str.append("'%s'" % arg)
+ msg = "missing %s required %s argument%s: %s" % (
+ len(self.missing),
+ "positional" if self.positional else "keyword-only",
+ "s" if len(self.missing) != 1 else "",
+ arguments_str.build())
+ return msg
+
+
+class ArgErrTooMany(ArgErr):
+ def __init__(self, num_args, num_defaults, given, kwonly_given):
+ self.num_args = num_args
+ self.num_defaults = num_defaults
+ self.given = given
+ self.kwonly_given = kwonly_given
+
+ def getmsg(self):
+ num_args = self.num_args
+ num_defaults = self.num_defaults
+ if num_defaults:
+ takes_str = "from %d to %d positional arguments" % (
+ num_args - num_defaults, num_args)
else:
- defcount = self.num_defaults
- has_kwarg = self.signature.has_kwarg()
- num_args = self.num_args
- num_kwds = self.num_kwds
- if defcount == 0 and not self.signature.has_vararg():
- msg1 = "exactly"
- if not has_kwarg:
- num_args += num_kwds
- num_kwds = 0
- elif not self.missing_args:
- msg1 = "at most"
- else:
- msg1 = "at least"
- has_kwarg = False
- n -= defcount
- if n == 1:
- plural = ""
- else:
- plural = "s"
- if has_kwarg or num_kwds > 0:
- msg2 = " non-keyword"
- else:
- msg2 = ""
- msg = "takes %s %d%s argument%s (%d given)" % (
- msg1,
- n,
- msg2,
- plural,
- num_args)
+ takes_str = "%d positional argument%s" % (
+ num_args, "s" if num_args != 1 else "")
+ if self.kwonly_given:
+ given_str = ("%s positional argument%s "
+ "(and %s keyword-only argument%s) were") % (
+ self.given, "s" if self.given != 1 else "",
+ self.kwonly_given, "s" if self.kwonly_given != 1 else "")
+ else:
+ given_str = "%s %s" % (
+ self.given, "were" if self.given != 1 else "was")
+ msg = "takes %s but %s given" % (takes_str, given_str)
return msg
+
class ArgErrMultipleValues(ArgErr):
def __init__(self, argname):
self.argname = argname
def getmsg(self):
- msg = "got multiple values for keyword argument '%s'" % (
- self.argname)
+ msg = "got multiple values for argument '%s'" % self.argname
return msg
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,7 +1,7 @@
# -*- coding: utf-8 -*-
import py
from pypy.interpreter.argument import (Arguments, ArgErr, ArgErrUnknownKwds,
- ArgErrMultipleValues, ArgErrCount)
+ ArgErrMultipleValues, ArgErrMissing, ArgErrTooMany)
from pypy.interpreter.signature import Signature
from pypy.interpreter.error import OperationError
@@ -575,51 +575,54 @@
class TestErrorHandling(object):
def test_missing_args(self):
- # got_nargs, nkwds, expected_nargs, has_vararg, has_kwarg,
- # defaults_w, missing_args
- sig = Signature([], None, None)
- err = ArgErrCount(1, 0, sig, None, None, 0)
+ err = ArgErrMissing(['a'], True)
s = err.getmsg()
- assert s == "takes no arguments (1 given)"
+ assert s == "missing 1 required positional argument: 'a'"
- sig = Signature(['a'], None, None)
- err = ArgErrCount(0, 0, sig, [], None, 1)
+ err = ArgErrMissing(['a', 'b'], True)
s = err.getmsg()
- assert s == "takes exactly 1 argument (0 given)"
+ assert s == "missing 2 required positional arguments: 'a' and 'b'"
- sig = Signature(['a', 'b'], None, None)
- err = ArgErrCount(3, 0, sig, [], None, 0)
+ err = ArgErrMissing(['a', 'b', 'c'], True)
s = err.getmsg()
- assert s == "takes exactly 2 arguments (3 given)"
- err = ArgErrCount(3, 0, sig, ['a'], None, 0)
+ assert s == "missing 3 required positional arguments: 'a', 'b', and
'c'"
+
+ err = ArgErrMissing(['a'], False)
s = err.getmsg()
- assert s == "takes at most 2 arguments (3 given)"
+ assert s == "missing 1 required keyword-only argument: 'a'"
- sig = Signature(['a', 'b'], '*', None)
- err = ArgErrCount(1, 0, sig, [], None, 1)
+ def test_too_many(self):
+ err = ArgErrTooMany(0, 0, 1, 0)
s = err.getmsg()
- assert s == "takes at least 2 arguments (1 given)"
- err = ArgErrCount(0, 1, sig, ['a'], None, 1)
+ assert s == "takes 0 positional arguments but 1 was given"
+
+ err = ArgErrTooMany(0, 0, 2, 0)
s = err.getmsg()
- assert s == "takes at least 1 non-keyword argument (0 given)"
+ assert s == "takes 0 positional arguments but 2 were given"
- sig = Signature(['a'], None, '**')
- err = ArgErrCount(2, 1, sig, [], None, 0)
+ err = ArgErrTooMany(1, 0, 2, 0)
s = err.getmsg()
- assert s == "takes exactly 1 non-keyword argument (2 given)"
- err = ArgErrCount(0, 1, sig, [], None, 1)
+ assert s == "takes 1 positional argument but 2 were given"
+
+ err = ArgErrTooMany(2, 0, 3, 0)
s = err.getmsg()
- assert s == "takes exactly 1 non-keyword argument (0 given)"
+ assert s == "takes 2 positional arguments but 3 were given"
- sig = Signature(['a'], '*', '**')
- err = ArgErrCount(0, 1, sig, [], None, 1)
+ err = ArgErrTooMany(2, 1, 3, 0)
s = err.getmsg()
- assert s == "takes at least 1 non-keyword argument (0 given)"
+ assert s == "takes from 1 to 2 positional arguments but 3 were given"
- sig = Signature(['a'], None, '**')
- err = ArgErrCount(2, 1, sig, ['a'], None, 0)
+ err = ArgErrTooMany(0, 0, 1, 1)
s = err.getmsg()
- assert s == "takes at most 1 non-keyword argument (2 given)"
+ assert s == "takes 0 positional arguments but 1 positional argument
(and 1 keyword-only argument) were given"
+
+ err = ArgErrTooMany(0, 0, 2, 1)
+ s = err.getmsg()
+ assert s == "takes 0 positional arguments but 2 positional arguments
(and 1 keyword-only argument) were given"
+
+ err = ArgErrTooMany(0, 0, 1, 2)
+ s = err.getmsg()
+ assert s == "takes 0 positional arguments but 1 positional argument
(and 2 keyword-only arguments) were given"
def test_bad_type_for_star(self):
space = self.space
@@ -665,28 +668,36 @@
def test_multiple_values(self):
err = ArgErrMultipleValues('bla')
s = err.getmsg()
- assert s == "got multiple values for keyword argument 'bla'"
+ assert s == "got multiple values for argument 'bla'"
class AppTestArgument:
def test_error_message(self):
exc = raises(TypeError, (lambda a, b=2: 0), b=3)
- assert str(exc.value) == "<lambda>() takes at least 1 non-keyword
argument (0 given)"
+ assert str(exc.value) == "<lambda>() missing 1 required positional
argument: 'a'"
exc = raises(TypeError, (lambda: 0), b=3)
- assert str(exc.value) == "<lambda>() takes no arguments (1 given)"
+ assert str(exc.value) == "<lambda>() got an unexpected keyword
argument 'b'"
exc = raises(TypeError, (lambda a, b: 0), 1, 2, 3, a=1)
- assert str(exc.value) == "<lambda>() takes exactly 2 arguments (4
given)"
+ assert str(exc.value) == "<lambda>() takes 2 positional arguments but
3 were given"
exc = raises(TypeError, (lambda a, b=1: 0), 1, 2, 3, a=1)
- assert str(exc.value) == "<lambda>() takes at most 2 non-keyword
arguments (3 given)"
+ assert str(exc.value) == "<lambda>() takes from 1 to 2 positional
arguments but 3 were given"
+ exc = raises(TypeError, (lambda a, **kw: 0), 1, 2, 3)
+ assert str(exc.value) == "<lambda>() takes 1 positional argument but 3
were given"
exc = raises(TypeError, (lambda a, b=1, **kw: 0), 1, 2, 3)
- assert str(exc.value) == "<lambda>() takes at most 2 non-keyword
arguments (3 given)"
+ assert str(exc.value) == "<lambda>() takes from 1 to 2 positional
arguments but 3 were given"
exc = raises(TypeError, (lambda a, b, c=3, **kw: 0), 1)
- assert str(exc.value) == "<lambda>() takes at least 2 arguments (1
given)"
+ assert str(exc.value) == "<lambda>() missing 1 required positional
argument: 'b'"
exc = raises(TypeError, (lambda a, b, **kw: 0), 1)
- assert str(exc.value) == "<lambda>() takes exactly 2 non-keyword
arguments (1 given)"
+ assert str(exc.value) == "<lambda>() missing 1 required positional
argument: 'b'"
exc = raises(TypeError, (lambda a, b, c=3, **kw: 0), a=1)
- assert str(exc.value) == "<lambda>() takes at least 2 non-keyword
arguments (0 given)"
+ assert str(exc.value) == "<lambda>() missing 1 required positional
argument: 'b'"
exc = raises(TypeError, (lambda a, b, **kw: 0), a=1)
- assert str(exc.value) == "<lambda>() takes exactly 2 non-keyword
arguments (0 given)"
+ assert str(exc.value) == "<lambda>() missing 1 required positional
argument: 'b'"
+ exc = raises(TypeError, '(lambda *, a: 0)()')
+ assert str(exc.value) == "<lambda>() missing 1 required keyword-only
argument: 'a'"
+ exc = raises(TypeError, '(lambda *, a=1, b: 0)(a=1)')
+ assert str(exc.value) == "<lambda>() missing 1 required keyword-only
argument: 'b'"
+ exc = raises(TypeError, '(lambda *, kw: 0)(1, kw=3)')
+ assert str(exc.value) == "<lambda>() takes 0 positional arguments but
1 positional argument (and 1 keyword-only argument) were given"
def test_unicode_keywords(self):
"""
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit