Author: Carl Friedrich Bolz <[email protected]>
Branch:
Changeset: r87567:a8c5f42e70cf
Date: 2016-10-04 16:22 +0200
http://bitbucket.org/pypy/pypy/changeset/a8c5f42e70cf/
Log: merge better-error-missing-self: improve the error message when
trying to call a method where the self parameter is missing in the
definition
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -51,3 +51,7 @@
.. branch: test-cpyext
Refactor cpyext testing to be more pypy3-friendly.
+
+.. branch: better-error-missing-self
+
+Improve the error message when the user forgot the "self" argument of a method.
diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -21,7 +21,8 @@
### Construction ###
def __init__(self, space, args_w, keywords=None, keywords_w=None,
- w_stararg=None, w_starstararg=None, keyword_names_w=None):
+ w_stararg=None, w_starstararg=None, keyword_names_w=None,
+ methodcall=False):
self.space = space
assert isinstance(args_w, list)
self.arguments_w = args_w
@@ -41,6 +42,9 @@
# 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))
+ # a flag whether this is likely a method call, which doesn't change the
+ # behaviour but produces better error messages
+ self.methodcall = methodcall
def __repr__(self):
""" NOT_RPYTHON """
@@ -207,7 +211,7 @@
starargs_w = []
scope_w[co_argcount] = self.space.newtuple(starargs_w)
elif avail > co_argcount:
- raise ArgErrCount(avail, num_kwds, signature, defaults_w, 0)
+ raise self.argerrcount(avail, num_kwds, signature, defaults_w, 0)
# if a **kwargs argument is needed, create the dict
w_kwds = None
@@ -241,7 +245,7 @@
kwds_mapping, self.keyword_names_w,
self._jit_few_keywords)
else:
if co_argcount == 0:
- raise ArgErrCount(avail, num_kwds, signature,
defaults_w, 0)
+ raise self.argerrcount(avail, num_kwds, signature,
defaults_w, 0)
raise ArgErrUnknownKwds(self.space, num_remainingkwds,
keywords,
kwds_mapping, self.keyword_names_w)
@@ -265,9 +269,12 @@
else:
missing += 1
if missing:
- raise ArgErrCount(avail, num_kwds, signature, defaults_w,
missing)
+ raise self.argerrcount(avail, num_kwds, signature, defaults_w,
missing)
-
+ def argerrcount(self, *args):
+ if self.methodcall:
+ return ArgErrCountMethod(*args)
+ return ArgErrCount(*args)
def parse_into_scope(self, w_firstarg,
scope_w, fnname, signature, defaults_w=None):
@@ -478,6 +485,22 @@
num_args)
return msg
+class ArgErrCountMethod(ArgErrCount):
+ """ A subclass of ArgErrCount that is used if the argument matching is done
+ as part of a method call, in which case more information is added to the
+ error message, if the cause of the error is likely a forgotten `self`
+ argument.
+ """
+
+ def getmsg(self):
+ msg = ArgErrCount.getmsg(self)
+ n = self.signature.num_argnames()
+ if (self.num_args == n + 1 and
+ (n == 0 or self.signature.argnames[0] != "self")):
+ msg += ". Did you forget 'self' in the function definition?"
+ return msg
+
+
class ArgErrMultipleValues(ArgErr):
def __init__(self, argname):
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1115,7 +1115,8 @@
args = Arguments(self, list(args_w))
return self.call_args(w_func, args)
- def call_valuestack(self, w_func, nargs, frame):
+ def call_valuestack(self, w_func, nargs, frame, methodcall=False):
+ # methodcall is only used for better error messages in argument.py
from pypy.interpreter.function import Function, Method, is_builtin_code
if frame.get_is_being_profiled() and is_builtin_code(w_func):
# XXX: this code is copied&pasted :-( from the slow path below
@@ -1132,13 +1133,15 @@
# reuse callable stack place for w_inst
frame.settopvalue(w_inst, nargs)
nargs += 1
+ methodcall = True
elif nargs > 0 and (
self.abstract_isinstance_w(frame.peekvalue(nargs-1), #
:-(
w_func.w_class)):
w_func = w_func.w_function
if isinstance(w_func, Function):
- return w_func.funccall_valuestack(nargs, frame)
+ return w_func.funccall_valuestack(
+ nargs, frame, methodcall=methodcall)
# end of hack for performance
args = frame.make_arguments(nargs)
diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py
--- a/pypy/interpreter/function.py
+++ b/pypy/interpreter/function.py
@@ -117,7 +117,8 @@
list(args_w[1:])))
return self.call_args(Arguments(self.space, list(args_w)))
- def funccall_valuestack(self, nargs, frame): # speed hack
+ def funccall_valuestack(self, nargs, frame, methodcall=False): # speed hack
+ # methodcall is only for better error messages
from pypy.interpreter import gateway
from pypy.interpreter.pycode import PyCode
@@ -164,7 +165,7 @@
args = frame.make_arguments(nargs-1)
return code.funcrun_obj(self, w_obj, args)
- args = frame.make_arguments(nargs)
+ args = frame.make_arguments(nargs, methodcall=methodcall)
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
@@ -403,11 +403,14 @@
depth -= 1
self.valuestackdepth = finaldepth
- def make_arguments(self, nargs):
- return Arguments(self.space, self.peekvalues(nargs))
+ def make_arguments(self, nargs, methodcall=False):
+ return Arguments(
+ self.space, self.peekvalues(nargs), methodcall=methodcall)
- def argument_factory(self, arguments, keywords, keywords_w, w_star,
w_starstar):
- return Arguments(self.space, arguments, keywords, keywords_w, w_star,
w_starstar)
+ def argument_factory(self, arguments, keywords, keywords_w, w_star,
w_starstar, methodcall=False):
+ return Arguments(
+ self.space, arguments, keywords, keywords_w, w_star,
+ w_starstar, methodcall=methodcall)
@jit.dont_look_inside
def descr__reduce__(self, space):
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, ArgErrCount, ArgErrCountMethod)
from pypy.interpreter.signature import Signature
from pypy.interpreter.error import OperationError
@@ -573,6 +573,10 @@
s = err.getmsg()
assert s == "takes exactly 1 argument (0 given)"
+ sig = Signature(['self', 'b'], None, None)
+ err = ArgErrCount(3, 0, sig, [], 0)
+ s = err.getmsg()
+ assert s == "takes exactly 2 arguments (3 given)"
sig = Signature(['a', 'b'], None, None)
err = ArgErrCount(3, 0, sig, [], 0)
s = err.getmsg()
@@ -607,6 +611,57 @@
s = err.getmsg()
assert s == "takes at most 1 non-keyword argument (2 given)"
+ def test_missing_args_method(self):
+ # got_nargs, nkwds, expected_nargs, has_vararg, has_kwarg,
+ # defaults_w, missing_args
+ sig = Signature([], None, None)
+ err = ArgErrCountMethod(1, 0, sig, None, 0)
+ s = err.getmsg()
+ assert s == "takes no arguments (1 given). Did you forget 'self' in
the function definition?"
+
+ sig = Signature(['a'], None, None)
+ err = ArgErrCountMethod(0, 0, sig, [], 1)
+ s = err.getmsg()
+ assert s == "takes exactly 1 argument (0 given)"
+
+ sig = Signature(['self', 'b'], None, None)
+ err = ArgErrCountMethod(3, 0, sig, [], 0)
+ s = err.getmsg()
+ assert s == "takes exactly 2 arguments (3 given)"
+ sig = Signature(['a', 'b'], None, None)
+ err = ArgErrCountMethod(3, 0, sig, [], 0)
+ s = err.getmsg()
+ assert s == "takes exactly 2 arguments (3 given). Did you forget
'self' in the function definition?"
+ err = ArgErrCountMethod(3, 0, sig, ['a'], 0)
+ s = err.getmsg()
+ assert s == "takes at most 2 arguments (3 given). Did you forget
'self' in the function definition?"
+
+ sig = Signature(['a', 'b'], '*', None)
+ err = ArgErrCountMethod(1, 0, sig, [], 1)
+ s = err.getmsg()
+ assert s == "takes at least 2 arguments (1 given)"
+ err = ArgErrCountMethod(0, 1, sig, ['a'], 1)
+ s = err.getmsg()
+ assert s == "takes at least 1 non-keyword argument (0 given)"
+
+ sig = Signature(['a'], None, '**')
+ err = ArgErrCountMethod(2, 1, sig, [], 0)
+ s = err.getmsg()
+ assert s == "takes exactly 1 non-keyword argument (2 given). Did you
forget 'self' in the function definition?"
+ err = ArgErrCountMethod(0, 1, sig, [], 1)
+ s = err.getmsg()
+ assert s == "takes exactly 1 non-keyword argument (0 given)"
+
+ sig = Signature(['a'], '*', '**')
+ err = ArgErrCountMethod(0, 1, sig, [], 1)
+ s = err.getmsg()
+ assert s == "takes at least 1 non-keyword argument (0 given)"
+
+ sig = Signature(['a'], None, '**')
+ err = ArgErrCountMethod(2, 1, sig, ['a'], 0)
+ s = err.getmsg()
+ assert s == "takes at most 1 non-keyword argument (2 given). Did you
forget 'self' in the function definition?"
+
def test_bad_type_for_star(self):
space = self.space
try:
@@ -674,6 +729,45 @@
exc = raises(TypeError, (lambda a, b, **kw: 0), a=1)
assert exc.value.message == "<lambda>() takes exactly 2 non-keyword
arguments (0 given)"
+ @py.test.mark.skipif("config.option.runappdirect")
+ def test_error_message_method(self):
+ class A(object):
+ def f0():
+ pass
+ def f1(a):
+ pass
+ exc = raises(TypeError, lambda : A().f0())
+ assert exc.value.message == "f0() takes no arguments (1 given). Did
you forget 'self' in the function definition?"
+ exc = raises(TypeError, lambda : A().f1(1))
+ assert exc.value.message == "f1() takes exactly 1 argument (2 given).
Did you forget 'self' in the function definition?"
+ def f0():
+ pass
+ exc = raises(TypeError, f0, 1)
+ # does not contain the warning about missing self
+ assert exc.value.message == "f0() takes no arguments (1 given)"
+
+ @py.test.mark.skipif("config.option.runappdirect")
+ def test_error_message_module_function(self):
+ import operator # use repeat because it's defined at applevel
+ exc = raises(TypeError, lambda : operator.repeat(1, 2, 3))
+ # does not contain the warning about missing self
+ assert exc.value.message == "repeat() takes exactly 2 arguments (3
given)"
+
+ @py.test.mark.skipif("config.option.runappdirect")
+ def test_error_message_bound_method(self):
+ class A(object):
+ def f0():
+ pass
+ def f1(a):
+ pass
+ m0 = A().f0
+ exc = raises(TypeError, lambda : m0())
+ assert exc.value.message == "f0() takes no arguments (1 given). Did
you forget 'self' in the function definition?"
+ m1 = A().f1
+ exc = raises(TypeError, lambda : m1(1))
+ assert exc.value.message == "f1() takes exactly 1 argument (2 given).
Did you forget 'self' in the function definition?"
+
+
def test_unicode_keywords(self):
def f(**kwargs):
assert kwargs[u"美"] == 42
diff --git a/pypy/module/pypyjit/test_pypy_c/test_call.py
b/pypy/module/pypyjit/test_pypy_c/test_call.py
--- a/pypy/module/pypyjit/test_pypy_c/test_call.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_call.py
@@ -388,6 +388,7 @@
setfield_gc(p22, ConstPtr(null), descr=<FieldP
pypy.interpreter.argument.Arguments.inst_keywords_w .*>)
setfield_gc(p22, ConstPtr(null), descr=<FieldP
pypy.interpreter.argument.Arguments.inst_keywords .*>)
setfield_gc(p22, 1, descr=<FieldU
pypy.interpreter.argument.Arguments.inst__jit_few_keywords .*>)
+ setfield_gc(p22, 0, descr=<FieldU
pypy.interpreter.argument.Arguments.inst_methodcall .*>)
setfield_gc(p22, ConstPtr(null), descr=<FieldP
pypy.interpreter.argument.Arguments.inst_keyword_names_w .*>)
setfield_gc(p26, ConstPtr(ptr22), descr=<FieldP
pypy.objspace.std.listobject.W_ListObject.inst_strategy .*>)
setfield_gc(p26, ConstPtr(null), descr=<FieldP
pypy.objspace.std.listobject.W_ListObject.inst_lstorage .*>)
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
@@ -93,7 +93,8 @@
if not n_kwargs:
w_callable = f.peekvalue(n_args + (2 * n_kwargs) + 1)
try:
- w_result = f.space.call_valuestack(w_callable, n, f)
+ w_result = f.space.call_valuestack(
+ w_callable, n, f, methodcall=w_self is not None)
finally:
f.dropvalues(n_args + 2)
else:
@@ -110,7 +111,9 @@
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)
+ args = f.argument_factory(
+ arguments, keywords, keywords_w, None, None,
+ methodcall=w_self is not None)
if w_self is None:
f.popvalue() # removes w_self, which is None
w_callable = f.popvalue()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit