|
39 | 39 | logger = logging.getLogger(__name__)
|
40 | 40 |
|
41 | 41 |
|
42 |
| -# REs for Python signatures |
| 42 | +# REs for Python signatures (supports PEP 695) |
43 | 43 | py_sig_re = re.compile(
|
44 | 44 | r'''^ ([\w.]*\.)? # class name(s)
|
45 | 45 | (\w+) \s* # thing name
|
| 46 | + (?: \[\s*(.*)\s*])? # optional: generics (PEP 695) |
46 | 47 | (?: \(\s*(.*)\s*\) # optional: arguments
|
47 | 48 | (?:\s* -> \s* (.*))? # return annotation
|
48 | 49 | )? $ # and nothing more
|
@@ -257,6 +258,48 @@ def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
|
257 | 258 | return [type_to_xref(annotation, env)]
|
258 | 259 |
|
259 | 260 |
|
| 261 | +def _parse_tplist( |
| 262 | + tplist: str, env: BuildEnvironment | None = None, |
| 263 | + multi_line_parameter_list: bool = False, |
| 264 | +) -> addnodes.desc_tparameterlist: |
| 265 | + """Parse a list of type parameters according to PEP 695.""" |
| 266 | + tparams = addnodes.desc_tparameterlist(tplist) |
| 267 | + tparams['multi_line_parameter_list'] = multi_line_parameter_list |
| 268 | + sig = signature_from_str('(%s)' % tplist) |
| 269 | + # formal parameter names are interpreted as type parameter names and |
| 270 | + # type annotations are interpreted as type parameter bounds |
| 271 | + for tparam in sig.parameters.values(): |
| 272 | + node = addnodes.desc_parameter() |
| 273 | + if tparam.kind == tparam.VAR_POSITIONAL: |
| 274 | + node += addnodes.desc_sig_operator('', '*') |
| 275 | + node += addnodes.desc_sig_name('', tparam.name) |
| 276 | + elif tparam.kind == tparam.VAR_KEYWORD: |
| 277 | + node += addnodes.desc_sig_operator('', '**') |
| 278 | + node += addnodes.desc_sig_name('', tparam.name) |
| 279 | + else: |
| 280 | + node += addnodes.desc_sig_name('', tparam.name) |
| 281 | + if tparam.annotation is not tparam.empty: |
| 282 | + type_bound = _parse_annotation(tparam.annotation, env) |
| 283 | + if not type_bound: |
| 284 | + continue |
| 285 | + |
| 286 | + node += addnodes.desc_sig_punctuation('', ':') |
| 287 | + node += addnodes.desc_sig_space() |
| 288 | + |
| 289 | + type_bound_expr = addnodes.desc_sig_name('', '', *type_bound) # type: ignore |
| 290 | + |
| 291 | + # add delimiters around type bounds written as e.g., "(T1, T2)" |
| 292 | + if tparam.annotation.startswith('(') and tparam.annotation.endswith(')'): |
| 293 | + node += addnodes.desc_sig_punctuation('', '(') |
| 294 | + node += type_bound_expr |
| 295 | + node += addnodes.desc_sig_punctuation('', ')') |
| 296 | + else: |
| 297 | + node += type_bound_expr |
| 298 | + |
| 299 | + tparams += node |
| 300 | + return tparams |
| 301 | + |
| 302 | + |
260 | 303 | def _parse_arglist(
|
261 | 304 | arglist: str, env: BuildEnvironment | None = None, multi_line_parameter_list: bool = False,
|
262 | 305 | ) -> addnodes.desc_parameterlist:
|
@@ -514,7 +557,7 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
|
514 | 557 | m = py_sig_re.match(sig)
|
515 | 558 | if m is None:
|
516 | 559 | raise ValueError
|
517 |
| - prefix, name, arglist, retann = m.groups() |
| 560 | + prefix, name, tplist, arglist, retann = m.groups() |
518 | 561 |
|
519 | 562 | # determine module and class name (if applicable), as well as full name
|
520 | 563 | modname = self.options.get('module', self.env.ref_context.get('py:module'))
|
@@ -570,6 +613,13 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
|
570 | 613 | signode += addnodes.desc_addname(nodetext, nodetext)
|
571 | 614 |
|
572 | 615 | signode += addnodes.desc_name(name, name)
|
| 616 | + |
| 617 | + if tplist: |
| 618 | + try: |
| 619 | + signode += _parse_tplist(tplist, self.env, multi_line_parameter_list) |
| 620 | + except SyntaxError: |
| 621 | + pass |
| 622 | + |
573 | 623 | if arglist:
|
574 | 624 | try:
|
575 | 625 | signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
|
|
0 commit comments