Skip to content

Commit 9ef597f

Browse files
committed
Add suggestion for self
1 parent fcda96f commit 9ef597f

1 file changed

Lines changed: 58 additions & 5 deletions

File tree

Python/ceval.c

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,12 +1574,13 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
15741574
static void
15751575
too_many_positional(PyThreadState *tstate, PyCodeObject *co,
15761576
Py_ssize_t given, PyObject *defaults,
1577-
_PyStackRef *localsplus, PyObject *qualname)
1577+
_PyStackRef *localsplus, PyObject *qualname,
1578+
int suggest_missing_self)
15781579
{
15791580
int plural;
15801581
Py_ssize_t kwonly_given = 0;
15811582
Py_ssize_t i;
1582-
PyObject *sig, *kwonly_sig;
1583+
PyObject *sig, *kwonly_sig, *self_hint = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
15831584
Py_ssize_t co_argcount = co->co_argcount;
15841585

15851586
assert((co->co_flags & CO_VARARGS) == 0);
@@ -1617,18 +1618,69 @@ too_many_positional(PyThreadState *tstate, PyCodeObject *co,
16171618
kwonly_sig = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
16181619
assert(kwonly_sig != NULL);
16191620
}
1621+
if (suggest_missing_self) {
1622+
self_hint = PyUnicode_FromString(
1623+
". Did you forget to declare 'self' as the first parameter?");
1624+
if (self_hint == NULL) {
1625+
Py_DECREF(sig);
1626+
Py_DECREF(kwonly_sig);
1627+
return;
1628+
}
1629+
}
16201630
_PyErr_Format(tstate, PyExc_TypeError,
1621-
"%U() takes %U positional argument%s but %zd%U %s given",
1631+
"%U() takes %U positional argument%s but %zd%U %s given%U",
16221632
qualname,
16231633
sig,
16241634
plural ? "s" : "",
16251635
given,
16261636
kwonly_sig,
1627-
given == 1 && !kwonly_given ? "was" : "were");
1637+
given == 1 && !kwonly_given ? "was" : "were",
1638+
self_hint
1639+
);
1640+
Py_DECREF(self_hint);
16281641
Py_DECREF(sig);
16291642
Py_DECREF(kwonly_sig);
16301643
}
16311644

1645+
static int
1646+
suggest_missing_self(PyFunctionObject *func, PyCodeObject *co,
1647+
_PyStackRef const *args, Py_ssize_t argcount)
1648+
{
1649+
if (co->co_argcount != 0 || argcount == 0) {
1650+
return 0;
1651+
}
1652+
1653+
PyObject *self = PyStackRef_AsPyObjectBorrow(args[0]);
1654+
if (self == NULL) {
1655+
return 0;
1656+
}
1657+
1658+
Py_ssize_t qualname_len;
1659+
const char *qualname = PyUnicode_AsUTF8AndSize(
1660+
func->func_qualname, &qualname_len);
1661+
if (qualname == NULL) {
1662+
PyErr_Clear();
1663+
return 0;
1664+
}
1665+
1666+
const char *method_dot = strrchr(qualname, '.');
1667+
if (method_dot == NULL) {
1668+
return 0;
1669+
}
1670+
1671+
const char *class_start = qualname;
1672+
for (const char *p = qualname; p < method_dot; p++) {
1673+
if (*p == '.') {
1674+
class_start = p + 1;
1675+
}
1676+
}
1677+
Py_ssize_t class_len = method_dot - class_start;
1678+
const char *type_name = Py_TYPE(self)->tp_name;
1679+
1680+
return (strlen(type_name) == (size_t)class_len
1681+
&& strncmp(type_name, class_start, (size_t)class_len) == 0);
1682+
}
1683+
16321684
static int
16331685
positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
16341686
Py_ssize_t kwcount, PyObject* kwnames,
@@ -1721,6 +1773,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
17211773

17221774
/* Copy all positional arguments into local variables */
17231775
Py_ssize_t j, n;
1776+
int missing_self_hint = suggest_missing_self(func, co, args, argcount);
17241777
if (argcount > co->co_argcount) {
17251778
n = co->co_argcount;
17261779
}
@@ -1864,7 +1917,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
18641917
/* Check the number of positional arguments */
18651918
if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
18661919
too_many_positional(tstate, co, argcount, func->func_defaults, localsplus,
1867-
func->func_qualname);
1920+
func->func_qualname, missing_self_hint);
18681921
goto fail_post_args;
18691922
}
18701923

0 commit comments

Comments
 (0)