Skip to content

gh-84805: Autogenerate signature for METH_NOARGS and METH_O extension functions #107794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
77 changes: 77 additions & 0 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import _pickle
import pickle
import shutil
import stat
import sys
import time
import types
import tempfile
import textwrap
Expand All @@ -22,6 +24,7 @@
import unittest.mock
import warnings


try:
from concurrent.futures import ThreadPoolExecutor
except ImportError:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions Lib/test/test_pydoc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import os
import sys
import contextlib
Expand All @@ -12,6 +13,7 @@
import stat
import tempfile
import test.support
import time
import types
import typing
import unittest
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_rlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Autogenerate signature for :c:macro:`METH_NOARGS` and :c:macro:`METH_O`
extension functions.
64 changes: 55 additions & 9 deletions Modules/_testcapi/docstring.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
10 changes: 7 additions & 3 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down Expand Up @@ -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[] = {
Expand Down Expand Up @@ -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 *
Expand Down
4 changes: 3 additions & 1 deletion Objects/methodobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
Expand Down
29 changes: 27 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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
Expand Down
Loading