summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErlend E. Aasland <erlend@python.org>2024-04-10 08:12:05 (GMT)
committerGitHub <noreply@github.com>2024-04-10 08:12:05 (GMT)
commit0d42ac9474f857633d00b414c0715f4efa73f1ca (patch)
tree96a3f6f4b4e7ed544d07b547ddf51b8cc5853acd
parent73906d5c908c1e0b73c5436faeff7d93698fc074 (diff)
downloadcpython-0d42ac9474f857633d00b414c0715f4efa73f1ca.zip
cpython-0d42ac9474f857633d00b414c0715f4efa73f1ca.tar.gz
cpython-0d42ac9474f857633d00b414c0715f4efa73f1ca.tar.bz2
gh-117431: Argument Clinic: copy forced text signature when cloning (#117591)
-rw-r--r--Lib/test/test_clinic.py57
-rw-r--r--Objects/clinic/unicodeobject.c.h10
-rw-r--r--Tools/clinic/libclinic/dsl_parser.py12
-rw-r--r--Tools/clinic/libclinic/function.py1
4 files changed, 70 insertions, 10 deletions
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index a5887cd..e3ba3d9 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
@@ -662,6 +662,61 @@ class ClinicWholeFileTest(TestCase):
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
+ param a
+ b: object = NULL
+ /
+
+ docstring
+ [clinic start generated code]*/
+
+ /*[clinic input]
+ dst = src
+ [clinic start generated code]*/
+ """)
+ self.clinic.parse(block)
+ self.addCleanup(rmtree, "clinic")
+ 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")
+
+ # Signatures are copied.
+ self.assertEqual(src_docstring_lines[0], "src($module, a[, b])")
+ self.assertEqual(dst_docstring_lines[0], "dst($module, a[, b])")
+
+ # 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 = """
+ /*[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/Objects/clinic/unicodeobject.c.h b/Objects/clinic/unicodeobject.c.h
index 01c40b9..78e14b0 100644
--- a/Objects/clinic/unicodeobject.c.h
+++ b/Objects/clinic/unicodeobject.c.h
@@ -357,7 +357,7 @@ exit:
}
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 @@ exit:
}
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 @@ exit:
}
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 @@ exit:
}
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 @@ skip_optional_pos:
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 4c739ef..9e22d84 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -666,6 +666,8 @@ class DSLParser:
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)
@@ -689,7 +691,8 @@ class DSLParser:
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 +1327,14 @@ class DSLParser:
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 572916b..9390126 100644
--- a/Tools/clinic/libclinic/function.py
+++ b/Tools/clinic/libclinic/function.py
@@ -107,6 +107,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)