From 8ef887ce47421e0e3e9ec0c95735990bb0594ffe Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Mon, 13 Apr 2015 18:22:35 -0500 Subject: [PATCH] Issue #20586: Argument Clinic now ensures signatures on functions without docstrings. --- Lib/test/test_capi.py | 6 +++- Lib/test/test_inspect.py | 3 ++ Misc/NEWS | 3 ++ Modules/_testcapimodule.c | 9 +++++ Modules/cjkcodecs/clinic/multibytecodec.c.h | 38 ++++++++++++++------- Modules/clinic/pyexpat.c.h | 5 +-- Objects/typeobject.c | 2 +- Tools/clinic/clinic.py | 13 +++++-- 8 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index dff717b5e05..367feaa0b89 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -123,7 +123,7 @@ class CAPITest(unittest.TestCase): self.assertEqual(_testcapi.no_docstring.__doc__, None) self.assertEqual(_testcapi.no_docstring.__text_signature__, None) - self.assertEqual(_testcapi.docstring_empty.__doc__, "") + self.assertEqual(_testcapi.docstring_empty.__doc__, None) self.assertEqual(_testcapi.docstring_empty.__text_signature__, None) self.assertEqual(_testcapi.docstring_no_signature.__doc__, @@ -150,6 +150,10 @@ class CAPITest(unittest.TestCase): "This docstring has a valid signature.") self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "($module, /, sig)") + self.assertEqual(_testcapi.docstring_with_signature_but_no_doc.__doc__, None) + self.assertEqual(_testcapi.docstring_with_signature_but_no_doc.__text_signature__, + "($module, /, sig)") + self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__, "\nThis docstring has a valid signature and some extra newlines.") self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__, diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cd051c4183b..76f2b474012 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1864,6 +1864,9 @@ class TestSignatureObject(unittest.TestCase): test_unbound_method(dict.__delitem__) test_unbound_method(property.__delete__) + # Regression test for issue #20586 + test_callable(_testcapi.docstring_with_signature_but_no_doc) + @cpython_only @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") diff --git a/Misc/NEWS b/Misc/NEWS index 79ed545695f..969da2cc920 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -156,6 +156,9 @@ Tests Tools/Demos ----------- +- Issue #20586: Argument Clinic now ensures that functions without docstrings + have signatures. + - Issue #23492: Argument Clinic now generates argument parsing code with PyArg_Parse instead of PyArg_ParseTuple if possible. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index d6eb6d4509a..d77d1dbcd98 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3083,6 +3083,12 @@ PyDoc_STRVAR(docstring_with_signature, "This docstring has a valid signature." ); +PyDoc_STRVAR(docstring_with_signature_but_no_doc, +"docstring_with_signature_but_no_doc($module, /, sig)\n" +"--\n" +"\n" +); + PyDoc_STRVAR(docstring_with_signature_and_extra_newlines, "docstring_with_signature_and_extra_newlines($module, /, parameter)\n" "--\n" @@ -3635,6 +3641,9 @@ static PyMethodDef TestMethods[] = { {"docstring_with_signature", (PyCFunction)test_with_docstring, METH_NOARGS, docstring_with_signature}, + {"docstring_with_signature_but_no_doc", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_with_signature_but_no_doc}, {"docstring_with_signature_and_extra_newlines", (PyCFunction)test_with_docstring, METH_NOARGS, docstring_with_signature_and_extra_newlines}, diff --git a/Modules/cjkcodecs/clinic/multibytecodec.c.h b/Modules/cjkcodecs/clinic/multibytecodec.c.h index 2f9cb639276..29b00acd88b 100644 --- a/Modules/cjkcodecs/clinic/multibytecodec.c.h +++ b/Modules/cjkcodecs/clinic/multibytecodec.c.h @@ -78,7 +78,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteIncrementalEncoder_encode__doc__, "encode($self, /, input, final=0)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTEINCREMENTALENCODER_ENCODE_METHODDEF \ {"encode", (PyCFunction)_multibytecodec_MultibyteIncrementalEncoder_encode, METH_VARARGS|METH_KEYWORDS, _multibytecodec_MultibyteIncrementalEncoder_encode__doc__}, @@ -106,7 +107,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteIncrementalEncoder_reset__doc__, "reset($self, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTEINCREMENTALENCODER_RESET_METHODDEF \ {"reset", (PyCFunction)_multibytecodec_MultibyteIncrementalEncoder_reset, METH_NOARGS, _multibytecodec_MultibyteIncrementalEncoder_reset__doc__}, @@ -122,7 +124,8 @@ _multibytecodec_MultibyteIncrementalEncoder_reset(MultibyteIncrementalEncoderObj PyDoc_STRVAR(_multibytecodec_MultibyteIncrementalDecoder_decode__doc__, "decode($self, /, input, final=0)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTEINCREMENTALDECODER_DECODE_METHODDEF \ {"decode", (PyCFunction)_multibytecodec_MultibyteIncrementalDecoder_decode, METH_VARARGS|METH_KEYWORDS, _multibytecodec_MultibyteIncrementalDecoder_decode__doc__}, @@ -154,7 +157,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteIncrementalDecoder_reset__doc__, "reset($self, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTEINCREMENTALDECODER_RESET_METHODDEF \ {"reset", (PyCFunction)_multibytecodec_MultibyteIncrementalDecoder_reset, METH_NOARGS, _multibytecodec_MultibyteIncrementalDecoder_reset__doc__}, @@ -170,7 +174,8 @@ _multibytecodec_MultibyteIncrementalDecoder_reset(MultibyteIncrementalDecoderObj PyDoc_STRVAR(_multibytecodec_MultibyteStreamReader_read__doc__, "read($self, sizeobj=None, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMREADER_READ_METHODDEF \ {"read", (PyCFunction)_multibytecodec_MultibyteStreamReader_read, METH_VARARGS, _multibytecodec_MultibyteStreamReader_read__doc__}, @@ -196,7 +201,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteStreamReader_readline__doc__, "readline($self, sizeobj=None, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMREADER_READLINE_METHODDEF \ {"readline", (PyCFunction)_multibytecodec_MultibyteStreamReader_readline, METH_VARARGS, _multibytecodec_MultibyteStreamReader_readline__doc__}, @@ -222,7 +228,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteStreamReader_readlines__doc__, "readlines($self, sizehintobj=None, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMREADER_READLINES_METHODDEF \ {"readlines", (PyCFunction)_multibytecodec_MultibyteStreamReader_readlines, METH_VARARGS, _multibytecodec_MultibyteStreamReader_readlines__doc__}, @@ -248,7 +255,8 @@ exit: PyDoc_STRVAR(_multibytecodec_MultibyteStreamReader_reset__doc__, "reset($self, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMREADER_RESET_METHODDEF \ {"reset", (PyCFunction)_multibytecodec_MultibyteStreamReader_reset, METH_NOARGS, _multibytecodec_MultibyteStreamReader_reset__doc__}, @@ -264,21 +272,24 @@ _multibytecodec_MultibyteStreamReader_reset(MultibyteStreamReaderObject *self, P PyDoc_STRVAR(_multibytecodec_MultibyteStreamWriter_write__doc__, "write($self, strobj, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMWRITER_WRITE_METHODDEF \ {"write", (PyCFunction)_multibytecodec_MultibyteStreamWriter_write, METH_O, _multibytecodec_MultibyteStreamWriter_write__doc__}, PyDoc_STRVAR(_multibytecodec_MultibyteStreamWriter_writelines__doc__, "writelines($self, lines, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMWRITER_WRITELINES_METHODDEF \ {"writelines", (PyCFunction)_multibytecodec_MultibyteStreamWriter_writelines, METH_O, _multibytecodec_MultibyteStreamWriter_writelines__doc__}, PyDoc_STRVAR(_multibytecodec_MultibyteStreamWriter_reset__doc__, "reset($self, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC_MULTIBYTESTREAMWRITER_RESET_METHODDEF \ {"reset", (PyCFunction)_multibytecodec_MultibyteStreamWriter_reset, METH_NOARGS, _multibytecodec_MultibyteStreamWriter_reset__doc__}, @@ -294,8 +305,9 @@ _multibytecodec_MultibyteStreamWriter_reset(MultibyteStreamWriterObject *self, P PyDoc_STRVAR(_multibytecodec___create_codec__doc__, "__create_codec($module, arg, /)\n" -"--"); +"--\n" +"\n"); #define _MULTIBYTECODEC___CREATE_CODEC_METHODDEF \ {"__create_codec", (PyCFunction)_multibytecodec___create_codec, METH_O, _multibytecodec___create_codec__doc__}, -/*[clinic end generated code: output=dff1459dec464796 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0ea29cd57f7cbc1a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/pyexpat.c.h b/Modules/clinic/pyexpat.c.h index d4a3a4b8ce4..707cc0c8c3b 100644 --- a/Modules/clinic/pyexpat.c.h +++ b/Modules/clinic/pyexpat.c.h @@ -209,7 +209,8 @@ exit: PyDoc_STRVAR(pyexpat_xmlparser___dir____doc__, "__dir__($self, /)\n" -"--"); +"--\n" +"\n"); #define PYEXPAT_XMLPARSER___DIR___METHODDEF \ {"__dir__", (PyCFunction)pyexpat_xmlparser___dir__, METH_NOARGS, pyexpat_xmlparser___dir____doc__}, @@ -286,4 +287,4 @@ exit: #ifndef PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF #define PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF #endif /* !defined(PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF) */ -/*[clinic end generated code: output=9715b916f2d618fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e5993de4e9dd2236 input=a9049054013a1b77]*/ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0e54fe60b0f..4b992878bf6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -137,7 +137,7 @@ _PyType_GetDocFromInternalDoc(const char *name, const char *internal_doc) { const char *doc = _PyType_DocWithoutSignature(name, internal_doc); - if (!doc) { + if (!doc || *doc == '\0') { Py_INCREF(Py_None); return Py_None; } diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 9623ab44663..99f5c3d60af 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -66,6 +66,8 @@ class Unknown: unknown = Unknown() +sig_end_marker = '--' + _text_accumulator_nt = collections.namedtuple("_text_accumulator", "text append output") @@ -559,8 +561,13 @@ class CLanguage(Language): add(quoted_for_c_string(line)) add('\\n"\n') - text.pop() - add('"') + if text[-2] == sig_end_marker: + # If we only have a signature, add the blank line that the + # __text_signature__ getter expects to be there. + add('"\\n"') + else: + text.pop() + add('"') return ''.join(text) def output_templates(self, f): @@ -4015,7 +4022,7 @@ class DSLParser: # add(f.return_converter.py_default) if not f.docstring_only: - add("\n--\n") + add("\n" + sig_end_marker + "\n") docstring_first_line = output()