Support callables in `Annotated` types (#12625)

This commit is contained in:
Adam Turner 2024-07-20 11:16:33 +01:00 committed by GitHub
parent 1ed4ca7e03
commit dd77f85149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 3 deletions

View File

@ -8,6 +8,9 @@ Bugs fixed
Patch by Adam Turner.
* #12620: Ensure that old-style object description options are respected.
Patch by Adam Turner.
* #12601, #12625: Support callable objects in :py:class:`~typing.Annotated` type
metadata in the Python domain.
Patch by Adam Turner.
Release 7.4.6 (released Jul 18, 2024)
=====================================

View File

@ -161,7 +161,29 @@ def _parse_annotation(annotation: str, env: BuildEnvironment) -> list[Node]:
addnodes.desc_sig_punctuation('', ')')]
return result
raise SyntaxError # unsupported syntax
if isinstance(node, ast.Call):
# Call nodes can be used in Annotated type metadata,
# for example Annotated[str, ArbitraryTypeValidator(str, len=10)]
args = []
for arg in node.args:
args += unparse(arg)
args.append(addnodes.desc_sig_punctuation('', ','))
args.append(addnodes.desc_sig_space())
for kwd in node.keywords:
args.append(addnodes.desc_sig_name(kwd.arg, kwd.arg)) # type: ignore[arg-type]
args.append(addnodes.desc_sig_operator('', '='))
args += unparse(kwd.value)
args.append(addnodes.desc_sig_punctuation('', ','))
args.append(addnodes.desc_sig_space())
result = [
*unparse(node.func),
addnodes.desc_sig_punctuation('', '('),
*args[:-2], # skip the final comma and space
addnodes.desc_sig_punctuation('', ')'),
]
return result
msg = f'unsupported syntax: {node}'
raise SyntaxError(msg) # unsupported syntax
def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
subscript = node.slice

View File

@ -370,6 +370,27 @@ def test_parse_annotation(app):
[desc_sig_punctuation, "]"]))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Literal")
# Annotated type with callable gets parsed
doctree = _parse_annotation("Annotated[Optional[str], annotated_types.MaxLen(max_length=10)]", app.env)
assert_node(doctree, (
[pending_xref, 'Annotated'],
[desc_sig_punctuation, '['],
[pending_xref, 'str'],
[desc_sig_space, ' '],
[desc_sig_punctuation, '|'],
[desc_sig_space, ' '],
[pending_xref, 'None'],
[desc_sig_punctuation, ','],
[desc_sig_space, ' '],
[pending_xref, 'annotated_types.MaxLen'],
[desc_sig_punctuation, '('],
[desc_sig_name, 'max_length'],
[desc_sig_operator, '='],
[desc_sig_literal_number, '10'],
[desc_sig_punctuation, ')'],
[desc_sig_punctuation, ']'],
))
def test_parse_annotation_suppress(app):
doctree = _parse_annotation("~typing.Dict[str, str]", app.env)
@ -802,7 +823,22 @@ def test_function_pep_695(app):
[desc_sig_name, 'A'],
[desc_sig_punctuation, ':'],
desc_sig_space,
[desc_sig_name, ([pending_xref, 'int | Annotated[int, ctype("char")]'])],
[desc_sig_name, (
[pending_xref, 'int'],
[desc_sig_space, ' '],
[desc_sig_punctuation, '|'],
[desc_sig_space, ' '],
[pending_xref, 'Annotated'],
[desc_sig_punctuation, '['],
[pending_xref, 'int'],
[desc_sig_punctuation, ','],
[desc_sig_space, ' '],
[pending_xref, 'ctype'],
[desc_sig_punctuation, '('],
[desc_sig_literal_string, "'char'"],
[desc_sig_punctuation, ')'],
[desc_sig_punctuation, ']'],
)],
)],
[desc_type_parameter, (
[desc_sig_operator, '*'],
@ -987,7 +1023,7 @@ def test_class_def_pep_696(app):
('[T:(*Ts)|int]', '[T: (*Ts) | int]'),
('[T:(int|(*Ts))]', '[T: (int | (*Ts))]'),
('[T:((*Ts)|int)]', '[T: ((*Ts) | int)]'),
('[T:Annotated[int,ctype("char")]]', '[T: Annotated[int, ctype("char")]]'),
("[T:Annotated[int,ctype('char')]]", "[T: Annotated[int, ctype('char')]]"),
])
def test_pep_695_and_pep_696_whitespaces_in_bound(app, tp_list, tptext):
text = f'.. py:function:: f{tp_list}()'