[Python-checkins] gh-84805: Autogenerate signature for METH_NOARGS and METH_O extension functions (GH-107794)

serhiy-storchaka webhook-mailer at python.org
Fri Aug 11 11:08:42 EDT 2023


https://github.com/python/cpython/commit/3901c991e169da6fba8c0033a86a6f2e6146bb7f
commit: 3901c991e169da6fba8c0033a86a6f2e6146bb7f
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2023-08-11T18:08:38+03:00
summary:

gh-84805: Autogenerate signature for METH_NOARGS and METH_O extension functions (GH-107794)

files:
A Misc/NEWS.d/next/Core and Builtins/2023-08-09-08-31-20.gh-issue-84805.7JRWua.rst
M Include/internal/pycore_object.h
M Lib/test/test_inspect.py
M Lib/test/test_pydoc.py
M Lib/test/test_rlcompleter.py
M Modules/_testcapi/docstring.c
M Objects/descrobject.c
M Objects/methodobject.c
M Objects/typeobject.c
M Tools/c-analyzer/cpython/ignored.tsv

diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 7cdf64bcdacb9..857d6efec3b3b 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -381,7 +381,7 @@ extern PyObject *_PyType_NewManagedObject(PyTypeObject *type);
 
 extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *);
 extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
-extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *);
+extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
 
 extern int _PyObject_InitializeDict(PyObject *obj);
 int _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 5c31748ce2caa..07c48eac5b48b 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -13,7 +13,9 @@
 import _pickle
 import pickle
 import shutil
+import stat
 import sys
+import time
 import types
 import tempfile
 import textwrap
@@ -22,6 +24,7 @@
 import unittest.mock
 import warnings
 
+
 try:
     from concurrent.futures import ThreadPoolExecutor
 except ImportError:
@@ -136,6 +139,14 @@ def gen_coroutine_function_example(self):
     yield
     return 'spam'
 
+def meth_noargs(): pass
+def meth_o(object, /): pass
+def meth_self_noargs(self, /): pass
+def meth_self_o(self, object, /): pass
+def meth_type_noargs(type, /): pass
+def meth_type_o(type, object, /): pass
+
+
 class TestPredicates(IsTestBase):
 
     def test_excluding_predicates(self):
@@ -1173,6 +1184,39 @@ def test_getfullargspec_builtin_func_no_signature(self):
         with self.assertRaises(TypeError):
             inspect.getfullargspec(builtin)
 
+        cls = _testcapi.DocStringNoSignatureTest
+        obj = _testcapi.DocStringNoSignatureTest()
+        for builtin, template in [
+            (_testcapi.docstring_no_signature_noargs, meth_noargs),
+            (_testcapi.docstring_no_signature_o, meth_o),
+            (cls.meth_noargs, meth_self_noargs),
+            (cls.meth_o, meth_self_o),
+            (obj.meth_noargs, meth_self_noargs),
+            (obj.meth_o, meth_self_o),
+            (cls.meth_noargs_class, meth_type_noargs),
+            (cls.meth_o_class, meth_type_o),
+            (cls.meth_noargs_static, meth_noargs),
+            (cls.meth_o_static, meth_o),
+            (cls.meth_noargs_coexist, meth_self_noargs),
+            (cls.meth_o_coexist, meth_self_o),
+
+            (time.time, meth_noargs),
+            (stat.S_IMODE, meth_o),
+            (str.lower, meth_self_noargs),
+            (''.lower, meth_self_noargs),
+            (set.add, meth_self_o),
+            (set().add, meth_self_o),
+            (set.__contains__, meth_self_o),
+            (set().__contains__, meth_self_o),
+            (datetime.datetime.__dict__['utcnow'], meth_type_noargs),
+            (datetime.datetime.utcnow, meth_type_noargs),
+            (dict.__dict__['__class_getitem__'], meth_type_o),
+            (dict.__class_getitem__, meth_type_o),
+        ]:
+            with self.subTest(builtin):
+                self.assertEqual(inspect.getfullargspec(builtin),
+                                 inspect.getfullargspec(template))
+
     def test_getfullargspec_definition_order_preserved_on_kwonly(self):
         for fn in signatures_with_lexicographic_keyword_only_parameters():
             signature = inspect.getfullargspec(fn)
@@ -2888,6 +2932,39 @@ def test_signature_on_builtins_no_signature(self):
                                     'no signature found for builtin'):
             inspect.signature(str)
 
+        cls = _testcapi.DocStringNoSignatureTest
+        obj = _testcapi.DocStringNoSignatureTest()
+        for builtin, template in [
+            (_testcapi.docstring_no_signature_noargs, meth_noargs),
+            (_testcapi.docstring_no_signature_o, meth_o),
+            (cls.meth_noargs, meth_self_noargs),
+            (cls.meth_o, meth_self_o),
+            (obj.meth_noargs, meth_noargs),
+            (obj.meth_o, meth_o),
+            (cls.meth_noargs_class, meth_noargs),
+            (cls.meth_o_class, meth_o),
+            (cls.meth_noargs_static, meth_noargs),
+            (cls.meth_o_static, meth_o),
+            (cls.meth_noargs_coexist, meth_self_noargs),
+            (cls.meth_o_coexist, meth_self_o),
+
+            (time.time, meth_noargs),
+            (stat.S_IMODE, meth_o),
+            (str.lower, meth_self_noargs),
+            (''.lower, meth_noargs),
+            (set.add, meth_self_o),
+            (set().add, meth_o),
+            (set.__contains__, meth_self_o),
+            (set().__contains__, meth_o),
+            (datetime.datetime.__dict__['utcnow'], meth_type_noargs),
+            (datetime.datetime.utcnow, meth_noargs),
+            (dict.__dict__['__class_getitem__'], meth_type_o),
+            (dict.__class_getitem__, meth_o),
+        ]:
+            with self.subTest(builtin):
+                self.assertEqual(inspect.signature(builtin),
+                                 inspect.signature(template))
+
     def test_signature_on_non_function(self):
         with self.assertRaisesRegex(TypeError, 'is not a callable object'):
             inspect.signature(42)
diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py
index ddb5187f90da9..8df8b608cf959 100644
--- a/Lib/test/test_pydoc.py
+++ b/Lib/test/test_pydoc.py
@@ -1,3 +1,4 @@
+import datetime
 import os
 import sys
 import contextlib
@@ -12,6 +13,7 @@
 import stat
 import tempfile
 import test.support
+import time
 import types
 import typing
 import unittest
@@ -1180,6 +1182,54 @@ def test_module_level_callable(self):
         self.assertEqual(self._get_summary_line(os.stat),
             "stat(path, *, dir_fd=None, follow_symlinks=True)")
 
+    def test_module_level_callable_noargs(self):
+        self.assertEqual(self._get_summary_line(time.time),
+            "time()")
+
+    def test_module_level_callable_o(self):
+        self.assertEqual(self._get_summary_line(stat.S_IMODE),
+            "S_IMODE(object, /)")
+
+    def test_unbound_builtin_method_noargs(self):
+        self.assertEqual(self._get_summary_line(str.lower),
+            "lower(self, /)")
+
+    def test_bound_builtin_method_noargs(self):
+        self.assertEqual(self._get_summary_line(''.lower),
+            "lower() method of builtins.str instance")
+
+    def test_unbound_builtin_method_o(self):
+        self.assertEqual(self._get_summary_line(set.add),
+            "add(self, object, /)")
+
+    def test_bound_builtin_method_o(self):
+        self.assertEqual(self._get_summary_line(set().add),
+            "add(object, /) method of builtins.set instance")
+
+    def test_unbound_builtin_method_coexist_o(self):
+        self.assertEqual(self._get_summary_line(set.__contains__),
+            "__contains__(self, object, /)")
+
+    def test_bound_builtin_method_coexist_o(self):
+        self.assertEqual(self._get_summary_line(set().__contains__),
+            "__contains__(object, /) method of builtins.set instance")
+
+    def test_unbound_builtin_classmethod_noargs(self):
+        self.assertEqual(self._get_summary_line(datetime.datetime.__dict__['utcnow']),
+            "utcnow(type, /)")
+
+    def test_bound_builtin_classmethod_noargs(self):
+        self.assertEqual(self._get_summary_line(datetime.datetime.utcnow),
+            "utcnow() method of builtins.type instance")
+
+    def test_unbound_builtin_classmethod_o(self):
+        self.assertEqual(self._get_summary_line(dict.__dict__['__class_getitem__']),
+            "__class_getitem__(type, object, /)")
+
+    def test_bound_builtin_classmethod_o(self):
+        self.assertEqual(self._get_summary_line(dict.__class_getitem__),
+            "__class_getitem__(object, /) method of builtins.type instance")
+
     @requires_docstrings
     def test_staticmethod(self):
         class X:
diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py
index 6b5fc9a0247f4..7347fca71be2f 100644
--- a/Lib/test/test_rlcompleter.py
+++ b/Lib/test/test_rlcompleter.py
@@ -53,7 +53,10 @@ def test_attr_matches(self):
                          ['str.{}('.format(x) for x in dir(str)
                           if x.startswith('s')])
         self.assertEqual(self.stdcompleter.attr_matches('tuple.foospamegg'), [])
-        expected = sorted({'None.%s%s' % (x, '(' if x != '__doc__' else '')
+        expected = sorted({'None.%s%s' % (x,
+                                          '()' if x == '__init_subclass__'
+                                          else '' if x == '__doc__'
+                                          else '(')
                            for x in dir(None)})
         self.assertEqual(self.stdcompleter.attr_matches('None.'), expected)
         self.assertEqual(self.stdcompleter.attr_matches('None._'), expected)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-08-09-08-31-20.gh-issue-84805.7JRWua.rst b/Misc/NEWS.d/next/Core and Builtins/2023-08-09-08-31-20.gh-issue-84805.7JRWua.rst
new file mode 100644
index 0000000000000..23dfba989fa55
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-08-09-08-31-20.gh-issue-84805.7JRWua.rst	
@@ -0,0 +1,2 @@
+Autogenerate signature for :c:macro:`METH_NOARGS` and :c:macro:`METH_O`
+extension functions.
diff --git a/Modules/_testcapi/docstring.c b/Modules/_testcapi/docstring.c
index a997c54a8a69c..b680171cc1437 100644
--- a/Modules/_testcapi/docstring.c
+++ b/Modules/_testcapi/docstring.c
@@ -66,42 +66,88 @@ test_with_docstring(PyObject *self, PyObject *Py_UNUSED(ignored))
 
 static PyMethodDef test_methods[] = {
     {"docstring_empty",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_empty},
     {"docstring_no_signature",
+        (PyCFunction)test_with_docstring, METH_VARARGS,
+        docstring_no_signature},
+    {"docstring_no_signature_noargs",
         (PyCFunction)test_with_docstring, METH_NOARGS,
         docstring_no_signature},
+    {"docstring_no_signature_o",
+        (PyCFunction)test_with_docstring, METH_O,
+        docstring_no_signature},
     {"docstring_with_invalid_signature",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_invalid_signature},
     {"docstring_with_invalid_signature2",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_invalid_signature2},
     {"docstring_with_signature",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_signature},
     {"docstring_with_signature_and_extra_newlines",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_signature_and_extra_newlines},
     {"docstring_with_signature_but_no_doc",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_signature_but_no_doc},
     {"docstring_with_signature_with_defaults",
-        (PyCFunction)test_with_docstring, METH_NOARGS,
+        (PyCFunction)test_with_docstring, METH_VARARGS,
         docstring_with_signature_with_defaults},
     {"no_docstring",
-        (PyCFunction)test_with_docstring, METH_NOARGS},
+        (PyCFunction)test_with_docstring, METH_VARARGS},
     {"test_with_docstring",
-        test_with_docstring,              METH_NOARGS,
+        test_with_docstring,              METH_VARARGS,
         PyDoc_STR("This is a pretty normal docstring.")},
     {NULL},
 };
 
+static PyMethodDef DocStringNoSignatureTest_methods[] = {
+    {"meth_noargs",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        docstring_no_signature},
+    {"meth_o",
+        (PyCFunction)test_with_docstring, METH_O,
+        docstring_no_signature},
+    {"meth_noargs_class",
+        (PyCFunction)test_with_docstring, METH_NOARGS|METH_CLASS,
+        docstring_no_signature},
+    {"meth_o_class",
+        (PyCFunction)test_with_docstring, METH_O|METH_CLASS,
+        docstring_no_signature},
+    {"meth_noargs_static",
+        (PyCFunction)test_with_docstring, METH_NOARGS|METH_STATIC,
+        docstring_no_signature},
+    {"meth_o_static",
+        (PyCFunction)test_with_docstring, METH_O|METH_STATIC,
+        docstring_no_signature},
+    {"meth_noargs_coexist",
+        (PyCFunction)test_with_docstring, METH_NOARGS|METH_COEXIST,
+        docstring_no_signature},
+    {"meth_o_coexist",
+        (PyCFunction)test_with_docstring, METH_O|METH_COEXIST,
+        docstring_no_signature},
+    {NULL},
+};
+
+static PyTypeObject DocStringNoSignatureTest = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    .tp_name = "_testcapi.DocStringNoSignatureTest",
+    .tp_basicsize = sizeof(PyObject),
+    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_methods = DocStringNoSignatureTest_methods,
+    .tp_new = PyType_GenericNew,
+};
+
 int
 _PyTestCapi_Init_Docstring(PyObject *mod)
 {
     if (PyModule_AddFunctions(mod, test_methods) < 0) {
         return -1;
     }
+    if (PyModule_AddType(mod, &DocStringNoSignatureTest) < 0) {
+        return -1;
+    }
     return 0;
 }
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index 60383dd06d1ad..a744c3d1e5865 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -588,7 +588,9 @@ method_get_doc(PyMethodDescrObject *descr, void *closure)
 static PyObject *
 method_get_text_signature(PyMethodDescrObject *descr, void *closure)
 {
-    return _PyType_GetTextSignatureFromInternalDoc(descr->d_method->ml_name, descr->d_method->ml_doc);
+    return _PyType_GetTextSignatureFromInternalDoc(descr->d_method->ml_name,
+                                                   descr->d_method->ml_doc,
+                                                   descr->d_method->ml_flags);
 }
 
 static PyObject *
@@ -691,7 +693,8 @@ wrapperdescr_get_doc(PyWrapperDescrObject *descr, void *closure)
 static PyObject *
 wrapperdescr_get_text_signature(PyWrapperDescrObject *descr, void *closure)
 {
-    return _PyType_GetTextSignatureFromInternalDoc(descr->d_base->name, descr->d_base->doc);
+    return _PyType_GetTextSignatureFromInternalDoc(descr->d_base->name,
+                                                   descr->d_base->doc, 0);
 }
 
 static PyGetSetDef wrapperdescr_getset[] = {
@@ -1384,7 +1387,8 @@ wrapper_doc(wrapperobject *wp, void *Py_UNUSED(ignored))
 static PyObject *
 wrapper_text_signature(wrapperobject *wp, void *Py_UNUSED(ignored))
 {
-    return _PyType_GetTextSignatureFromInternalDoc(wp->descr->d_base->name, wp->descr->d_base->doc);
+    return _PyType_GetTextSignatureFromInternalDoc(wp->descr->d_base->name,
+                                                   wp->descr->d_base->doc, 0);
 }
 
 static PyObject *
diff --git a/Objects/methodobject.c b/Objects/methodobject.c
index 628d227ef33c3..521c9059770ac 100644
--- a/Objects/methodobject.c
+++ b/Objects/methodobject.c
@@ -192,7 +192,9 @@ static PyMethodDef meth_methods[] = {
 static PyObject *
 meth_get__text_signature__(PyCFunctionObject *m, void *closure)
 {
-    return _PyType_GetTextSignatureFromInternalDoc(m->m_ml->ml_name, m->m_ml->ml_doc);
+    return _PyType_GetTextSignatureFromInternalDoc(m->m_ml->ml_name,
+                                                   m->m_ml->ml_doc,
+                                                   m->m_ml->ml_flags);
 }
 
 static PyObject *
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index aca14e79a1997..030e8bfc99b6d 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -586,8 +586,29 @@ _PyType_GetDocFromInternalDoc(const char *name, const char *internal_doc)
     return PyUnicode_FromString(doc);
 }
 
+static const char *
+signature_from_flags(int flags)
+{
+    switch (flags & ~METH_COEXIST) {
+        case METH_NOARGS:
+            return "($self, /)";
+        case METH_NOARGS|METH_CLASS:
+            return "($type, /)";
+        case METH_NOARGS|METH_STATIC:
+            return "()";
+        case METH_O:
+            return "($self, object, /)";
+        case METH_O|METH_CLASS:
+            return "($type, object, /)";
+        case METH_O|METH_STATIC:
+            return "(object, /)";
+        default:
+            return NULL;
+    }
+}
+
 PyObject *
-_PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_doc)
+_PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_doc, int flags)
 {
     const char *start = find_signature(name, internal_doc);
     const char *end;
@@ -597,6 +618,10 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d
     else
         end = NULL;
     if (!end) {
+        start = signature_from_flags(flags);
+        if (start) {
+            return PyUnicode_FromString(start);
+        }
         Py_RETURN_NONE;
     }
 
@@ -1429,7 +1454,7 @@ type_get_doc(PyTypeObject *type, void *context)
 static PyObject *
 type_get_text_signature(PyTypeObject *type, void *context)
 {
-    return _PyType_GetTextSignatureFromInternalDoc(type->tp_name, type->tp_doc);
+    return _PyType_GetTextSignatureFromInternalDoc(type->tp_name, type->tp_doc, 0);
 }
 
 static int
diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv
index 66815c72ffbc6..c64d391bae13b 100644
--- a/Tools/c-analyzer/cpython/ignored.tsv
+++ b/Tools/c-analyzer/cpython/ignored.tsv
@@ -419,6 +419,7 @@ Modules/_testbuffer.c	staticarray_init	kwlist	-
 Modules/_testcapi/buffer.c	-	testBufType	-
 Modules/_testcapi/code.c	get_code_extra_index	key	-
 Modules/_testcapi/datetime.c	-	test_run_counter	-
+Modules/_testcapi/docstring.c	-	DocStringNoSignatureTest	-
 Modules/_testcapi/exceptions.c	-	PyRecursingInfinitelyError_Type	-
 Modules/_testcapi/heaptype.c	-	_testcapimodule	-
 Modules/_testcapi/mem.c	-	FmData	-



More information about the Python-checkins mailing list