Skip to content

Commit dd77f85

Browse files
authored
Support callables in Annotated types (#12625)
1 parent 1ed4ca7 commit dd77f85

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

Diff for: CHANGES.rst

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Bugs fixed
88
Patch by Adam Turner.
99
* #12620: Ensure that old-style object description options are respected.
1010
Patch by Adam Turner.
11+
* #12601, #12625: Support callable objects in :py:class:`~typing.Annotated` type
12+
metadata in the Python domain.
13+
Patch by Adam Turner.
1114

1215
Release 7.4.6 (released Jul 18, 2024)
1316
=====================================

Diff for: sphinx/domains/python/_annotations.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,29 @@ def unparse(node: ast.AST) -> list[Node]:
161161
addnodes.desc_sig_punctuation('', ')')]
162162

163163
return result
164-
raise SyntaxError # unsupported syntax
164+
if isinstance(node, ast.Call):
165+
# Call nodes can be used in Annotated type metadata,
166+
# for example Annotated[str, ArbitraryTypeValidator(str, len=10)]
167+
args = []
168+
for arg in node.args:
169+
args += unparse(arg)
170+
args.append(addnodes.desc_sig_punctuation('', ','))
171+
args.append(addnodes.desc_sig_space())
172+
for kwd in node.keywords:
173+
args.append(addnodes.desc_sig_name(kwd.arg, kwd.arg)) # type: ignore[arg-type]
174+
args.append(addnodes.desc_sig_operator('', '='))
175+
args += unparse(kwd.value)
176+
args.append(addnodes.desc_sig_punctuation('', ','))
177+
args.append(addnodes.desc_sig_space())
178+
result = [
179+
*unparse(node.func),
180+
addnodes.desc_sig_punctuation('', '('),
181+
*args[:-2], # skip the final comma and space
182+
addnodes.desc_sig_punctuation('', ')'),
183+
]
184+
return result
185+
msg = f'unsupported syntax: {node}'
186+
raise SyntaxError(msg) # unsupported syntax
165187

166188
def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
167189
subscript = node.slice

Diff for: tests/test_domains/test_domain_py.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,27 @@ def test_parse_annotation(app):
370370
[desc_sig_punctuation, "]"]))
371371
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Literal")
372372

373+
# Annotated type with callable gets parsed
374+
doctree = _parse_annotation("Annotated[Optional[str], annotated_types.MaxLen(max_length=10)]", app.env)
375+
assert_node(doctree, (
376+
[pending_xref, 'Annotated'],
377+
[desc_sig_punctuation, '['],
378+
[pending_xref, 'str'],
379+
[desc_sig_space, ' '],
380+
[desc_sig_punctuation, '|'],
381+
[desc_sig_space, ' '],
382+
[pending_xref, 'None'],
383+
[desc_sig_punctuation, ','],
384+
[desc_sig_space, ' '],
385+
[pending_xref, 'annotated_types.MaxLen'],
386+
[desc_sig_punctuation, '('],
387+
[desc_sig_name, 'max_length'],
388+
[desc_sig_operator, '='],
389+
[desc_sig_literal_number, '10'],
390+
[desc_sig_punctuation, ')'],
391+
[desc_sig_punctuation, ']'],
392+
))
393+
373394

374395
def test_parse_annotation_suppress(app):
375396
doctree = _parse_annotation("~typing.Dict[str, str]", app.env)
@@ -802,7 +823,22 @@ def test_function_pep_695(app):
802823
[desc_sig_name, 'A'],
803824
[desc_sig_punctuation, ':'],
804825
desc_sig_space,
805-
[desc_sig_name, ([pending_xref, 'int | Annotated[int, ctype("char")]'])],
826+
[desc_sig_name, (
827+
[pending_xref, 'int'],
828+
[desc_sig_space, ' '],
829+
[desc_sig_punctuation, '|'],
830+
[desc_sig_space, ' '],
831+
[pending_xref, 'Annotated'],
832+
[desc_sig_punctuation, '['],
833+
[pending_xref, 'int'],
834+
[desc_sig_punctuation, ','],
835+
[desc_sig_space, ' '],
836+
[pending_xref, 'ctype'],
837+
[desc_sig_punctuation, '('],
838+
[desc_sig_literal_string, "'char'"],
839+
[desc_sig_punctuation, ')'],
840+
[desc_sig_punctuation, ']'],
841+
)],
806842
)],
807843
[desc_type_parameter, (
808844
[desc_sig_operator, '*'],
@@ -987,7 +1023,7 @@ def test_class_def_pep_696(app):
9871023
('[T:(*Ts)|int]', '[T: (*Ts) | int]'),
9881024
('[T:(int|(*Ts))]', '[T: (int | (*Ts))]'),
9891025
('[T:((*Ts)|int)]', '[T: ((*Ts) | int)]'),
990-
('[T:Annotated[int,ctype("char")]]', '[T: Annotated[int, ctype("char")]]'),
1026+
("[T:Annotated[int,ctype('char')]]", "[T: Annotated[int, ctype('char')]]"),
9911027
])
9921028
def test_pep_695_and_pep_696_whitespaces_in_bound(app, tp_list, tptext):
9931029
text = f'.. py:function:: f{tp_list}()'

0 commit comments

Comments
 (0)