[Python-checkins] [3.11] gh-86457: Fix signature for code.replace() (GH-23199) (GH-107746)

serhiy-storchaka webhook-mailer at python.org
Wed Aug 9 02:12:06 EDT 2023


https://github.com/python/cpython/commit/edaa0db93ee23b1d936631dedde3adf5a1a0fb13
commit: edaa0db93ee23b1d936631dedde3adf5a1a0fb13
branch: 3.11
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2023-08-09T06:12:02Z
summary:

[3.11] gh-86457: Fix signature for code.replace() (GH-23199) (GH-107746)

Also add support of @text_signature in Argument Clinic.
(cherry picked from commit 0e6e32fb84b2f7cb668e0b9927637587081e38cd)

files:
A Misc/NEWS.d/next/Tools-Demos/2023-08-07-16-30-48.gh-issue-95065.-im4R5.rst
M Objects/clinic/codeobject.c.h
M Objects/codeobject.c
M Tools/clinic/clinic.py

diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-08-07-16-30-48.gh-issue-95065.-im4R5.rst b/Misc/NEWS.d/next/Tools-Demos/2023-08-07-16-30-48.gh-issue-95065.-im4R5.rst
new file mode 100644
index 0000000000000..4768e6767574d
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2023-08-07-16-30-48.gh-issue-95065.-im4R5.rst
@@ -0,0 +1,2 @@
+Argument Clinic now supports overriding automatically generated signature by
+using directive ``@text_signature``.
diff --git a/Objects/clinic/codeobject.c.h b/Objects/clinic/codeobject.c.h
index df82524a86afe..9bf9e14b17e51 100644
--- a/Objects/clinic/codeobject.c.h
+++ b/Objects/clinic/codeobject.c.h
@@ -157,12 +157,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 }
 
 PyDoc_STRVAR(code_replace__doc__,
-"replace($self, /, *, co_argcount=-1, co_posonlyargcount=-1,\n"
-"        co_kwonlyargcount=-1, co_nlocals=-1, co_stacksize=-1,\n"
-"        co_flags=-1, co_firstlineno=-1, co_code=None, co_consts=None,\n"
-"        co_names=None, co_varnames=None, co_freevars=None,\n"
-"        co_cellvars=None, co_filename=None, co_name=None,\n"
-"        co_qualname=None, co_linetable=None, co_exceptiontable=None)\n"
+"replace($self, /, **changes)\n"
 "--\n"
 "\n"
 "Return a copy of the code object with new values for the specified fields.");
@@ -174,13 +169,12 @@ static PyObject *
 code_replace_impl(PyCodeObject *self, int co_argcount,
                   int co_posonlyargcount, int co_kwonlyargcount,
                   int co_nlocals, int co_stacksize, int co_flags,
-                  int co_firstlineno, PyBytesObject *co_code,
-                  PyObject *co_consts, PyObject *co_names,
-                  PyObject *co_varnames, PyObject *co_freevars,
-                  PyObject *co_cellvars, PyObject *co_filename,
-                  PyObject *co_name, PyObject *co_qualname,
-                  PyBytesObject *co_linetable,
-                  PyBytesObject *co_exceptiontable);
+                  int co_firstlineno, PyObject *co_code, PyObject *co_consts,
+                  PyObject *co_names, PyObject *co_varnames,
+                  PyObject *co_freevars, PyObject *co_cellvars,
+                  PyObject *co_filename, PyObject *co_name,
+                  PyObject *co_qualname, PyObject *co_linetable,
+                  PyObject *co_exceptiontable);
 
 static PyObject *
 code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@@ -197,7 +191,7 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
     int co_stacksize = self->co_stacksize;
     int co_flags = self->co_flags;
     int co_firstlineno = self->co_firstlineno;
-    PyBytesObject *co_code = NULL;
+    PyObject *co_code = NULL;
     PyObject *co_consts = self->co_consts;
     PyObject *co_names = self->co_names;
     PyObject *co_varnames = NULL;
@@ -206,8 +200,8 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
     PyObject *co_filename = self->co_filename;
     PyObject *co_name = self->co_name;
     PyObject *co_qualname = self->co_qualname;
-    PyBytesObject *co_linetable = (PyBytesObject *)self->co_linetable;
-    PyBytesObject *co_exceptiontable = (PyBytesObject *)self->co_exceptiontable;
+    PyObject *co_linetable = self->co_linetable;
+    PyObject *co_exceptiontable = self->co_exceptiontable;
 
     args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf);
     if (!args) {
@@ -284,7 +278,7 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
             _PyArg_BadArgument("replace", "argument 'co_code'", "bytes", args[7]);
             goto exit;
         }
-        co_code = (PyBytesObject *)args[7];
+        co_code = args[7];
         if (!--noptargs) {
             goto skip_optional_kwonly;
         }
@@ -383,7 +377,7 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
             _PyArg_BadArgument("replace", "argument 'co_linetable'", "bytes", args[16]);
             goto exit;
         }
-        co_linetable = (PyBytesObject *)args[16];
+        co_linetable = args[16];
         if (!--noptargs) {
             goto skip_optional_kwonly;
         }
@@ -392,7 +386,7 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
         _PyArg_BadArgument("replace", "argument 'co_exceptiontable'", "bytes", args[17]);
         goto exit;
     }
-    co_exceptiontable = (PyBytesObject *)args[17];
+    co_exceptiontable = args[17];
 skip_optional_kwonly:
     return_value = code_replace_impl(self, co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals, co_stacksize, co_flags, co_firstlineno, co_code, co_consts, co_names, co_varnames, co_freevars, co_cellvars, co_filename, co_name, co_qualname, co_linetable, co_exceptiontable);
 
@@ -436,4 +430,4 @@ code__varname_from_oparg(PyCodeObject *self, PyObject *const *args, Py_ssize_t n
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=9c521b6c79f90ff7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=d1bbf51b746ca2d0 input=a9049054013a1b77]*/
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 77ea4abf35dca..c4a0d9ad1cf98 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -1875,27 +1875,28 @@ code_linesiterator(PyCodeObject *code, PyObject *Py_UNUSED(args))
 }
 
 /*[clinic input]
+ at text_signature "($self, /, **changes)"
 code.replace
 
     *
-    co_argcount: int(c_default="self->co_argcount") = -1
-    co_posonlyargcount: int(c_default="self->co_posonlyargcount") = -1
-    co_kwonlyargcount: int(c_default="self->co_kwonlyargcount") = -1
-    co_nlocals: int(c_default="self->co_nlocals") = -1
-    co_stacksize: int(c_default="self->co_stacksize") = -1
-    co_flags: int(c_default="self->co_flags") = -1
-    co_firstlineno: int(c_default="self->co_firstlineno") = -1
-    co_code: PyBytesObject(c_default="NULL") = None
-    co_consts: object(subclass_of="&PyTuple_Type", c_default="self->co_consts") = None
-    co_names: object(subclass_of="&PyTuple_Type", c_default="self->co_names") = None
-    co_varnames: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
-    co_freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
-    co_cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
-    co_filename: unicode(c_default="self->co_filename") = None
-    co_name: unicode(c_default="self->co_name") = None
-    co_qualname: unicode(c_default="self->co_qualname") = None
-    co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None
-    co_exceptiontable: PyBytesObject(c_default="(PyBytesObject *)self->co_exceptiontable") = None
+    co_argcount: int(c_default="self->co_argcount") = unchanged
+    co_posonlyargcount: int(c_default="self->co_posonlyargcount") = unchanged
+    co_kwonlyargcount: int(c_default="self->co_kwonlyargcount") = unchanged
+    co_nlocals: int(c_default="self->co_nlocals") = unchanged
+    co_stacksize: int(c_default="self->co_stacksize") = unchanged
+    co_flags: int(c_default="self->co_flags") = unchanged
+    co_firstlineno: int(c_default="self->co_firstlineno") = unchanged
+    co_code: object(subclass_of="&PyBytes_Type", c_default="NULL") = unchanged
+    co_consts: object(subclass_of="&PyTuple_Type", c_default="self->co_consts") = unchanged
+    co_names: object(subclass_of="&PyTuple_Type", c_default="self->co_names") = unchanged
+    co_varnames: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
+    co_freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
+    co_cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
+    co_filename: unicode(c_default="self->co_filename") = unchanged
+    co_name: unicode(c_default="self->co_name") = unchanged
+    co_qualname: unicode(c_default="self->co_qualname") = unchanged
+    co_linetable: object(subclass_of="&PyBytes_Type", c_default="self->co_linetable") = unchanged
+    co_exceptiontable: object(subclass_of="&PyBytes_Type", c_default="self->co_exceptiontable") = unchanged
 
 Return a copy of the code object with new values for the specified fields.
 [clinic start generated code]*/
@@ -1904,14 +1905,13 @@ static PyObject *
 code_replace_impl(PyCodeObject *self, int co_argcount,
                   int co_posonlyargcount, int co_kwonlyargcount,
                   int co_nlocals, int co_stacksize, int co_flags,
-                  int co_firstlineno, PyBytesObject *co_code,
-                  PyObject *co_consts, PyObject *co_names,
-                  PyObject *co_varnames, PyObject *co_freevars,
-                  PyObject *co_cellvars, PyObject *co_filename,
-                  PyObject *co_name, PyObject *co_qualname,
-                  PyBytesObject *co_linetable,
-                  PyBytesObject *co_exceptiontable)
-/*[clinic end generated code: output=b6cd9988391d5711 input=f6f68e03571f8d7c]*/
+                  int co_firstlineno, PyObject *co_code, PyObject *co_consts,
+                  PyObject *co_names, PyObject *co_varnames,
+                  PyObject *co_freevars, PyObject *co_cellvars,
+                  PyObject *co_filename, PyObject *co_name,
+                  PyObject *co_qualname, PyObject *co_linetable,
+                  PyObject *co_exceptiontable)
+/*[clinic end generated code: output=e75c48a15def18b9 input=18e280e07846c122]*/
 {
 #define CHECK_INT_ARG(ARG) \
         if (ARG < 0) { \
@@ -1936,7 +1936,7 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
         if (code == NULL) {
             return NULL;
         }
-        co_code = (PyBytesObject *)code;
+        co_code = code;
     }
 
     if (PySys_Audit("code.__new__", "OOOiiiiii",
@@ -1975,10 +1975,10 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
 
     co = PyCode_NewWithPosOnlyArgs(
         co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals,
-        co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names,
+        co_stacksize, co_flags, co_code, co_consts, co_names,
         co_varnames, co_freevars, co_cellvars, co_filename, co_name,
         co_qualname, co_firstlineno,
-        (PyObject*)co_linetable, (PyObject*)co_exceptiontable);
+        co_linetable, co_exceptiontable);
 
 error:
     Py_XDECREF(code);
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 4d30e66b4822a..84636b92e38a3 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -4072,6 +4072,7 @@ def reset(self):
         self.indent = IndentStack()
         self.kind = CALLABLE
         self.coexist = False
+        self.forced_text_signature: str | None = None
         self.parameter_continuation = ''
         self.preserve_output = False
 
@@ -4201,6 +4202,11 @@ def at_coexist(self):
             fail("Called @coexist twice!")
         self.coexist = True
 
+    def at_text_signature(self, text_signature):
+        if self.forced_text_signature:
+            fail("Called @text_signature twice!")
+        self.forced_text_signature = text_signature
+
     def parse(self, block):
         self.reset()
         self.block = block
@@ -4903,142 +4909,145 @@ def format_docstring(self):
             add(f.cls.name)
         else:
             add(f.name)
-        add('(')
+        if self.forced_text_signature:
+            add(self.forced_text_signature)
+        else:
+            add('(')
+
+            # populate "right_bracket_count" field for every parameter
+            assert parameters, "We should always have a self parameter. " + repr(f)
+            assert isinstance(parameters[0].converter, self_converter)
+            # self is always positional-only.
+            assert parameters[0].is_positional_only()
+            parameters[0].right_bracket_count = 0
+            positional_only = True
+            for p in parameters[1:]:
+                if not p.is_positional_only():
+                    positional_only = False
+                else:
+                    assert positional_only
+                if positional_only:
+                    p.right_bracket_count = abs(p.group)
+                else:
+                    # don't put any right brackets around non-positional-only parameters, ever.
+                    p.right_bracket_count = 0
+
+            right_bracket_count = 0
+
+            def fix_right_bracket_count(desired):
+                nonlocal right_bracket_count
+                s = ''
+                while right_bracket_count < desired:
+                    s += '['
+                    right_bracket_count += 1
+                while right_bracket_count > desired:
+                    s += ']'
+                    right_bracket_count -= 1
+                return s
+
+            need_slash = False
+            added_slash = False
+            need_a_trailing_slash = False
+
+            # we only need a trailing slash:
+            #   * if this is not a "docstring_only" signature
+            #   * and if the last *shown* parameter is
+            #     positional only
+            if not f.docstring_only:
+                for p in reversed(parameters):
+                    if not p.converter.show_in_signature:
+                        continue
+                    if p.is_positional_only():
+                        need_a_trailing_slash = True
+                    break
 
-        # populate "right_bracket_count" field for every parameter
-        assert parameters, "We should always have a self parameter. " + repr(f)
-        assert isinstance(parameters[0].converter, self_converter)
-        # self is always positional-only.
-        assert parameters[0].is_positional_only()
-        parameters[0].right_bracket_count = 0
-        positional_only = True
-        for p in parameters[1:]:
-            if not p.is_positional_only():
-                positional_only = False
-            else:
-                assert positional_only
-            if positional_only:
-                p.right_bracket_count = abs(p.group)
-            else:
-                # don't put any right brackets around non-positional-only parameters, ever.
-                p.right_bracket_count = 0
-
-        right_bracket_count = 0
-
-        def fix_right_bracket_count(desired):
-            nonlocal right_bracket_count
-            s = ''
-            while right_bracket_count < desired:
-                s += '['
-                right_bracket_count += 1
-            while right_bracket_count > desired:
-                s += ']'
-                right_bracket_count -= 1
-            return s
 
-        need_slash = False
-        added_slash = False
-        need_a_trailing_slash = False
+            added_star = False
 
-        # we only need a trailing slash:
-        #   * if this is not a "docstring_only" signature
-        #   * and if the last *shown* parameter is
-        #     positional only
-        if not f.docstring_only:
-            for p in reversed(parameters):
+            first_parameter = True
+            last_p = parameters[-1]
+            line_length = len(''.join(text))
+            indent = " " * line_length
+            def add_parameter(text):
+                nonlocal line_length
+                nonlocal first_parameter
+                if first_parameter:
+                    s = text
+                    first_parameter = False
+                else:
+                    s = ' ' + text
+                    if line_length + len(s) >= 72:
+                        add('\n')
+                        add(indent)
+                        line_length = len(indent)
+                        s = text
+                line_length += len(s)
+                add(s)
+
+            for p in parameters:
                 if not p.converter.show_in_signature:
                     continue
-                if p.is_positional_only():
-                    need_a_trailing_slash = True
-                break
+                assert p.name
 
+                is_self = isinstance(p.converter, self_converter)
+                if is_self and f.docstring_only:
+                    # this isn't a real machine-parsable signature,
+                    # so let's not print the "self" parameter
+                    continue
 
-        added_star = False
-
-        first_parameter = True
-        last_p = parameters[-1]
-        line_length = len(''.join(text))
-        indent = " " * line_length
-        def add_parameter(text):
-            nonlocal line_length
-            nonlocal first_parameter
-            if first_parameter:
-                s = text
-                first_parameter = False
-            else:
-                s = ' ' + text
-                if line_length + len(s) >= 72:
-                    add('\n')
-                    add(indent)
-                    line_length = len(indent)
-                    s = text
-            line_length += len(s)
-            add(s)
-
-        for p in parameters:
-            if not p.converter.show_in_signature:
-                continue
-            assert p.name
-
-            is_self = isinstance(p.converter, self_converter)
-            if is_self and f.docstring_only:
-                # this isn't a real machine-parsable signature,
-                # so let's not print the "self" parameter
-                continue
-
-            if p.is_positional_only():
-                need_slash = not f.docstring_only
-            elif need_slash and not (added_slash or p.is_positional_only()):
-                added_slash = True
-                add_parameter('/,')
-
-            if p.is_keyword_only() and not added_star:
-                added_star = True
-                add_parameter('*,')
-
-            p_add, p_output = text_accumulator()
-            p_add(fix_right_bracket_count(p.right_bracket_count))
-
-            if isinstance(p.converter, self_converter):
-                # annotate first parameter as being a "self".
-                #
-                # if inspect.Signature gets this function,
-                # and it's already bound, the self parameter
-                # will be stripped off.
-                #
-                # if it's not bound, it should be marked
-                # as positional-only.
-                #
-                # note: we don't print "self" for __init__,
-                # because this isn't actually the signature
-                # for __init__.  (it can't be, __init__ doesn't
-                # have a docstring.)  if this is an __init__
-                # (or __new__), then this signature is for
-                # calling the class to construct a new instance.
-                p_add('$')
+                if p.is_positional_only():
+                    need_slash = not f.docstring_only
+                elif need_slash and not (added_slash or p.is_positional_only()):
+                    added_slash = True
+                    add_parameter('/,')
+
+                if p.is_keyword_only() and not added_star:
+                    added_star = True
+                    add_parameter('*,')
+
+                p_add, p_output = text_accumulator()
+                p_add(fix_right_bracket_count(p.right_bracket_count))
+
+                if isinstance(p.converter, self_converter):
+                    # annotate first parameter as being a "self".
+                    #
+                    # if inspect.Signature gets this function,
+                    # and it's already bound, the self parameter
+                    # will be stripped off.
+                    #
+                    # if it's not bound, it should be marked
+                    # as positional-only.
+                    #
+                    # note: we don't print "self" for __init__,
+                    # because this isn't actually the signature
+                    # for __init__.  (it can't be, __init__ doesn't
+                    # have a docstring.)  if this is an __init__
+                    # (or __new__), then this signature is for
+                    # calling the class to construct a new instance.
+                    p_add('$')
 
-            if p.is_vararg():
-                p_add("*")
+                if p.is_vararg():
+                    p_add("*")
 
-            name = p.converter.signature_name or p.name
-            p_add(name)
+                name = p.converter.signature_name or p.name
+                p_add(name)
 
-            if not p.is_vararg() and p.converter.is_optional():
-                p_add('=')
-                value = p.converter.py_default
-                if not value:
-                    value = repr(p.converter.default)
-                p_add(value)
+                if not p.is_vararg() and p.converter.is_optional():
+                    p_add('=')
+                    value = p.converter.py_default
+                    if not value:
+                        value = repr(p.converter.default)
+                    p_add(value)
 
-            if (p != last_p) or need_a_trailing_slash:
-                p_add(',')
+                if (p != last_p) or need_a_trailing_slash:
+                    p_add(',')
 
-            add_parameter(p_output())
+                add_parameter(p_output())
 
-        add(fix_right_bracket_count(0))
-        if need_a_trailing_slash:
-            add_parameter('/')
-        add(')')
+            add(fix_right_bracket_count(0))
+            if need_a_trailing_slash:
+                add_parameter('/')
+            add(')')
 
         # PEP 8 says:
         #



More information about the Python-checkins mailing list