Skip to content

Commit 2a30bb6

Browse files
authored
Fix PEP 695 output for classes in the LaTeX builder (#12561)
1 parent aeebfab commit 2a30bb6

File tree

6 files changed

+88
-12
lines changed

6 files changed

+88
-12
lines changed

CHANGES.rst

+2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ Bugs fixed
160160
Patch by Jakob Lykke Andersen and Adam Turner.
161161
* #11041: linkcheck: Ignore URLs that respond with non-Unicode content.
162162
Patch by James Addison.
163+
* #12543: Fix :pep:`695` formatting for LaTeX output.
164+
Patch by Bénédikt Tran.
163165

164166
Testing
165167
-------

sphinx/writers/latex.py

+18-10
Original file line numberDiff line numberDiff line change
@@ -724,19 +724,21 @@ def has_multi_line(e: Element) -> bool:
724724
return e.get('multi_line_parameter_list')
725725

726726
self.has_tp_list = False
727+
self.orphan_tp_list = False
727728

728729
for child in node:
729730
if isinstance(child, addnodes.desc_type_parameter_list):
730731
self.has_tp_list = True
731-
# recall that return annotations must follow an argument list,
732-
# so signatures of the form "foo[tp_list] -> retann" will not
733-
# be encountered (if they should, the `domains.python.py_sig_re`
734-
# pattern must be modified accordingly)
735-
arglist = next_sibling(child)
736-
assert isinstance(arglist, addnodes.desc_parameterlist)
737-
# tp_list + arglist: \macro{name}{tp_list}{arglist}{return}
738732
multi_tp_list = has_multi_line(child)
739-
multi_arglist = has_multi_line(arglist)
733+
arglist = next_sibling(child)
734+
if isinstance(arglist, addnodes.desc_parameterlist):
735+
# tp_list + arglist: \macro{name}{tp_list}{arglist}{retann}
736+
multi_arglist = has_multi_line(arglist)
737+
else:
738+
# orphan tp_list: \macro{name}{tp_list}{}{retann}
739+
# see: https://github.com/sphinx-doc/sphinx/issues/12543
740+
self.orphan_tp_list = True
741+
multi_arglist = False
740742

741743
if multi_tp_list:
742744
if multi_arglist:
@@ -751,7 +753,7 @@ def has_multi_line(e: Element) -> bool:
751753
break
752754

753755
if isinstance(child, addnodes.desc_parameterlist):
754-
# arglist only: \macro{name}{arglist}{return}
756+
# arglist only: \macro{name}{arglist}{retann}
755757
if has_multi_line(child):
756758
self.body.append(CR + r'\pysigwithonelineperarg{')
757759
else:
@@ -857,7 +859,13 @@ def _visit_sig_parameter_list(self, node: Element, parameter_group: type[Element
857859
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
858860

859861
def visit_desc_parameterlist(self, node: Element) -> None:
860-
if not self.has_tp_list:
862+
if self.has_tp_list:
863+
if self.orphan_tp_list:
864+
# close type parameters list (#2)
865+
self.body.append('}{')
866+
# empty parameters list argument (#3)
867+
return
868+
else:
861869
# close name argument (#1), open parameters list argument (#2)
862870
self.body.append('}{')
863871
self._visit_sig_parameter_list(node, addnodes.desc_parameter)

tests/roots/test-domain-py-python_maximum_signature_line_length/index.rst

+13
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,16 @@ domain-py-maximum_signature_line_length
44
.. py:function:: hello(name: str) -> str
55
66
.. py:function:: foo([a, [b, ]]c, d[, e, f])
7+
8+
.. py:function:: generic_arg[T]
9+
10+
.. py:function:: generic_foo[T]()
11+
12+
.. py:function:: generic_bar[T](x: list[T])
13+
14+
.. py:function:: generic_ret[R]() -> R
15+
16+
.. py:class:: MyGenericClass[X]
17+
18+
.. py:class:: MyList[T](list[T])
19+

tests/test_builders/test_build_html.py

+31
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,34 @@ def handler(app):
376376

377377
file = os.fsdecode(target)
378378
assert f'WARNING: cannot copy image file {file!r}: {file!s} does not exist' == ws[-1]
379+
380+
381+
@pytest.mark.sphinx('html', testroot='domain-py-python_maximum_signature_line_length',
382+
confoverrides={'python_maximum_signature_line_length': 1})
383+
def test_html_pep_695_one_type_per_line(app, cached_etree_parse):
384+
app.build()
385+
fname = app.outdir / 'index.html'
386+
etree = cached_etree_parse(fname)
387+
388+
class chk:
389+
def __init__(self, expect):
390+
self.expect = expect
391+
392+
def __call__(self, nodes):
393+
assert len(nodes) == 1, nodes
394+
objnode = ''.join(nodes[0].itertext()).replace('\n\n', '')
395+
objnode = objnode.rstrip(chr(182)) # remove '¶' symbol
396+
objnode = objnode.strip('\n') # remove surrounding new lines
397+
assert objnode == self.expect
398+
399+
# each signature has a dangling ',' at the end of its parameters lists
400+
check_xpath(etree, fname, r'.//dt[@id="generic_foo"][1]',
401+
chk('generic_foo[\nT,\n]()'))
402+
check_xpath(etree, fname, r'.//dt[@id="generic_bar"][1]',
403+
chk('generic_bar[\nT,\n](\nx: list[T],\n)'))
404+
check_xpath(etree, fname, r'.//dt[@id="generic_ret"][1]',
405+
chk('generic_ret[\nR,\n]() → R'))
406+
check_xpath(etree, fname, r'.//dt[@id="MyGenericClass"][1]',
407+
chk('class MyGenericClass[\nX,\n]'))
408+
check_xpath(etree, fname, r'.//dt[@id="MyList"][1]',
409+
chk('class MyList[\nT,\n](list[T])'))

tests/test_builders/test_build_latex.py

+22
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,28 @@ def test_one_parameter_per_line(app, status, warning):
17601760

17611761
assert ('\\pysigwithonelineperarg{\\sphinxbfcode{\\sphinxupquote{foo}}}' in result)
17621762

1763+
# generic_arg[T]
1764+
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_arg}}}'
1765+
'{\\sphinxtypeparam{\\DUrole{n}{T}}}{}{}' in result)
1766+
1767+
# generic_foo[T]()
1768+
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_foo}}}' in result)
1769+
1770+
# generic_bar[T](x: list[T])
1771+
assert ('\\pysigwithonelineperargwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_bar}}}' in result)
1772+
1773+
# generic_ret[R]() -> R
1774+
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_ret}}}'
1775+
'{\\sphinxtypeparam{\\DUrole{n}{R}}}{}{{ $\\rightarrow$ R}}' in result)
1776+
1777+
# MyGenericClass[X]
1778+
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ '
1779+
'}}}\\sphinxbfcode{\\sphinxupquote{MyGenericClass}}}' in result)
1780+
1781+
# MyList[T](list[T])
1782+
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ '
1783+
'}}}\\sphinxbfcode{\\sphinxupquote{MyList}}}' in result)
1784+
17631785

17641786
@pytest.mark.sphinx('latex', testroot='markup-rubric')
17651787
def test_latex_rubric(app):

tests/test_domains/test_domain_py.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ def test_function_pep_695(app):
753753
S,\
754754
T: int,\
755755
U: (int, str),\
756-
R: int | int,\
756+
R: int | str,\
757757
A: int | Annotated[int, ctype("char")],\
758758
*V,\
759759
**P\
@@ -795,7 +795,7 @@ def test_function_pep_695(app):
795795
desc_sig_space,
796796
[desc_sig_punctuation, '|'],
797797
desc_sig_space,
798-
[pending_xref, 'int'],
798+
[pending_xref, 'str'],
799799
)],
800800
)],
801801
[desc_type_parameter, (

0 commit comments

Comments
 (0)