Skip to main content

Module arglist

Module arglist 

Source
Expand description

Arglist and PEP-695 type-parameter-list parsing for the py domain: the _parse_arglist / _pseudo_parse_arglist / _parse_type_list port (sphinx/domains/python/_annotations.py:254-619, sphinx 9.1.0) plus the signature_from_str grammar (sphinx/util/inspect.py:967-1038) and the multi_line_parameter_list measurement (_object.py:291-312).

Ground truth, cited throughout as [PY §n] / [SIG §n]:

  • [PY] docs/superpowers/plans/2026-09-01-m2-wave4.5-research-spec-py-domain.md (§2.1 arglist node shapes, §2.2 pseudo fallback, §2.4 type parameter lists, §1.6 probe outputs);
  • [SIG] docs/superpowers/plans/2026-09-01-m2-wave4.5-research-spec-signature-config.md (§1.3 measurement, §1.4 attr placement, appendix A.1 matrix).

Expected pformats in the test module are verbatim probe output against the pinned toolchain (sphinx 9.1.0 / docutils 0.22.4, harness3 conventions); cases cited as probe <case> come from the spec’s §1.6/§A.1 blocks or the task-5 probe run (probe task5/<case>, logged in the task-5 report).

§The two render pipelines (trap)

Defaults and annotations do NOT round through CPython’s ast.unparse (which task 3’s expr::unparse mirrors): signature_from_str routes them through sphinx.pycode.ast.unparse — “a greatly cut-down version of ast._Unparser” — whose rules differ observably (probe task5/default_normalized: f(x=0xFF, y=[1,2]) renders 0xFF and [1, 2]):

  • int/float constants keep their SOURCE text via ast.get_source_segment (0xFF, 1_000, 1e5), falling back to repr only when the segment is unavailable;
  • no precedence parentheses at all ((a+b)*c → a + b * c);
  • ** is rendered without surrounding spaces (a**b);
  • unary operators never parenthesize (-(a+b) → -a + b);
  • a u'' string prefix is dropped (repr of the value).

[pycode_unparse] implements those rules over the task-3 PyExpr AST; annotation nodes are still rendered by task 4’s parse_annotation, which re-parses the pycode-normalized string exactly as _parse_annotation(param.annotation) does (_annotations.py:495).

§Documented divergences (all conservative)

  • Expressions outside the task-3 subset (lambdas, comparisons, ternaries, slices, f-strings, starred ** dict unpacks, complex literals) make signature_from_str return SigParseError::Syntax, routing task 6 into the silent pseudo fallback. Sphinx renders most of these (or warns via NotImplementedError/ValueError for a == b, a if b else c, f-strings and {**a}), so the fallback shape — and a missing warning — can diverge for such signatures.
  • A top-level lambda keyword in the arglist is rejected before comma splitting (its parameter commas are not bracket-protected, so no naive split is faithful); sphinx parses it and prints lambda a, b: ....
  • Numeric source recovery maps number tokens to constants in render order and verifies each token re-parses to the same value; when the mapping is ambiguous the whole fragment falls back to repr form (normalized digits) where sphinx would keep source text. visit_Constant recovers its text by AST POSITION (ast.get_source_segment), so render order only has to agree with source order — which it now does everywhere except one shape: ast.Call splits args from keywords, losing their interleaving, so a positional (necessarily a * unpack) written AFTER a keyword argument is rendered before it and consumes the earlier token. The text order still matches sphinx (visit_Call reorders identically), but a non-decimal spelling in such a fragment lands in the repr fallback: f(k=0x1, *a(0x2)) prints f(*a(2), k=1) where sphinx keeps f(*a(0x2), k=0x1). Fixing it needs source spans on PyConst, not a render-order tweak.
  • Type-parameter tokenization errors surface as SigParseError::Syntax with an approximate message where sphinx’s warning embeds the exact tokenize.TokenError text.
  • pseudo_parse_arglist takes (ctx, cfg) in addition to the brief’s (arglist, multi_line): the pseudo path renders annotation xrefs through _parse_annotation and stamps multi_line_trailing_comma from python_trailing_comma_in_multi_line_signatures (_object.py:363-380), neither of which is derivable without them.
  • multi_line_flags measures Python len(sig) — Unicode scalar count — while PySigMatch spans are byte offsets; widths are computed as the char count of the spanned slice, so non-ASCII signatures measure exactly as CPython does.

Structs§

PyParam
One parameter of a parsed arglist. annotation and default are sphinx.pycode.ast.unparse-normalized source strings, exactly what inspect.Parameter.annotation / DefaultValue carry in sphinx (util/inspect.py:1027-1038).
PySigMatch
The py_sig_re match carrier (_object.py:41-50): groups (prefix, name, tp_list, arglist, retann) plus the two inner-text spans the multi-line measurement subtracts (_object.py:300-311). Task 6’s handle_py_signature constructs this from its matcher; spans are BYTE offsets of the group’s inner text within the stripped signature, (0, 0) when the group did not participate (Python’s (-1, -1) span normalizes to width 0 either way).
SingleLineOpts
The two single-line-* directive flags (_object.py:180-181); each suppresses only its own list ([SIG §1.5], probes D1/D2).

Enums§

ParamKind
inspect._ParameterKind, as classified by signature_from_ast (sphinx/util/inspect.py:976-1025).
SigParseError
Why an arglist / type-parameter list failed to parse. The two variants carry sphinx’s two observable failure channels ([PY §1.3 step 5], _object.py:355-381):

Functions§

multi_line_flags
The (multi_line_parameter_list, multi_line_type_parameter_list) pair (_object.py:300-311, [SIG §1.3]): each flag is !single-line-option && (len(sig) - width(other group)) > max_len > 0, strictly greater, with max_len resolved by PySigConfig::max_len. Lengths are Python len() — Unicode scalar counts — computed from the byte spans’ slices; an out-of-range span counts as width 0.
parse_arglist
Port of _parse_arglist (_annotations.py:462-516, [PY §2.1]): a desc_parameterlist carrying multi_line_parameter_list / multi_line_trailing_comma UNCONDITIONALLY ([SIG §1.4]), one desc_parameter per param, / and * separator parameters between kind transitions, and the trailing-/ epilogue when the list ends positional-only.
parse_type_list
Port of _parse_type_list + _TypeParameterListParser (_annotations.py:254-459, [PY §2.4]): a desc_type_parameter_list with the same two multi_line attrs, one desc_type_parameter per param, */** operators for variadics, bounds/constraints after :+space inside a desc_sig_name wrapper (constraints re-parenthesized), and ␣=␣ + default_value inline defaults.
pseudo_parse_arglist
Port of _pseudo_parse_arglist (_annotations.py:541-619, [PY §2.2]): comma-split fallback with [/] push/pop of desc_optional, name[:annotation][=default] partition, = always a bare desc_sig_operator (space-wrapped only when annotated), and — on total bracket imbalance — a fresh paramlist containing the raw arglist as one desc_parameter, with NO multi_line attributes (the one attr-less exception, [SIG §1.4]).
signature_from_str
Parse arglist with the grammar of def func(<arglist>): pass (sphinx/util/inspect.py:967-974): positional-only via /, keyword-only after a bare * or *args, **kwargs last. Defaults and annotations are validated by task 3’s parse_py_expr and rendered by [pycode_unparse]; any rejected fragment or grammar violation is SigParseError::Syntax, and duplicate parameter names — which ast.parse accepts — are SigParseError::Duplicate exactly as inspect.Signature.__init__ raises ValueError after a clean parse.