Skip to content

Commit

Permalink
Fix PEP 695 output for classes in the LaTeX builder (#12561)
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz committed Jul 15, 2024
1 parent aeebfab commit 2a30bb6
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ Bugs fixed
Patch by Jakob Lykke Andersen and Adam Turner.
* #11041: linkcheck: Ignore URLs that respond with non-Unicode content.
Patch by James Addison.
* #12543: Fix :pep:`695` formatting for LaTeX output.
Patch by Bénédikt Tran.

Testing
-------
Expand Down
28 changes: 18 additions & 10 deletions sphinx/writers/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,19 +724,21 @@ def has_multi_line(e: Element) -> bool:
return e.get('multi_line_parameter_list')

self.has_tp_list = False
self.orphan_tp_list = False

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

if multi_tp_list:
if multi_arglist:
Expand All @@ -751,7 +753,7 @@ def has_multi_line(e: Element) -> bool:
break

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

def visit_desc_parameterlist(self, node: Element) -> None:
if not self.has_tp_list:
if self.has_tp_list:
if self.orphan_tp_list:
# close type parameters list (#2)
self.body.append('}{')
# empty parameters list argument (#3)
return
else:
# close name argument (#1), open parameters list argument (#2)
self.body.append('}{')
self._visit_sig_parameter_list(node, addnodes.desc_parameter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ domain-py-maximum_signature_line_length
.. py:function:: hello(name: str) -> str
.. py:function:: foo([a, [b, ]]c, d[, e, f])
.. py:function:: generic_arg[T]
.. py:function:: generic_foo[T]()
.. py:function:: generic_bar[T](x: list[T])
.. py:function:: generic_ret[R]() -> R
.. py:class:: MyGenericClass[X]
.. py:class:: MyList[T](list[T])
31 changes: 31 additions & 0 deletions tests/test_builders/test_build_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,34 @@ def handler(app):

file = os.fsdecode(target)
assert f'WARNING: cannot copy image file {file!r}: {file!s} does not exist' == ws[-1]


@pytest.mark.sphinx('html', testroot='domain-py-python_maximum_signature_line_length',
confoverrides={'python_maximum_signature_line_length': 1})
def test_html_pep_695_one_type_per_line(app, cached_etree_parse):
app.build()
fname = app.outdir / 'index.html'
etree = cached_etree_parse(fname)

class chk:
def __init__(self, expect):
self.expect = expect

def __call__(self, nodes):
assert len(nodes) == 1, nodes
objnode = ''.join(nodes[0].itertext()).replace('\n\n', '')
objnode = objnode.rstrip(chr(182)) # remove '¶' symbol
objnode = objnode.strip('\n') # remove surrounding new lines
assert objnode == self.expect

# each signature has a dangling ',' at the end of its parameters lists
check_xpath(etree, fname, r'.//dt[@id="generic_foo"][1]',
chk('generic_foo[\nT,\n]()'))
check_xpath(etree, fname, r'.//dt[@id="generic_bar"][1]',
chk('generic_bar[\nT,\n](\nx: list[T],\n)'))
check_xpath(etree, fname, r'.//dt[@id="generic_ret"][1]',
chk('generic_ret[\nR,\n]() → R'))
check_xpath(etree, fname, r'.//dt[@id="MyGenericClass"][1]',
chk('class MyGenericClass[\nX,\n]'))
check_xpath(etree, fname, r'.//dt[@id="MyList"][1]',
chk('class MyList[\nT,\n](list[T])'))
22 changes: 22 additions & 0 deletions tests/test_builders/test_build_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,28 @@ def test_one_parameter_per_line(app, status, warning):

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

# generic_arg[T]
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_arg}}}'
'{\\sphinxtypeparam{\\DUrole{n}{T}}}{}{}' in result)

# generic_foo[T]()
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_foo}}}' in result)

# generic_bar[T](x: list[T])
assert ('\\pysigwithonelineperargwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_bar}}}' in result)

# generic_ret[R]() -> R
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{generic\\_ret}}}'
'{\\sphinxtypeparam{\\DUrole{n}{R}}}{}{{ $\\rightarrow$ R}}' in result)

# MyGenericClass[X]
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ '
'}}}\\sphinxbfcode{\\sphinxupquote{MyGenericClass}}}' in result)

# MyList[T](list[T])
assert ('\\pysiglinewithargsretwithtypelist{\\sphinxbfcode{\\sphinxupquote{class\\DUrole{w}{ '
'}}}\\sphinxbfcode{\\sphinxupquote{MyList}}}' in result)


@pytest.mark.sphinx('latex', testroot='markup-rubric')
def test_latex_rubric(app):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_domains/test_domain_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ def test_function_pep_695(app):
S,\
T: int,\
U: (int, str),\
R: int | int,\
R: int | str,\
A: int | Annotated[int, ctype("char")],\
*V,\
**P\
Expand Down Expand Up @@ -795,7 +795,7 @@ def test_function_pep_695(app):
desc_sig_space,
[desc_sig_punctuation, '|'],
desc_sig_space,
[pending_xref, 'int'],
[pending_xref, 'str'],
)],
)],
[desc_type_parameter, (
Expand Down

0 comments on commit 2a30bb6

Please sign in to comment.