diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index 0c2d470104d0..ecf8c37f83c9 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -375,7 +375,7 @@ def emit_line() -> None: flags.append("Py_TPFLAGS_MANAGED_DICT") fields["tp_flags"] = " | ".join(flags) - fields["tp_doc"] = native_class_doc_initializer(cl) + fields["tp_doc"] = f"PyDoc_STR({native_class_doc_initializer(cl)})" emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{") emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)") @@ -925,7 +925,7 @@ def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None: flags.append("METH_CLASS") doc = native_function_doc_initializer(fn) - emitter.emit_line(" {}, {}}},".format(" | ".join(flags), doc)) + emitter.emit_line(" {}, PyDoc_STR({})}},".format(" | ".join(flags), doc)) # Provide a default __getstate__ and __setstate__ if not cl.has_method("__setstate__") and not cl.has_method("__getstate__"): diff --git a/mypyc/codegen/emitmodule.py b/mypyc/codegen/emitmodule.py index de34ed9fc7da..1e49b1320b26 100644 --- a/mypyc/codegen/emitmodule.py +++ b/mypyc/codegen/emitmodule.py @@ -983,7 +983,7 @@ def emit_module_methods( emitter.emit_line( ( '{{"{name}", (PyCFunction){prefix}{cname}, {flag} | METH_KEYWORDS, ' - "{doc} /* docstring */}}," + "PyDoc_STR({doc}) /* docstring */}}," ).format( name=name, cname=fn.cname(emitter.names), prefix=PREFIX, flag=flag, doc=doc ) diff --git a/mypyc/lib-rt/misc_ops.c b/mypyc/lib-rt/misc_ops.c index 3787ea553037..0c9d7812ac6c 100644 --- a/mypyc/lib-rt/misc_ops.c +++ b/mypyc/lib-rt/misc_ops.c @@ -300,6 +300,21 @@ PyObject *CPyType_FromTemplate(PyObject *template, Py_XDECREF(dummy_class); + // Unlike the tp_doc slots of most other object, a heap type's tp_doc + // must be heap allocated. + if (template_->tp_doc) { + // Silently truncate the docstring if it contains a null byte + Py_ssize_t size = strlen(template_->tp_doc) + 1; + char *tp_doc = (char *)PyMem_Malloc(size); + if (tp_doc == NULL) { + PyErr_NoMemory(); + goto error; + } + + memcpy(tp_doc, template_->tp_doc, size); + t->ht_type.tp_doc = tp_doc; + } + #if PY_MINOR_VERSION == 11 // This is a hack. Python 3.11 doesn't include good public APIs to work with managed // dicts, which are the default for heap types. So we try to opt-out until Python 3.12. diff --git a/mypyc/test-data/run-signatures.test b/mypyc/test-data/run-signatures.test index a2de7076f5ef..0a9ea32f5357 100644 --- a/mypyc/test-data/run-signatures.test +++ b/mypyc/test-data/run-signatures.test @@ -184,6 +184,22 @@ for cls in [Empty, HasInit, InheritedInit]: assert getattr(cls, "__doc__") == "" assert getattr(HasInitBad, "__doc__") is None +[case testSignaturesConstructorsNonExt] +from mypy_extensions import mypyc_attr + +@mypyc_attr(native_class=False) +class NonExt: + def __init__(self, x) -> None: pass + +[file driver.py] +import inspect +from testutil import assertRaises +from native import * + +# TODO: support constructor signatures for non-extension classes +with assertRaises(ValueError, "no signature found for builtin"): + inspect.signature(NonExt) + [case testSignaturesHistoricalPositionalOnly] import inspect