I agree, though the challenge is how we can detect that we’re calling a bound method when currently the plumbing is just not there–by the time the error is being generated the context of a bound method is already gone.
One fairly reasonable heuristic I can think of to help detect a bound method call is to check if the type of the first argument has an attribute whose name is the same as the name of the code object of the function being called, and whose value is the code object of the function being called. Unless one deliberately tries to meet these conditions, the heuristic should be enough to determine that a helpful hint is warranted when the number of given arguments is one more than the number of declare parameters.
Here’s my attempt at a modification to the existing too_many_positional function in Python/ceval.c to help illustrate the heuristic:
static int
implicit_self_was_passed(PyCodeObject *co, _PyStackRef *localsplus)
{
if (co->co_argcount < 1) {
return 0;
}
PyObject *first_name = PyTuple_GET_ITEM(co->co_localsplusnames, 0);
/* if the first parameter is actually named self, it means the user
clearly knows about self so there's no need to produce the hint */
if (_PyUnicode_EqualToASCIIString(first_name, "self")) {
return 0;
}
PyObject *first = PyStackRef_AsPyObjectBorrow(localsplus[0]);
if (first == NULL) {
return 0;
}
PyObject *attr = _PyType_Lookup(Py_TYPE(first), co->co_name);
if (attr == NULL) {
return 0;
}
if (!PyFunction_Check(attr)) {
return 0;
}
return ((PyFunctionObject *)attr)->func_code == (PyObject *)co;
}
static void
too_many_positional(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t given, PyObject *defaults,
_PyStackRef *localsplus, PyObject *qualname)
{
/* ... */
const char *self_hint =
(given == co_argcount + 1 && implicit_self_was_passed(co, localsplus))
? ". Note: 'self' was passed implicitly; did you forget to declare it?"
: "";
_PyErr_Format(tstate, PyExc_TypeError,
"%U() takes %U positional argument%s but %zd%U %s given%s",
qualname,
sig,
plural ? "s" : "",
given,
kwonly_sig,
given == 1 && !kwonly_given ? "was" : "were",
self_hint);
/* ... */
}