From 5bafa02cf086ccff0ca527953457bc4f8ea49005 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Apr 2024 22:36:56 +0200 Subject: [PATCH 1/5] Argument Clinic: copy forced text signature when cloning --- Objects/clinic/unicodeobject.c.h | 10 +++++----- Tools/clinic/libclinic/dsl_parser.py | 10 ++++++---- Tools/clinic/libclinic/function.py | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Objects/clinic/unicodeobject.c.h b/Objects/clinic/unicodeobject.c.h index 01c40b90d9b4b8..78e14b0021d006 100644 --- a/Objects/clinic/unicodeobject.c.h +++ b/Objects/clinic/unicodeobject.c.h @@ -357,7 +357,7 @@ unicode_expandtabs(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyOb } PyDoc_STRVAR(unicode_find__doc__, -"find($self, sub, start=None, end=None, /)\n" +"find($self, sub[, start[, end]], /)\n" "--\n" "\n" "Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].\n" @@ -413,7 +413,7 @@ unicode_find(PyObject *str, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(unicode_index__doc__, -"index($self, sub, start=None, end=None, /)\n" +"index($self, sub[, start[, end]], /)\n" "--\n" "\n" "Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end].\n" @@ -1060,7 +1060,7 @@ unicode_removesuffix(PyObject *self, PyObject *arg) } PyDoc_STRVAR(unicode_rfind__doc__, -"rfind($self, sub, start=None, end=None, /)\n" +"rfind($self, sub[, start[, end]], /)\n" "--\n" "\n" "Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].\n" @@ -1116,7 +1116,7 @@ unicode_rfind(PyObject *str, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(unicode_rindex__doc__, -"rindex($self, sub, start=None, end=None, /)\n" +"rindex($self, sub[, start[, end]], /)\n" "--\n" "\n" "Return the highest index in S where substring sub is found, such that sub is contained within S[start:end].\n" @@ -1888,4 +1888,4 @@ unicode_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=3aa49013ffa3fa93 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9fee62bd337f809b input=a9049054013a1b77]*/ diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index 4c739efe1066e4..b846f6e9649b85 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -689,7 +689,8 @@ def state_modulename_name(self, line: str) -> None: kind=self.kind, coexist=self.coexist, critical_section=self.critical_section, - target_critical_section=self.target_critical_section + target_critical_section=self.target_critical_section, + forced_text_signature=self.forced_text_signature ) self.add_function(func) @@ -1324,13 +1325,14 @@ def state_function_docstring(self, line: str) -> None: self.docstring_append(self.function, line) + @staticmethod def format_docstring_signature( - self, f: Function, parameters: list[Parameter] + f: Function, parameters: list[Parameter] ) -> str: lines = [] lines.append(f.displayname) - if self.forced_text_signature: - lines.append(self.forced_text_signature) + if f.forced_text_signature: + lines.append(f.forced_text_signature) elif f.kind in {GETTER, SETTER}: # @getter and @setter do not need signatures like a method or a function. return '' diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py index 1563fdf9065b7e..c1f2aed6054fa4 100644 --- a/Tools/clinic/libclinic/function.py +++ b/Tools/clinic/libclinic/function.py @@ -109,6 +109,7 @@ class Function: # functions with optional groups because we can't represent # those accurately with inspect.Signature in 3.4. docstring_only: bool = False + forced_text_signature: str | None = None critical_section: bool = False target_critical_section: list[str] = dc.field(default_factory=list) From fd9f706e8b7c92db05445def262bb34a5f46edc8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Apr 2024 22:59:36 +0200 Subject: [PATCH 2/5] Add test --- Lib/test/test_clinic.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 9788ac0261fa49..b8e61d83b631de 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -662,6 +662,30 @@ class C "void *" "" err = "Illegal C basename: '.illegal.'" self.expect_failure(block, err, lineno=7) + def test_cloned_forced_text_signature(self): + block = dedent(""" + /*[clinic input] + @text_signature "($module, a[, b])" + src + a: object + b: object = NULL + / + [clinic start generated code]*/ + + /*[clinic input] + dst = src + [clinic start generated code]*/ + """) + self.clinic.parse(block) + funcs = self.clinic.functions + self.assertEqual(len(funcs), 2) + + src_docstring_lines = funcs[0].docstring.split("\n") + dst_docstring_lines = funcs[1].docstring.split("\n") + self.assertEqual(src_docstring_lines[0], "src($module, a[, b])") + self.assertEqual(dst_docstring_lines[0], "dst($module, a[, b])") + self.assertEqual(src_docstring_lines[1:], dst_docstring_lines[1:]) + class ParseFileUnitTest(TestCase): def expect_parsing_failure( From 86f7b4a4f70a1ac9a24621d44b2923ecf59dde00 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Apr 2024 23:12:25 +0200 Subject: [PATCH 3/5] @text_signature when cloning does not make sense --- Lib/test/test_clinic.py | 18 ++++++++++++++++++ Tools/clinic/libclinic/dsl_parser.py | 2 ++ 2 files changed, 20 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index b8e61d83b631de..140af2c6c8d5e2 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -686,6 +686,24 @@ def test_cloned_forced_text_signature(self): self.assertEqual(dst_docstring_lines[0], "dst($module, a[, b])") self.assertEqual(src_docstring_lines[1:], dst_docstring_lines[1:]) + def test_cloned_forced_text_signature_illegal(self): + block = """ + /*[clinic input] + @text_signature "($module, a[, b])" + src + a: object + b: object = NULL + / + [clinic start generated code]*/ + + /*[clinic input] + @text_signature "($module, a_override[, b])" + dst = src + [clinic start generated code]*/ + """ + err = "Cannot use @text_signature when cloning a function" + self.expect_failure(block, err, lineno=11) + class ParseFileUnitTest(TestCase): def expect_parsing_failure( diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index b846f6e9649b85..9e22d847c4dc90 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -666,6 +666,8 @@ def state_modulename_name(self, line: str) -> None: if equals: existing = existing.strip() if libclinic.is_legal_py_identifier(existing): + if self.forced_text_signature: + fail("Cannot use @text_signature when cloning a function") # we're cloning! names = self.parse_function_names(before) return self.parse_cloned_function(names, existing) From 2d193aae92aca3eb1495fc9967891772d73e8f94 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 6 Apr 2024 23:31:50 +0200 Subject: [PATCH 4/5] Clean up --- Lib/test/test_clinic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 140af2c6c8d5e2..dc89b3e5652c02 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -5,7 +5,7 @@ from functools import partial from test import support, test_tools from test.support import os_helper -from test.support.os_helper import TESTFN, unlink +from test.support.os_helper import TESTFN, unlink, rmtree from textwrap import dedent from unittest import TestCase import inspect @@ -677,6 +677,7 @@ def test_cloned_forced_text_signature(self): [clinic start generated code]*/ """) self.clinic.parse(block) + self.addCleanup(rmtree, "clinic") funcs = self.clinic.functions self.assertEqual(len(funcs), 2) From ae0fa89b041480ac246b723c4bde0356bd4c0e20 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 9 Apr 2024 23:20:24 +0200 Subject: [PATCH 5/5] More tests --- Lib/test/test_clinic.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 2290a4c7a4b764..e3ba3d943216de 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -668,8 +668,11 @@ def test_cloned_forced_text_signature(self): @text_signature "($module, a[, b])" src a: object + param a b: object = NULL / + + docstring [clinic start generated code]*/ /*[clinic input] @@ -683,9 +686,18 @@ def test_cloned_forced_text_signature(self): src_docstring_lines = funcs[0].docstring.split("\n") dst_docstring_lines = funcs[1].docstring.split("\n") + + # Signatures are copied. self.assertEqual(src_docstring_lines[0], "src($module, a[, b])") self.assertEqual(dst_docstring_lines[0], "dst($module, a[, b])") - self.assertEqual(src_docstring_lines[1:], dst_docstring_lines[1:]) + + # Param docstrings are copied. + self.assertIn(" param a", src_docstring_lines) + self.assertIn(" param a", dst_docstring_lines) + + # Docstrings are not copied. + self.assertIn("docstring", src_docstring_lines) + self.assertNotIn("docstring", dst_docstring_lines) def test_cloned_forced_text_signature_illegal(self): block = """