@@ -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,71 @@ 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 >= argcount ) {
1650+ // When declared count is more than provided, there is nothing to add
1651+ return 0 ;
1652+ }
1653+
1654+ PyObject * self = PyStackRef_AsPyObjectBorrow (args [0 ]);
1655+ if (self == NULL ) {
1656+ // When first arg is NULL, its not really about self
1657+ return 0 ;
1658+ }
1659+
1660+ Py_ssize_t qualname_len ;
1661+ const char * qualname = PyUnicode_AsUTF8AndSize (
1662+ func -> func_qualname , & qualname_len );
1663+ if (qualname == NULL ) {
1664+ PyErr_Clear ();
1665+ return 0 ;
1666+ }
1667+
1668+ const char * method_dot = strrchr (qualname , '.' );
1669+ if (method_dot == NULL ) {
1670+ return 0 ;
1671+ }
1672+
1673+ const char * class_start = qualname ;
1674+ for (const char * p = qualname ; p < method_dot ; p ++ ) {
1675+ if (* p == '.' ) {
1676+ class_start = p + 1 ;
1677+ }
1678+ }
1679+ Py_ssize_t class_len = method_dot - class_start ;
1680+ const char * type_name = Py_TYPE (self )-> tp_name ;
1681+
1682+ return (strlen (type_name ) == (size_t )class_len
1683+ && strncmp (type_name , class_start , (size_t )class_len ) == 0 );
1684+ }
1685+
16321686static int
16331687positional_only_passed_as_keyword (PyThreadState * tstate , PyCodeObject * co ,
16341688 Py_ssize_t kwcount , PyObject * kwnames ,
@@ -1721,6 +1775,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
17211775
17221776 /* Copy all positional arguments into local variables */
17231777 Py_ssize_t j , n ;
1778+ int missing_self_hint = suggest_missing_self (func , co , args , argcount );
17241779 if (argcount > co -> co_argcount ) {
17251780 n = co -> co_argcount ;
17261781 }
@@ -1864,7 +1919,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
18641919 /* Check the number of positional arguments */
18651920 if ((argcount > co -> co_argcount ) && !(co -> co_flags & CO_VARARGS )) {
18661921 too_many_positional (tstate , co , argcount , func -> func_defaults , localsplus ,
1867- func -> func_qualname );
1922+ func -> func_qualname , missing_self_hint );
18681923 goto fail_post_args ;
18691924 }
18701925
0 commit comments