Skip to content

Commit

Permalink
Basic support for typing_extensions.Annotated. (#7292)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brandtbucher authored and msullivan committed Aug 6, 2019
1 parent e0a5b2e commit f67c3ee
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 0 deletions.
7 changes: 7 additions & 0 deletions mypy/newsemanal/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
'typing.Union',
'typing.Literal',
'typing_extensions.Literal',
'typing_extensions.Annotated',
} # type: Final

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

def get_omitted_any(self, typ: Type, fullname: Optional[str] = None) -> AnyType:
Expand Down
1 change: 1 addition & 0 deletions mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
'check-newsemanal.test',
'check-inline-config.test',
'check-reports.test',
'check-annotated.test',
]

# Tests that use Python 3.8-only AST features (like expression-scoped ignores):
Expand Down
7 changes: 7 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'typing.Union',
'typing.Literal',
'typing_extensions.Literal',
'typing_extensions.Annotated',
} # type: Final

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

def analyze_unbound_type_with_type_info(self, t: UnboundType, info: TypeInfo) -> Type:
Expand Down
65 changes: 65 additions & 0 deletions test-data/unit/check-annotated.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
[case testAnnotated0]
from typing_extensions import Annotated
x: Annotated[int, ...]
reveal_type(x) # N: Revealed type is 'builtins.int'

[case testAnnotated1]
from typing import Union
from typing_extensions import Annotated
x: Annotated[Union[int, str], ...]
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'

[case testAnnotated2]
from typing_extensions import Annotated
x: Annotated[int, THESE, ARE, IGNORED, FOR, NOW]
reveal_type(x) # N: Revealed type is 'builtins.int'

[case testAnnotated3]
from typing_extensions import Annotated
x: Annotated[int, -+~12.3, "som"[e], more(anno+a+ions, that=[are]), (b"ignored",), 4, N.O.W, ...]
reveal_type(x) # N: Revealed type is 'builtins.int'

[case testAnnotatedBadType]
from typing_extensions import Annotated
x: Annotated[XXX, ...] # E: Name 'XXX' is not defined
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedBadNoArgs]
from typing_extensions import Annotated
x: Annotated # E: Annotated[...] must have exactly one type argument and at least one annotation
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedBadOneArg]
from typing_extensions import Annotated
x: Annotated[int] # E: Annotated[...] must have exactly one type argument and at least one annotation
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedNested0]
from typing_extensions import Annotated
x: Annotated[Annotated[int, ...], ...]
reveal_type(x) # N: Revealed type is 'builtins.int'

[case testAnnotatedNested1]
from typing import Union
from typing_extensions import Annotated
x: Annotated[Annotated[Union[int, str], ...], ...]
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'

[case testAnnotatedNestedBadType]
from typing_extensions import Annotated
x: Annotated[Annotated[XXX, ...], ...] # E: Name 'XXX' is not defined
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedNestedBadNoArgs]
from typing_extensions import Annotated
x: Annotated[Annotated, ...] # E: Annotated[...] must have exactly one type argument and at least one annotation
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedNestedBadOneArg]
from typing_extensions import Annotated
x: Annotated[Annotated[int], ...] # E: Annotated[...] must have exactly one type argument and at least one annotation
reveal_type(x) # N: Revealed type is 'Any'

[case testAnnotatedNoImport]
x: Annotated[int, ...] # E: Name 'Annotated' is not defined
reveal_type(x) # N: Revealed type is 'Any'
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/typing_extensions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def final(x: _T) -> _T: pass

Literal: _SpecialForm = ...

Annotated: _SpecialForm = ...


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

0 comments on commit f67c3ee

Please sign in to comment.