summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/howto/clinic.rst43
-rw-r--r--Misc/NEWS3
-rwxr-xr-xTools/clinic/clinic.py46
3 files changed, 92 insertions, 0 deletions
diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst
index 9c558bc5..0df8d44 100644
--- a/Doc/howto/clinic.rst
+++ b/Doc/howto/clinic.rst
@@ -847,6 +847,49 @@ their parameters (if any),
just run ``Tools/clinic/clinic.py --converters`` for the full list.
+Cloning existing functions
+--------------------------
+
+If you have a number of functions that look similar, you may be able to
+use Clinic's "clone" feature. When you clone an existing function,
+you reuse:
+
+* its parameters, including
+
+ * their names,
+
+ * their converters, with all parameters,
+
+ * their default values,
+
+ * their per-parameter docstrings,
+
+ * their *kind* (whether they're positional only,
+ positional or keyword, or keyword only), and
+
+* its return converter.
+
+The only thing not copied from the original function is its docstring;
+the syntax allows you to specify a new docstring.
+
+Here's the syntax for cloning a function::
+
+ /*[clinic input]
+ module.class.new_function [as c_basename] = module.class.existing_function
+
+ Docstring for new_function goes here.
+ [clinic start generated code]*/
+
+(The functions can be in different modules or classes. I wrote
+``module.class`` in the sample just to illustrate that you must
+use the full path to *both* functions.)
+
+Sorry, there's no syntax for partially-cloning a function, or cloning a function
+then modifying it. Cloning is an all-or nothing proposition.
+
+Also, the function you are cloning from must have been previously defined
+in the current file.
+
Calling Python code
-------------------
diff --git a/Misc/NEWS b/Misc/NEWS
index 73a660a..ea31879 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -86,6 +86,9 @@ Tests
Tools/Demos
-----------
+- Issue #20268: Argument Clinic now supports cloning the parameters and
+ return converter of existing functions.
+
- Issue #20228: Argument Clinic now has special support for class special
methods.
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 56e4911..ed59f05 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -2301,6 +2301,12 @@ class DSLParser:
# modulename.fnname [as c_basename] [-> return annotation]
# square brackets denote optional syntax.
#
+ # alternatively:
+ # modulename.fnname [as c_basename] = modulename.existing_fn_name
+ # clones the parameters and return converter from that
+ # function. you can't modify them. you must enter a
+ # new docstring.
+ #
# (but we might find a directive first!)
#
# this line is permitted to start with whitespace.
@@ -2319,6 +2325,45 @@ class DSLParser:
directive(*fields[1:])
return
+ # are we cloning?
+ before, equals, existing = line.rpartition('=')
+ if equals:
+ full_name, _, c_basename = before.partition(' as ')
+ full_name = full_name.strip()
+ c_basename = c_basename.strip()
+ existing = existing.strip()
+ if (is_legal_py_identifier(full_name) and
+ (not c_basename or is_legal_c_identifier(c_basename)) and
+ is_legal_py_identifier(existing)):
+ # we're cloning!
+ fields = [x.strip() for x in existing.split('.')]
+ function_name = fields.pop()
+ module, cls = self.clinic._module_and_class(fields)
+
+ for existing_function in (cls or module).functions:
+ if existing_function.name == function_name:
+ break
+ else:
+ existing_function = None
+ if not existing_function:
+ fail("Couldn't find existing function " + repr(existing) + "!")
+
+ fields = [x.strip() for x in full_name.split('.')]
+ function_name = fields.pop()
+ module, cls = self.clinic._module_and_class(fields)
+
+ if not (existing_function.kind == self.kind and existing_function.coexist == self.coexist):
+ fail("'kind' of function and cloned function don't match! (@classmethod/@staticmethod/@coexist)")
+ self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
+ return_converter=existing_function.return_converter, kind=existing_function.kind, coexist=existing_function.coexist)
+
+ self.function.parameters = existing_function.parameters.copy()
+
+ self.block.signatures.append(self.function)
+ (cls or module).functions.append(self.function)
+ self.next(self.state_function_docstring)
+ return
+
line, _, returns = line.partition('->')
full_name, _, c_basename = line.partition(' as ')
@@ -2373,6 +2418,7 @@ class DSLParser:
self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename,
return_converter=return_converter, kind=self.kind, coexist=self.coexist)
self.block.signatures.append(self.function)
+ (cls or module).functions.append(self.function)
self.next(self.state_parameters_start)
# Now entering the parameters section. The rules, formally stated: