Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spurious space in default parameter values that are negative numbers in HTML output. #10550

Closed
WarrenWeckesser opened this issue Jun 13, 2022 · 13 comments · Fixed by #10551
Closed
Labels
Milestone

Comments

@WarrenWeckesser
Copy link

WarrenWeckesser commented Jun 13, 2022

Describe the bug

For several projects, I've noticed a problem in the HTML output of functions that have a parameter with a default value that is a negative number. In the rendered HTML, there is a spurious space between the minus sign and the first digit. A typical example is axis=-1 being rendered as axis=- 1. This issue was originally raised with SciPy.

Here are links to examples in several projects:

SciPy:

NumPy:

Pandas:

Matplotlib:

I wasn't able to find an existing issue for this.

I don't know which versions of Sphinx were used in all those projects, so I don't know if the problem still exists in the latest version of Sphinx. Also, it looks like those projects all use the PyData Sphinx theme, so it is possible that the problem is the theme and not Sphinx itself.

How to Reproduce

See the links.

Expected behavior

No response

Your project

See the links

Screenshots

No response

OS

See the above comments.

Python version

Probably varied; see the links.

Sphinx version

Maybe several; see the links.

Sphinx extensions

No response

Extra tools

No response

Additional context

No response

@AA-Turner
Copy link
Member

Please provide a minimal reproducer project, and additionally test with 'basic' or 'alabaster' to ensure it is not a bug in the pydata theme.

A

@WarrenWeckesser
Copy link
Author

@AA-Turner, sorry the bug report is not more thorough. I probably won't be able to dig into this any further. Perhaps some of the PyData theme maintainers could take a look and check if this issue is more appropriate for that project: ping @choldgraf, @12rambau

@WarrenWeckesser
Copy link
Author

One more data point: the issue also occurs in the Jax documentation, e.g.

According to their 'conf.py', the theme used by Jax is 'sphinx_book_theme'.

@choldgraf
Copy link
Contributor

choldgraf commented Jun 14, 2022

Sounds like a pydata theme issue indeed! Please open an issue there so others can discuss

https://github.com/pydata/pydata-sphinx-theme

Also the book theme inherits from the pydata theme so it'd make sense that they have the same issue

@WarrenWeckesser
Copy link
Author

I have opened a corresponding issue in the pydata theme repo, so I'll close this issue. We can reopen it if the pydata theme devs figure out that the problem is in Sphinx and not the theme.

@choldgraf
Copy link
Contributor

choldgraf commented Jun 14, 2022

Actually I just looked into it a little bit, and I think it might be a bug in autodoc and the pygments styling/structure. Here's the HTML of that section in the pydata theme:

<span class="default_value"><span class="pre">-</span> <span class="pre">inf</span></span>

Importantly, note there is a space between the two span elements. I think that this is generated HTML by autodoc and not theme-specific after all, right?

@AA-Turner
Copy link
Member

@choldgraf do you have the reST source that the snippet was generated from?

A

@AA-Turner AA-Turner reopened this Jun 14, 2022
@choldgraf
Copy link
Contributor

choldgraf commented Jun 14, 2022

Well as one example from SciPy:

@AA-Turner
Copy link
Member

Minimal reproducer:

import shutil
from pathlib import Path

from sphinx.cmd.make_mode import run_make_mode

def write(filename, text): Path(filename).write_text(text, encoding="utf-8")

write("conf.py", '''\
import os, sys
sys.path.insert(0, os.path.abspath(".."))
extensions = ["sphinx.ext.autodoc"]
''')

write("extra_white.py", '''\
def func(axis=-1):
    ...
''')

write("index.rst", '''\
.. autofunction:: extra_white.func
''')

shutil.rmtree("_build", ignore_errors=True)
run_make_mode(["html", ".", "_build", "-T", "-W"])

The spurious extra whitespace is present as Chris notes as a literal space character between the two span elements.

A

@AA-Turner AA-Turner added this to the 5.1.0 milestone Jun 14, 2022
@AA-Turner
Copy link
Member

This is actually a problem in the ast unparser rather than autodoc.

>>> import ast
>>> import sphinx.pycode.ast
>>> default = ast.parse("def func(axis=-1): ...", "<string>").body[0].args.defaults[0]
>>> print(ast.dump(default))
UnaryOp(op=USub(), operand=Constant(value=1))
>>> ast.unparse(default)
'-1'
>>> sphinx.pycode.ast.unparse(default, "def func(axis=-1): ...")
'- 1'

A

@choldgraf
Copy link
Contributor

Oooh this is a good one!

@ezio-melotti
Copy link
Contributor

I just run into this same issue while reviewing python/python-docs-zh-tw#260, since I noticed a read(n=- 1) in the output. I thought it was a translation error but noticed the problem exists in the main Python docs too: https://docs.python.org/3/library/asyncio-stream.html#asyncio.StreamReader.read.

The linked PR seems to fix the issue, however something similar also happens with binary operators (and other arbitrary expressions), such as

.. function:: func(x=2**16)
.. function:: func(x=1<<16)
.. function:: func(x=2**16-1)

which get rendered as

func(x=2 ** 16)
func(x=2 << 16)
func(x=2 ** 16 - 1)

Ideally it would be better if Sphinx just preserved the original spacing, but I understand this might not be easily doable and/or not worth the trouble.
Perhaps ** could be special-cased too since it's usually written without spaces, but the other cases can be left alone, also because they are quite uncommon.


While investigating the problem and before coming across this issue, I also wrote a test that I added in tests/test_domain_py.py (just after test_pyfunction_with_number_literals):

def test_pyfunction_with_signed_numbers(app):
    text = ".. py:function:: hello(age=-1, height=+1)"
    doctree = restructuredtext.parse(app, text)
    assert_node(doctree[1][0][1],
                [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "-1"])],
                                      [desc_parameter, ([desc_sig_name, "height"],
                                                        [desc_sig_operator, "="],
                                                        [nodes.inline, "+1"])])])

I'm not sure if it's worth adding this "higher-level" test to the PR too, but if you think it would be useful let me know -- I can also tweak it to add a few more cases.

@AA-Turner
Copy link
Member

Using stdlib ast:

In [17]: ast.unparse(ast.parse("def func(axis=1<<16): ...", "<string>").body[0].args.defaults[0])
Out[17]: '1 << 16'
In [18]: ast.unparse(ast.parse("def func(axis=2**16): ...", "<string>").body[0].args.defaults[0])
Out[18]: '2 ** 16'

We could similarly special case visit_BinOp for Pow(), LShift() and RShift() but I am not sure. Really the solution is to use a CST parser but the stdlib doesn't have one, and I'm not sure about adding a runtime dependency on LibCST.

A

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants