Skip to content

Commit 7838043

Browse files
koyuki7wAA-Turner
andauthored
Support annotations and default values in _pseudo_parse_arglist (#13536)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent c6e39d8 commit 7838043

File tree

5 files changed

+105
-17
lines changed

5 files changed

+105
-17
lines changed

sphinx/domains/javascript.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,9 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
137137
_pseudo_parse_arglist(
138138
signode,
139139
arglist,
140-
multi_line_parameter_list,
141-
trailing_comma,
140+
multi_line_parameter_list=multi_line_parameter_list,
141+
trailing_comma=trailing_comma,
142+
env=self.env,
142143
)
143144
return fullname, prefix
144145

sphinx/domains/python/_annotations.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,15 +552,18 @@ def _keyword_only_separator() -> addnodes.desc_parameter:
552552
def _pseudo_parse_arglist(
553553
signode: desc_signature,
554554
arglist: str,
555+
*,
555556
multi_line_parameter_list: bool = False,
556557
trailing_comma: bool = True,
558+
env: BuildEnvironment,
557559
) -> None:
558560
"""'Parse' a list of arguments separated by commas.
559561
560562
Arguments can have "optional" annotations given by enclosing them in
561563
brackets. Currently, this will split at any comma, even if it's inside a
562564
string literal (e.g. default argument value).
563565
"""
566+
# TODO: decompose 'env' parameter into only the required bits
564567
paramlist = addnodes.desc_parameterlist()
565568
paramlist['multi_line_parameter_list'] = multi_line_parameter_list
566569
paramlist['multi_line_trailing_comma'] = trailing_comma
@@ -583,9 +586,30 @@ def _pseudo_parse_arglist(
583586
ends_open += 1
584587
argument = argument[:-1].strip()
585588
if argument:
586-
stack[-1] += addnodes.desc_parameter(
587-
'', '', addnodes.desc_sig_name(argument, argument)
588-
)
589+
param_with_annotation, _, default_value = argument.partition('=')
590+
param_name, _, annotation = param_with_annotation.partition(':')
591+
del param_with_annotation
592+
593+
node = addnodes.desc_parameter()
594+
node += addnodes.desc_sig_name('', param_name.strip())
595+
if annotation:
596+
children = _parse_annotation(annotation.strip(), env=env)
597+
node += addnodes.desc_sig_punctuation('', ':')
598+
node += addnodes.desc_sig_space()
599+
node += addnodes.desc_sig_name('', '', *children) # type: ignore[arg-type]
600+
if default_value:
601+
if annotation:
602+
node += addnodes.desc_sig_space()
603+
node += addnodes.desc_sig_operator('', '=')
604+
if annotation:
605+
node += addnodes.desc_sig_space()
606+
node += nodes.inline(
607+
'',
608+
default_value.strip(),
609+
classes=['default_value'],
610+
support_smartquotes=False,
611+
)
612+
stack[-1] += node
589613
while ends_open:
590614
stack.append(addnodes.desc_optional())
591615
stack[-2] += stack[-1]

sphinx/domains/python/_object.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,8 +363,9 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
363363
_pseudo_parse_arglist(
364364
signode,
365365
arglist,
366-
multi_line_parameter_list,
367-
trailing_comma,
366+
multi_line_parameter_list=multi_line_parameter_list,
367+
trailing_comma=trailing_comma,
368+
env=self.env,
368369
)
369370
except (NotImplementedError, ValueError) as exc:
370371
# duplicated parameter names raise ValueError and not a SyntaxError
@@ -374,8 +375,9 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
374375
_pseudo_parse_arglist(
375376
signode,
376377
arglist,
377-
multi_line_parameter_list,
378-
trailing_comma,
378+
multi_line_parameter_list=multi_line_parameter_list,
379+
trailing_comma=trailing_comma,
380+
env=self.env,
379381
)
380382
else:
381383
if self.needs_arglist():

tests/test_domains/test_domain_py.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,25 @@
3838
from sphinx.testing.util import assert_node
3939
from sphinx.writers.text import STDINDENT
4040

41+
TYPE_CHECKING = False
42+
if TYPE_CHECKING:
43+
from sphinx.application import Sphinx
44+
from sphinx.environment import BuildEnvironment
4145

42-
def parse(sig):
46+
47+
def parse(sig: str, *, env: BuildEnvironment) -> str:
4348
m = py_sig_re.match(sig)
4449
if m is None:
4550
raise ValueError
4651
_name_prefix, _tp_list, _name, arglist, _retann = m.groups()
4752
signode = addnodes.desc_signature(sig, '')
48-
_pseudo_parse_arglist(signode, arglist)
53+
_pseudo_parse_arglist(signode, arglist, env=env)
4954
return signode.astext()
5055

5156

52-
def test_function_signatures() -> None:
53-
rv = parse("compile(source : string, filename, symbol='file')")
54-
assert rv == "(source : string, filename, symbol='file')"
57+
def test_function_signatures(app: Sphinx) -> None:
58+
rv = parse("compile(source : string, filename, symbol='file')", env=app.env)
59+
assert rv == "(source: string, filename, symbol='file')"
5560

5661
for params, expect in [
5762
('(a=1)', '(a=1)'),
@@ -60,17 +65,17 @@ def test_function_signatures() -> None:
6065
('(a=1[, b=None])', '(a=1, [b=None])'),
6166
('(a=[], [b=None])', '(a=[], [b=None])'),
6267
('(a=[][, b=None])', '(a=[], [b=None])'),
63-
('(a: Foo[Bar]=[][, b=None])', '(a: Foo[Bar]=[], [b=None])'),
68+
('(a: Foo[Bar]=[][, b=None])', '(a: Foo[Bar] = [], [b=None])'),
6469
]:
65-
rv = parse(f'func{params}')
70+
rv = parse(f'func{params}', env=app.env)
6671
assert rv == expect
6772

6873
# Note: 'def f[Foo[Bar]]()' is not valid Python but people might write
6974
# it in a reST document to convene the intent of a higher-kinded type
7075
# variable.
7176
for tparams in ['', '[Foo]', '[Foo[Bar]]']:
7277
for retann in ['', '-> Foo', '-> Foo[Bar]', '-> anything else']:
73-
rv = parse(f'func{tparams}{params} {retann}'.rstrip())
78+
rv = parse(f'func{tparams}{params} {retann}'.rstrip(), env=app.env)
7479
assert rv == expect
7580

7681

tests/test_domains/test_domain_py_pyfunction.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
from sphinx.testing import restructuredtext
2828
from sphinx.testing.util import assert_node
2929

30+
TYPE_CHECKING = False
31+
if TYPE_CHECKING:
32+
from sphinx.application import Sphinx
33+
3034

3135
@pytest.mark.sphinx('html', testroot='_blank')
3236
def test_pyfunction(app):
@@ -487,6 +491,58 @@ def test_optional_pyfunction_signature(app):
487491
)
488492

489493

494+
@pytest.mark.sphinx('html', testroot='_blank')
495+
def test_pyfunction_signature_with_bracket(app: Sphinx) -> None:
496+
text = '.. py:function:: hello(a : ~typing.Any = <b>) -> None'
497+
doctree = restructuredtext.parse(app, text)
498+
assert_node(
499+
doctree,
500+
(
501+
addnodes.index,
502+
[
503+
desc,
504+
(
505+
[
506+
desc_signature,
507+
(
508+
[desc_name, 'hello'],
509+
desc_parameterlist,
510+
[desc_returns, pending_xref, 'None'],
511+
),
512+
],
513+
desc_content,
514+
),
515+
],
516+
),
517+
)
518+
assert_node(
519+
doctree[1],
520+
addnodes.desc,
521+
desctype='function',
522+
domain='py',
523+
objtype='function',
524+
no_index=False,
525+
)
526+
assert_node(
527+
doctree[1][0][1], # type: ignore[index]
528+
(
529+
[
530+
desc_parameter,
531+
(
532+
[desc_sig_name, 'a'],
533+
[desc_sig_punctuation, ':'],
534+
desc_sig_space,
535+
[desc_sig_name, pending_xref, 'Any'],
536+
desc_sig_space,
537+
[desc_sig_operator, '='],
538+
desc_sig_space,
539+
[nodes.inline, '<b>'],
540+
),
541+
],
542+
),
543+
)
544+
545+
490546
@pytest.mark.sphinx(
491547
'html',
492548
testroot='root',

0 commit comments

Comments
 (0)