Skip to content

Commit 3901c99

Browse files
gh-84805: Autogenerate signature for METH_NOARGS and METH_O extension functions (GH-107794)
1 parent 23a6db9 commit 3901c99

File tree

10 files changed

+227
-17
lines changed

10 files changed

+227
-17
lines changed

Include/internal/pycore_object.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ extern PyObject *_PyType_NewManagedObject(PyTypeObject *type);
381381

382382
extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *);
383383
extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
384-
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *);
384+
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
385385

386386
extern int _PyObject_InitializeDict(PyObject *obj);
387387
int _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);

Lib/test/test_inspect.py

+77
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import _pickle
1414
import pickle
1515
import shutil
16+
import stat
1617
import sys
18+
import time
1719
import types
1820
import tempfile
1921
import textwrap
@@ -22,6 +24,7 @@
2224
import unittest.mock
2325
import warnings
2426

27+
2528
try:
2629
from concurrent.futures import ThreadPoolExecutor
2730
except ImportError:
@@ -136,6 +139,14 @@ def gen_coroutine_function_example(self):
136139
yield
137140
return 'spam'
138141

142+
def meth_noargs(): pass
143+
def meth_o(object, /): pass
144+
def meth_self_noargs(self, /): pass
145+
def meth_self_o(self, object, /): pass
146+
def meth_type_noargs(type, /): pass
147+
def meth_type_o(type, object, /): pass
148+
149+
139150
class TestPredicates(IsTestBase):
140151

141152
def test_excluding_predicates(self):
@@ -1173,6 +1184,39 @@ def test_getfullargspec_builtin_func_no_signature(self):
11731184
with self.assertRaises(TypeError):
11741185
inspect.getfullargspec(builtin)
11751186

1187+
cls = _testcapi.DocStringNoSignatureTest
1188+
obj = _testcapi.DocStringNoSignatureTest()
1189+
for builtin, template in [
1190+
(_testcapi.docstring_no_signature_noargs, meth_noargs),
1191+
(_testcapi.docstring_no_signature_o, meth_o),
1192+
(cls.meth_noargs, meth_self_noargs),
1193+
(cls.meth_o, meth_self_o),
1194+
(obj.meth_noargs, meth_self_noargs),
1195+
(obj.meth_o, meth_self_o),
1196+
(cls.meth_noargs_class, meth_type_noargs),
1197+
(cls.meth_o_class, meth_type_o),
1198+
(cls.meth_noargs_static, meth_noargs),
1199+
(cls.meth_o_static, meth_o),
1200+
(cls.meth_noargs_coexist, meth_self_noargs),
1201+
(cls.meth_o_coexist, meth_self_o),
1202+
1203+
(time.time, meth_noargs),
1204+
(stat.S_IMODE, meth_o),
1205+
(str.lower, meth_self_noargs),
1206+
(''.lower, meth_self_noargs),
1207+
(set.add, meth_self_o),
1208+
(set().add, meth_self_o),
1209+
(set.__contains__, meth_self_o),
1210+
(set().__contains__, meth_self_o),
1211+
(datetime.datetime.__dict__['utcnow'], meth_type_noargs),
1212+
(datetime.datetime.utcnow, meth_type_noargs),
1213+
(dict.__dict__['__class_getitem__'], meth_type_o),
1214+
(dict.__class_getitem__, meth_type_o),
1215+
]:
1216+
with self.subTest(builtin):
1217+
self.assertEqual(inspect.getfullargspec(builtin),
1218+
inspect.getfullargspec(template))
1219+
11761220
def test_getfullargspec_definition_order_preserved_on_kwonly(self):
11771221
for fn in signatures_with_lexicographic_keyword_only_parameters():
11781222
signature = inspect.getfullargspec(fn)
@@ -2888,6 +2932,39 @@ def test_signature_on_builtins_no_signature(self):
28882932
'no signature found for builtin'):
28892933
inspect.signature(str)
28902934

2935+
cls = _testcapi.DocStringNoSignatureTest
2936+
obj = _testcapi.DocStringNoSignatureTest()
2937+
for builtin, template in [
2938+
(_testcapi.docstring_no_signature_noargs, meth_noargs),
2939+
(_testcapi.docstring_no_signature_o, meth_o),
2940+
(cls.meth_noargs, meth_self_noargs),
2941+
(cls.meth_o, meth_self_o),
2942+
(obj.meth_noargs, meth_noargs),
2943+
(obj.meth_o, meth_o),
2944+
(cls.meth_noargs_class, meth_noargs),
2945+
(cls.meth_o_class, meth_o),
2946+
(cls.meth_noargs_static, meth_noargs),
2947+
(cls.meth_o_static, meth_o),
2948+
(cls.meth_noargs_coexist, meth_self_noargs),
2949+
(cls.meth_o_coexist, meth_self_o),
2950+
2951+
(time.time, meth_noargs),
2952+
(stat.S_IMODE, meth_o),
2953+
(str.lower, meth_self_noargs),
2954+
(''.lower, meth_noargs),
2955+
(set.add, meth_self_o),
2956+
(set().add, meth_o),
2957+
(set.__contains__, meth_self_o),
2958+
(set().__contains__, meth_o),
2959+
(datetime.datetime.__dict__['utcnow'], meth_type_noargs),
2960+
(datetime.datetime.utcnow, meth_noargs),
2961+
(dict.__dict__['__class_getitem__'], meth_type_o),
2962+
(dict.__class_getitem__, meth_o),
2963+
]:
2964+
with self.subTest(builtin):
2965+
self.assertEqual(inspect.signature(builtin),
2966+
inspect.signature(template))
2967+
28912968
def test_signature_on_non_function(self):
28922969
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
28932970
inspect.signature(42)

Lib/test/test_pydoc.py

+50
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import os
23
import sys
34
import contextlib
@@ -12,6 +13,7 @@
1213
import stat
1314
import tempfile
1415
import test.support
16+
import time
1517
import types
1618
import typing
1719
import unittest
@@ -1180,6 +1182,54 @@ def test_module_level_callable(self):
11801182
self.assertEqual(self._get_summary_line(os.stat),
11811183
"stat(path, *, dir_fd=None, follow_symlinks=True)")
11821184

1185+
def test_module_level_callable_noargs(self):
1186+
self.assertEqual(self._get_summary_line(time.time),
1187+
"time()")
1188+
1189+
def test_module_level_callable_o(self):
1190+
self.assertEqual(self._get_summary_line(stat.S_IMODE),
1191+
"S_IMODE(object, /)")
1192+
1193+
def test_unbound_builtin_method_noargs(self):
1194+
self.assertEqual(self._get_summary_line(str.lower),
1195+
"lower(self, /)")
1196+
1197+
def test_bound_builtin_method_noargs(self):
1198+
self.assertEqual(self._get_summary_line(''.lower),
1199+
"lower() method of builtins.str instance")
1200+
1201+
def test_unbound_builtin_method_o(self):
1202+
self.assertEqual(self._get_summary_line(set.add),
1203+
"add(self, object, /)")
1204+
1205+
def test_bound_builtin_method_o(self):
1206+
self.assertEqual(self._get_summary_line(set().add),
1207+
"add(object, /) method of builtins.set instance")
1208+
1209+
def test_unbound_builtin_method_coexist_o(self):
1210+
self.assertEqual(self._get_summary_line(set.__contains__),
1211+
"__contains__(self, object, /)")
1212+
1213+
def test_bound_builtin_method_coexist_o(self):
1214+
self.assertEqual(self._get_summary_line(set().__contains__),
1215+
"__contains__(object, /) method of builtins.set instance")
1216+
1217+
def test_unbound_builtin_classmethod_noargs(self):
1218+
self.assertEqual(self._get_summary_line(datetime.datetime.__dict__['utcnow']),
1219+
"utcnow(type, /)")
1220+
1221+
def test_bound_builtin_classmethod_noargs(self):
1222+
self.assertEqual(self._get_summary_line(datetime.datetime.utcnow),
1223+
"utcnow() method of builtins.type instance")
1224+
1225+
def test_unbound_builtin_classmethod_o(self):
1226+
self.assertEqual(self._get_summary_line(dict.__dict__['__class_getitem__']),
1227+
"__class_getitem__(type, object, /)")
1228+
1229+
def test_bound_builtin_classmethod_o(self):
1230+
self.assertEqual(self._get_summary_line(dict.__class_getitem__),
1231+
"__class_getitem__(object, /) method of builtins.type instance")
1232+
11831233
@requires_docstrings
11841234
def test_staticmethod(self):
11851235
class X:

Lib/test/test_rlcompleter.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ def test_attr_matches(self):
5353
['str.{}('.format(x) for x in dir(str)
5454
if x.startswith('s')])
5555
self.assertEqual(self.stdcompleter.attr_matches('tuple.foospamegg'), [])
56-
expected = sorted({'None.%s%s' % (x, '(' if x != '__doc__' else '')
56+
expected = sorted({'None.%s%s' % (x,
57+
'()' if x == '__init_subclass__'
58+
else '' if x == '__doc__'
59+
else '(')
5760
for x in dir(None)})
5861
self.assertEqual(self.stdcompleter.attr_matches('None.'), expected)
5962
self.assertEqual(self.stdcompleter.attr_matches('None._'), expected)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Autogenerate signature for :c:macro:`METH_NOARGS` and :c:macro:`METH_O`
2+
extension functions.

Modules/_testcapi/docstring.c

+55-9
Original file line numberDiff line numberDiff line change
@@ -66,42 +66,88 @@ test_with_docstring(PyObject *self, PyObject *Py_UNUSED(ignored))
6666

6767
static PyMethodDef test_methods[] = {
6868
{"docstring_empty",
69-
(PyCFunction)test_with_docstring, METH_NOARGS,
69+
(PyCFunction)test_with_docstring, METH_VARARGS,
7070
docstring_empty},
7171
{"docstring_no_signature",
72+
(PyCFunction)test_with_docstring, METH_VARARGS,
73+
docstring_no_signature},
74+
{"docstring_no_signature_noargs",
7275
(PyCFunction)test_with_docstring, METH_NOARGS,
7376
docstring_no_signature},
77+
{"docstring_no_signature_o",
78+
(PyCFunction)test_with_docstring, METH_O,
79+
docstring_no_signature},
7480
{"docstring_with_invalid_signature",
75-
(PyCFunction)test_with_docstring, METH_NOARGS,
81+
(PyCFunction)test_with_docstring, METH_VARARGS,
7682
docstring_with_invalid_signature},
7783
{"docstring_with_invalid_signature2",
78-
(PyCFunction)test_with_docstring, METH_NOARGS,
84+
(PyCFunction)test_with_docstring, METH_VARARGS,
7985
docstring_with_invalid_signature2},
8086
{"docstring_with_signature",
81-
(PyCFunction)test_with_docstring, METH_NOARGS,
87+
(PyCFunction)test_with_docstring, METH_VARARGS,
8288
docstring_with_signature},
8389
{"docstring_with_signature_and_extra_newlines",
84-
(PyCFunction)test_with_docstring, METH_NOARGS,
90+
(PyCFunction)test_with_docstring, METH_VARARGS,
8591
docstring_with_signature_and_extra_newlines},
8692
{"docstring_with_signature_but_no_doc",
87-
(PyCFunction)test_with_docstring, METH_NOARGS,
93+
(PyCFunction)test_with_docstring, METH_VARARGS,
8894
docstring_with_signature_but_no_doc},
8995
{"docstring_with_signature_with_defaults",
90-
(PyCFunction)test_with_docstring, METH_NOARGS,
96+
(PyCFunction)test_with_docstring, METH_VARARGS,
9197
docstring_with_signature_with_defaults},
9298
{"no_docstring",
93-
(PyCFunction)test_with_docstring, METH_NOARGS},
99+
(PyCFunction)test_with_docstring, METH_VARARGS},
94100
{"test_with_docstring",
95-
test_with_docstring, METH_NOARGS,
101+
test_with_docstring, METH_VARARGS,
96102
PyDoc_STR("This is a pretty normal docstring.")},
97103
{NULL},
98104
};
99105

106+
static PyMethodDef DocStringNoSignatureTest_methods[] = {
107+
{"meth_noargs",
108+
(PyCFunction)test_with_docstring, METH_NOARGS,
109+
docstring_no_signature},
110+
{"meth_o",
111+
(PyCFunction)test_with_docstring, METH_O,
112+
docstring_no_signature},
113+
{"meth_noargs_class",
114+
(PyCFunction)test_with_docstring, METH_NOARGS|METH_CLASS,
115+
docstring_no_signature},
116+
{"meth_o_class",
117+
(PyCFunction)test_with_docstring, METH_O|METH_CLASS,
118+
docstring_no_signature},
119+
{"meth_noargs_static",
120+
(PyCFunction)test_with_docstring, METH_NOARGS|METH_STATIC,
121+
docstring_no_signature},
122+
{"meth_o_static",
123+
(PyCFunction)test_with_docstring, METH_O|METH_STATIC,
124+
docstring_no_signature},
125+
{"meth_noargs_coexist",
126+
(PyCFunction)test_with_docstring, METH_NOARGS|METH_COEXIST,
127+
docstring_no_signature},
128+
{"meth_o_coexist",
129+
(PyCFunction)test_with_docstring, METH_O|METH_COEXIST,
130+
docstring_no_signature},
131+
{NULL},
132+
};
133+
134+
static PyTypeObject DocStringNoSignatureTest = {
135+
PyVarObject_HEAD_INIT(NULL, 0)
136+
.tp_name = "_testcapi.DocStringNoSignatureTest",
137+
.tp_basicsize = sizeof(PyObject),
138+
.tp_flags = Py_TPFLAGS_DEFAULT,
139+
.tp_methods = DocStringNoSignatureTest_methods,
140+
.tp_new = PyType_GenericNew,
141+
};
142+
100143
int
101144
_PyTestCapi_Init_Docstring(PyObject *mod)
102145
{
103146
if (PyModule_AddFunctions(mod, test_methods) < 0) {
104147
return -1;
105148
}
149+
if (PyModule_AddType(mod, &DocStringNoSignatureTest) < 0) {
150+
return -1;
151+
}
106152
return 0;
107153
}

Objects/descrobject.c

+7-3
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,9 @@ method_get_doc(PyMethodDescrObject *descr, void *closure)
588588
static PyObject *
589589
method_get_text_signature(PyMethodDescrObject *descr, void *closure)
590590
{
591-
return _PyType_GetTextSignatureFromInternalDoc(descr->d_method->ml_name, descr->d_method->ml_doc);
591+
return _PyType_GetTextSignatureFromInternalDoc(descr->d_method->ml_name,
592+
descr->d_method->ml_doc,
593+
descr->d_method->ml_flags);
592594
}
593595

594596
static PyObject *
@@ -691,7 +693,8 @@ wrapperdescr_get_doc(PyWrapperDescrObject *descr, void *closure)
691693
static PyObject *
692694
wrapperdescr_get_text_signature(PyWrapperDescrObject *descr, void *closure)
693695
{
694-
return _PyType_GetTextSignatureFromInternalDoc(descr->d_base->name, descr->d_base->doc);
696+
return _PyType_GetTextSignatureFromInternalDoc(descr->d_base->name,
697+
descr->d_base->doc, 0);
695698
}
696699

697700
static PyGetSetDef wrapperdescr_getset[] = {
@@ -1384,7 +1387,8 @@ wrapper_doc(wrapperobject *wp, void *Py_UNUSED(ignored))
13841387
static PyObject *
13851388
wrapper_text_signature(wrapperobject *wp, void *Py_UNUSED(ignored))
13861389
{
1387-
return _PyType_GetTextSignatureFromInternalDoc(wp->descr->d_base->name, wp->descr->d_base->doc);
1390+
return _PyType_GetTextSignatureFromInternalDoc(wp->descr->d_base->name,
1391+
wp->descr->d_base->doc, 0);
13881392
}
13891393

13901394
static PyObject *

Objects/methodobject.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ static PyMethodDef meth_methods[] = {
192192
static PyObject *
193193
meth_get__text_signature__(PyCFunctionObject *m, void *closure)
194194
{
195-
return _PyType_GetTextSignatureFromInternalDoc(m->m_ml->ml_name, m->m_ml->ml_doc);
195+
return _PyType_GetTextSignatureFromInternalDoc(m->m_ml->ml_name,
196+
m->m_ml->ml_doc,
197+
m->m_ml->ml_flags);
196198
}
197199

198200
static PyObject *

Objects/typeobject.c

+27-2
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,29 @@ _PyType_GetDocFromInternalDoc(const char *name, const char *internal_doc)
586586
return PyUnicode_FromString(doc);
587587
}
588588

589+
static const char *
590+
signature_from_flags(int flags)
591+
{
592+
switch (flags & ~METH_COEXIST) {
593+
case METH_NOARGS:
594+
return "($self, /)";
595+
case METH_NOARGS|METH_CLASS:
596+
return "($type, /)";
597+
case METH_NOARGS|METH_STATIC:
598+
return "()";
599+
case METH_O:
600+
return "($self, object, /)";
601+
case METH_O|METH_CLASS:
602+
return "($type, object, /)";
603+
case METH_O|METH_STATIC:
604+
return "(object, /)";
605+
default:
606+
return NULL;
607+
}
608+
}
609+
589610
PyObject *
590-
_PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_doc)
611+
_PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_doc, int flags)
591612
{
592613
const char *start = find_signature(name, internal_doc);
593614
const char *end;
@@ -597,6 +618,10 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d
597618
else
598619
end = NULL;
599620
if (!end) {
621+
start = signature_from_flags(flags);
622+
if (start) {
623+
return PyUnicode_FromString(start);
624+
}
600625
Py_RETURN_NONE;
601626
}
602627

@@ -1429,7 +1454,7 @@ type_get_doc(PyTypeObject *type, void *context)
14291454
static PyObject *
14301455
type_get_text_signature(PyTypeObject *type, void *context)
14311456
{
1432-
return _PyType_GetTextSignatureFromInternalDoc(type->tp_name, type->tp_doc);
1457+
return _PyType_GetTextSignatureFromInternalDoc(type->tp_name, type->tp_doc, 0);
14331458
}
14341459

14351460
static int

0 commit comments

Comments
 (0)