@@ -1574,12 +1574,13 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
15741574static void
15751575too_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+
16321684static int
16331685positional_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