Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0e6e32f

Browse files
authoredAug 7, 2023
pythongh-86457: Fix signature for code.replace() (pythonGH-23199)
Also add support of @text_signature in Argument Clinic.
1 parent bea5f93 commit 0e6e32f

File tree

4 files changed

+178
-173
lines changed

4 files changed

+178
-173
lines changed
 
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Argument Clinic now supports overriding automatically generated signature by
2+
using directive `@text_signature`.

‎Objects/clinic/codeobject.c.h

Lines changed: 14 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/codeobject.c

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,27 +1968,28 @@ code_linesiterator(PyCodeObject *code, PyObject *Py_UNUSED(args))
19681968
}
19691969

19701970
/*[clinic input]
1971+
@text_signature "($self, /, **changes)"
19711972
code.replace
19721973
19731974
*
1974-
co_argcount: int(c_default="self->co_argcount") = -1
1975-
co_posonlyargcount: int(c_default="self->co_posonlyargcount") = -1
1976-
co_kwonlyargcount: int(c_default="self->co_kwonlyargcount") = -1
1977-
co_nlocals: int(c_default="self->co_nlocals") = -1
1978-
co_stacksize: int(c_default="self->co_stacksize") = -1
1979-
co_flags: int(c_default="self->co_flags") = -1
1980-
co_firstlineno: int(c_default="self->co_firstlineno") = -1
1981-
co_code: PyBytesObject(c_default="NULL") = None
1982-
co_consts: object(subclass_of="&PyTuple_Type", c_default="self->co_consts") = None
1983-
co_names: object(subclass_of="&PyTuple_Type", c_default="self->co_names") = None
1984-
co_varnames: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
1985-
co_freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
1986-
co_cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = None
1987-
co_filename: unicode(c_default="self->co_filename") = None
1988-
co_name: unicode(c_default="self->co_name") = None
1989-
co_qualname: unicode(c_default="self->co_qualname") = None
1990-
co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None
1991-
co_exceptiontable: PyBytesObject(c_default="(PyBytesObject *)self->co_exceptiontable") = None
1975+
co_argcount: int(c_default="self->co_argcount") = unchanged
1976+
co_posonlyargcount: int(c_default="self->co_posonlyargcount") = unchanged
1977+
co_kwonlyargcount: int(c_default="self->co_kwonlyargcount") = unchanged
1978+
co_nlocals: int(c_default="self->co_nlocals") = unchanged
1979+
co_stacksize: int(c_default="self->co_stacksize") = unchanged
1980+
co_flags: int(c_default="self->co_flags") = unchanged
1981+
co_firstlineno: int(c_default="self->co_firstlineno") = unchanged
1982+
co_code: object(subclass_of="&PyBytes_Type", c_default="NULL") = unchanged
1983+
co_consts: object(subclass_of="&PyTuple_Type", c_default="self->co_consts") = unchanged
1984+
co_names: object(subclass_of="&PyTuple_Type", c_default="self->co_names") = unchanged
1985+
co_varnames: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
1986+
co_freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
1987+
co_cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = unchanged
1988+
co_filename: unicode(c_default="self->co_filename") = unchanged
1989+
co_name: unicode(c_default="self->co_name") = unchanged
1990+
co_qualname: unicode(c_default="self->co_qualname") = unchanged
1991+
co_linetable: object(subclass_of="&PyBytes_Type", c_default="self->co_linetable") = unchanged
1992+
co_exceptiontable: object(subclass_of="&PyBytes_Type", c_default="self->co_exceptiontable") = unchanged
19921993
19931994
Return a copy of the code object with new values for the specified fields.
19941995
[clinic start generated code]*/
@@ -1997,14 +1998,13 @@ static PyObject *
19971998
code_replace_impl(PyCodeObject *self, int co_argcount,
19981999
int co_posonlyargcount, int co_kwonlyargcount,
19992000
int co_nlocals, int co_stacksize, int co_flags,
2000-
int co_firstlineno, PyBytesObject *co_code,
2001-
PyObject *co_consts, PyObject *co_names,
2002-
PyObject *co_varnames, PyObject *co_freevars,
2003-
PyObject *co_cellvars, PyObject *co_filename,
2004-
PyObject *co_name, PyObject *co_qualname,
2005-
PyBytesObject *co_linetable,
2006-
PyBytesObject *co_exceptiontable)
2007-
/*[clinic end generated code: output=b6cd9988391d5711 input=f6f68e03571f8d7c]*/
2001+
int co_firstlineno, PyObject *co_code, PyObject *co_consts,
2002+
PyObject *co_names, PyObject *co_varnames,
2003+
PyObject *co_freevars, PyObject *co_cellvars,
2004+
PyObject *co_filename, PyObject *co_name,
2005+
PyObject *co_qualname, PyObject *co_linetable,
2006+
PyObject *co_exceptiontable)
2007+
/*[clinic end generated code: output=e75c48a15def18b9 input=18e280e07846c122]*/
20082008
{
20092009
#define CHECK_INT_ARG(ARG) \
20102010
if (ARG < 0) { \
@@ -2029,7 +2029,7 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
20292029
if (code == NULL) {
20302030
return NULL;
20312031
}
2032-
co_code = (PyBytesObject *)code;
2032+
co_code = code;
20332033
}
20342034

20352035
if (PySys_Audit("code.__new__", "OOOiiiiii",
@@ -2068,10 +2068,10 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
20682068

20692069
co = PyCode_NewWithPosOnlyArgs(
20702070
co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals,
2071-
co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names,
2071+
co_stacksize, co_flags, co_code, co_consts, co_names,
20722072
co_varnames, co_freevars, co_cellvars, co_filename, co_name,
20732073
co_qualname, co_firstlineno,
2074-
(PyObject*)co_linetable, (PyObject*)co_exceptiontable);
2074+
co_linetable, co_exceptiontable);
20752075

20762076
error:
20772077
Py_XDECREF(code);

‎Tools/clinic/clinic.py

Lines changed: 133 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -4595,6 +4595,7 @@ def reset(self) -> None:
45954595
self.indent = IndentStack()
45964596
self.kind = CALLABLE
45974597
self.coexist = False
4598+
self.forced_text_signature: str | None = None
45984599
self.parameter_continuation = ''
45994600
self.preserve_output = False
46004601

@@ -4735,6 +4736,11 @@ def at_coexist(self) -> None:
47354736
fail("Called @coexist twice!")
47364737
self.coexist = True
47374738

4739+
def at_text_signature(self, text_signature: str) -> None:
4740+
if self.forced_text_signature:
4741+
fail("Called @text_signature twice!")
4742+
self.forced_text_signature = text_signature
4743+
47384744
def parse(self, block: Block) -> None:
47394745
self.reset()
47404746
self.block = block
@@ -5515,142 +5521,145 @@ def format_docstring(self) -> str:
55155521
add(f.cls.name)
55165522
else:
55175523
add(f.name)
5518-
add('(')
5519-
5520-
# populate "right_bracket_count" field for every parameter
5521-
assert parameters, "We should always have a self parameter. " + repr(f)
5522-
assert isinstance(parameters[0].converter, self_converter)
5523-
# self is always positional-only.
5524-
assert parameters[0].is_positional_only()
5525-
assert parameters[0].right_bracket_count == 0
5526-
positional_only = True
5527-
for p in parameters[1:]:
5528-
if not p.is_positional_only():
5529-
positional_only = False
5530-
else:
5531-
assert positional_only
5532-
if positional_only:
5533-
p.right_bracket_count = abs(p.group)
5534-
else:
5535-
# don't put any right brackets around non-positional-only parameters, ever.
5536-
p.right_bracket_count = 0
5537-
5538-
right_bracket_count = 0
5539-
5540-
def fix_right_bracket_count(desired: int) -> str:
5541-
nonlocal right_bracket_count
5542-
s = ''
5543-
while right_bracket_count < desired:
5544-
s += '['
5545-
right_bracket_count += 1
5546-
while right_bracket_count > desired:
5547-
s += ']'
5548-
right_bracket_count -= 1
5549-
return s
5550-
5551-
need_slash = False
5552-
added_slash = False
5553-
need_a_trailing_slash = False
5554-
5555-
# we only need a trailing slash:
5556-
# * if this is not a "docstring_only" signature
5557-
# * and if the last *shown* parameter is
5558-
# positional only
5559-
if not f.docstring_only:
5560-
for p in reversed(parameters):
5561-
if not p.converter.show_in_signature:
5562-
continue
5563-
if p.is_positional_only():
5564-
need_a_trailing_slash = True
5565-
break
5524+
if self.forced_text_signature:
5525+
add(self.forced_text_signature)
5526+
else:
5527+
add('(')
5528+
5529+
# populate "right_bracket_count" field for every parameter
5530+
assert parameters, "We should always have a self parameter. " + repr(f)
5531+
assert isinstance(parameters[0].converter, self_converter)
5532+
# self is always positional-only.
5533+
assert parameters[0].is_positional_only()
5534+
assert parameters[0].right_bracket_count == 0
5535+
positional_only = True
5536+
for p in parameters[1:]:
5537+
if not p.is_positional_only():
5538+
positional_only = False
5539+
else:
5540+
assert positional_only
5541+
if positional_only:
5542+
p.right_bracket_count = abs(p.group)
5543+
else:
5544+
# don't put any right brackets around non-positional-only parameters, ever.
5545+
p.right_bracket_count = 0
5546+
5547+
right_bracket_count = 0
5548+
5549+
def fix_right_bracket_count(desired: int) -> str:
5550+
nonlocal right_bracket_count
5551+
s = ''
5552+
while right_bracket_count < desired:
5553+
s += '['
5554+
right_bracket_count += 1
5555+
while right_bracket_count > desired:
5556+
s += ']'
5557+
right_bracket_count -= 1
5558+
return s
5559+
5560+
need_slash = False
5561+
added_slash = False
5562+
need_a_trailing_slash = False
5563+
5564+
# we only need a trailing slash:
5565+
# * if this is not a "docstring_only" signature
5566+
# * and if the last *shown* parameter is
5567+
# positional only
5568+
if not f.docstring_only:
5569+
for p in reversed(parameters):
5570+
if not p.converter.show_in_signature:
5571+
continue
5572+
if p.is_positional_only():
5573+
need_a_trailing_slash = True
5574+
break
55665575

55675576

5568-
added_star = False
5577+
added_star = False
55695578

5570-
first_parameter = True
5571-
last_p = parameters[-1]
5572-
line_length = len(''.join(text))
5573-
indent = " " * line_length
5574-
def add_parameter(text: str) -> None:
5575-
nonlocal line_length
5576-
nonlocal first_parameter
5577-
if first_parameter:
5578-
s = text
5579-
first_parameter = False
5580-
else:
5581-
s = ' ' + text
5582-
if line_length + len(s) >= 72:
5583-
add('\n')
5584-
add(indent)
5585-
line_length = len(indent)
5579+
first_parameter = True
5580+
last_p = parameters[-1]
5581+
line_length = len(''.join(text))
5582+
indent = " " * line_length
5583+
def add_parameter(text: str) -> None:
5584+
nonlocal line_length
5585+
nonlocal first_parameter
5586+
if first_parameter:
55865587
s = text
5587-
line_length += len(s)
5588-
add(s)
5589-
5590-
for p in parameters:
5591-
if not p.converter.show_in_signature:
5592-
continue
5593-
assert p.name
5588+
first_parameter = False
5589+
else:
5590+
s = ' ' + text
5591+
if line_length + len(s) >= 72:
5592+
add('\n')
5593+
add(indent)
5594+
line_length = len(indent)
5595+
s = text
5596+
line_length += len(s)
5597+
add(s)
5598+
5599+
for p in parameters:
5600+
if not p.converter.show_in_signature:
5601+
continue
5602+
assert p.name
55945603

5595-
is_self = isinstance(p.converter, self_converter)
5596-
if is_self and f.docstring_only:
5597-
# this isn't a real machine-parsable signature,
5598-
# so let's not print the "self" parameter
5599-
continue
5604+
is_self = isinstance(p.converter, self_converter)
5605+
if is_self and f.docstring_only:
5606+
# this isn't a real machine-parsable signature,
5607+
# so let's not print the "self" parameter
5608+
continue
56005609

5601-
if p.is_positional_only():
5602-
need_slash = not f.docstring_only
5603-
elif need_slash and not (added_slash or p.is_positional_only()):
5604-
added_slash = True
5605-
add_parameter('/,')
5606-
5607-
if p.is_keyword_only() and not added_star:
5608-
added_star = True
5609-
add_parameter('*,')
5610-
5611-
p_add, p_output = text_accumulator()
5612-
p_add(fix_right_bracket_count(p.right_bracket_count))
5613-
5614-
if isinstance(p.converter, self_converter):
5615-
# annotate first parameter as being a "self".
5616-
#
5617-
# if inspect.Signature gets this function,
5618-
# and it's already bound, the self parameter
5619-
# will be stripped off.
5620-
#
5621-
# if it's not bound, it should be marked
5622-
# as positional-only.
5623-
#
5624-
# note: we don't print "self" for __init__,
5625-
# because this isn't actually the signature
5626-
# for __init__. (it can't be, __init__ doesn't
5627-
# have a docstring.) if this is an __init__
5628-
# (or __new__), then this signature is for
5629-
# calling the class to construct a new instance.
5630-
p_add('$')
5610+
if p.is_positional_only():
5611+
need_slash = not f.docstring_only
5612+
elif need_slash and not (added_slash or p.is_positional_only()):
5613+
added_slash = True
5614+
add_parameter('/,')
5615+
5616+
if p.is_keyword_only() and not added_star:
5617+
added_star = True
5618+
add_parameter('*,')
5619+
5620+
p_add, p_output = text_accumulator()
5621+
p_add(fix_right_bracket_count(p.right_bracket_count))
5622+
5623+
if isinstance(p.converter, self_converter):
5624+
# annotate first parameter as being a "self".
5625+
#
5626+
# if inspect.Signature gets this function,
5627+
# and it's already bound, the self parameter
5628+
# will be stripped off.
5629+
#
5630+
# if it's not bound, it should be marked
5631+
# as positional-only.
5632+
#
5633+
# note: we don't print "self" for __init__,
5634+
# because this isn't actually the signature
5635+
# for __init__. (it can't be, __init__ doesn't
5636+
# have a docstring.) if this is an __init__
5637+
# (or __new__), then this signature is for
5638+
# calling the class to construct a new instance.
5639+
p_add('$')
56315640

5632-
if p.is_vararg():
5633-
p_add("*")
5641+
if p.is_vararg():
5642+
p_add("*")
56345643

5635-
name = p.converter.signature_name or p.name
5636-
p_add(name)
5644+
name = p.converter.signature_name or p.name
5645+
p_add(name)
56375646

5638-
if not p.is_vararg() and p.converter.is_optional():
5639-
p_add('=')
5640-
value = p.converter.py_default
5641-
if not value:
5642-
value = repr(p.converter.default)
5643-
p_add(value)
5647+
if not p.is_vararg() and p.converter.is_optional():
5648+
p_add('=')
5649+
value = p.converter.py_default
5650+
if not value:
5651+
value = repr(p.converter.default)
5652+
p_add(value)
56445653

5645-
if (p != last_p) or need_a_trailing_slash:
5646-
p_add(',')
5654+
if (p != last_p) or need_a_trailing_slash:
5655+
p_add(',')
56475656

5648-
add_parameter(p_output())
5657+
add_parameter(p_output())
56495658

5650-
add(fix_right_bracket_count(0))
5651-
if need_a_trailing_slash:
5652-
add_parameter('/')
5653-
add(')')
5659+
add(fix_right_bracket_count(0))
5660+
if need_a_trailing_slash:
5661+
add_parameter('/')
5662+
add(')')
56545663

56555664
# PEP 8 says:
56565665
#

0 commit comments

Comments
 (0)
Please sign in to comment.