Skip to content

Commit f67c3ee

Browse files
brandtbuchermsullivan
authored andcommitted
Basic support for typing_extensions.Annotated. (#7292)
Closes #7021. This adds basic support for `typing_extensions.Annotated`. Currently, we just discard all annotations and resolve to the inner type. Plugins can use `get_type_analyze_hook` to try to intervene before this step, but most of the arguments that aren't just names or `Literal`-able values will have already been mangled by this point. Nothing other than the type sees any sort of validation, either. See the issue discussion for more on this.
1 parent e0a5b2e commit f67c3ee

File tree

5 files changed

+82
-0
lines changed

5 files changed

+82
-0
lines changed

mypy/newsemanal/typeanal.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
'typing.Union',
4242
'typing.Literal',
4343
'typing_extensions.Literal',
44+
'typing_extensions.Annotated',
4445
} # type: Final
4546

4647
ARG_KINDS_BY_CONSTRUCTOR = {
@@ -302,6 +303,12 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
302303
return UninhabitedType(is_noreturn=True)
303304
elif fullname in ('typing_extensions.Literal', 'typing.Literal'):
304305
return self.analyze_literal_type(t)
306+
elif fullname == 'typing_extensions.Annotated':
307+
if len(t.args) < 2:
308+
self.fail("Annotated[...] must have exactly one type argument"
309+
" and at least one annotation", t)
310+
return AnyType(TypeOfAny.from_error)
311+
return self.anal_type(t.args[0])
305312
return None
306313

307314
def get_omitted_any(self, typ: Type, fullname: Optional[str] = None) -> AnyType:

mypy/test/testcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
'check-newsemanal.test',
8787
'check-inline-config.test',
8888
'check-reports.test',
89+
'check-annotated.test',
8990
]
9091

9192
# Tests that use Python 3.8-only AST features (like expression-scoped ignores):

mypy/typeanal.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
'typing.Union',
4141
'typing.Literal',
4242
'typing_extensions.Literal',
43+
'typing_extensions.Annotated',
4344
} # type: Final
4445

4546
ARG_KINDS_BY_CONSTRUCTOR = {
@@ -312,6 +313,12 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
312313
return UninhabitedType(is_noreturn=True)
313314
elif fullname in ('typing_extensions.Literal', 'typing.Literal'):
314315
return self.analyze_literal_type(t)
316+
elif fullname == 'typing_extensions.Annotated':
317+
if len(t.args) < 2:
318+
self.fail("Annotated[...] must have exactly one type argument"
319+
" and at least one annotation", t)
320+
return AnyType(TypeOfAny.from_error)
321+
return self.anal_type(t.args[0])
315322
return None
316323

317324
def analyze_unbound_type_with_type_info(self, t: UnboundType, info: TypeInfo) -> Type:

test-data/unit/check-annotated.test

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
[case testAnnotated0]
2+
from typing_extensions import Annotated
3+
x: Annotated[int, ...]
4+
reveal_type(x) # N: Revealed type is 'builtins.int'
5+
6+
[case testAnnotated1]
7+
from typing import Union
8+
from typing_extensions import Annotated
9+
x: Annotated[Union[int, str], ...]
10+
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'
11+
12+
[case testAnnotated2]
13+
from typing_extensions import Annotated
14+
x: Annotated[int, THESE, ARE, IGNORED, FOR, NOW]
15+
reveal_type(x) # N: Revealed type is 'builtins.int'
16+
17+
[case testAnnotated3]
18+
from typing_extensions import Annotated
19+
x: Annotated[int, -+~12.3, "som"[e], more(anno+a+ions, that=[are]), (b"ignored",), 4, N.O.W, ...]
20+
reveal_type(x) # N: Revealed type is 'builtins.int'
21+
22+
[case testAnnotatedBadType]
23+
from typing_extensions import Annotated
24+
x: Annotated[XXX, ...] # E: Name 'XXX' is not defined
25+
reveal_type(x) # N: Revealed type is 'Any'
26+
27+
[case testAnnotatedBadNoArgs]
28+
from typing_extensions import Annotated
29+
x: Annotated # E: Annotated[...] must have exactly one type argument and at least one annotation
30+
reveal_type(x) # N: Revealed type is 'Any'
31+
32+
[case testAnnotatedBadOneArg]
33+
from typing_extensions import Annotated
34+
x: Annotated[int] # E: Annotated[...] must have exactly one type argument and at least one annotation
35+
reveal_type(x) # N: Revealed type is 'Any'
36+
37+
[case testAnnotatedNested0]
38+
from typing_extensions import Annotated
39+
x: Annotated[Annotated[int, ...], ...]
40+
reveal_type(x) # N: Revealed type is 'builtins.int'
41+
42+
[case testAnnotatedNested1]
43+
from typing import Union
44+
from typing_extensions import Annotated
45+
x: Annotated[Annotated[Union[int, str], ...], ...]
46+
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'
47+
48+
[case testAnnotatedNestedBadType]
49+
from typing_extensions import Annotated
50+
x: Annotated[Annotated[XXX, ...], ...] # E: Name 'XXX' is not defined
51+
reveal_type(x) # N: Revealed type is 'Any'
52+
53+
[case testAnnotatedNestedBadNoArgs]
54+
from typing_extensions import Annotated
55+
x: Annotated[Annotated, ...] # E: Annotated[...] must have exactly one type argument and at least one annotation
56+
reveal_type(x) # N: Revealed type is 'Any'
57+
58+
[case testAnnotatedNestedBadOneArg]
59+
from typing_extensions import Annotated
60+
x: Annotated[Annotated[int], ...] # E: Annotated[...] must have exactly one type argument and at least one annotation
61+
reveal_type(x) # N: Revealed type is 'Any'
62+
63+
[case testAnnotatedNoImport]
64+
x: Annotated[int, ...] # E: Name 'Annotated' is not defined
65+
reveal_type(x) # N: Revealed type is 'Any'

test-data/unit/lib-stub/typing_extensions.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def final(x: _T) -> _T: pass
1616

1717
Literal: _SpecialForm = ...
1818

19+
Annotated: _SpecialForm = ...
20+
1921

2022
# Fallback type for all typed dicts (does not exist at runtime).
2123
class _TypedDict(Mapping[str, object]):

0 commit comments

Comments
 (0)