Skip to content

Commit 73c69e5

Browse files
authored
Sync mypy with recent runtime updates in typing (#7013)
This introduces the following updates: * Allow using `typing.TypedDict` (still support all the old forms) * Add a test for `typing.Literal` (it was already supported, but there were no tests) * Rename `@runtime` to `@runtime_checkable`, while keeping the alias in `typing_extensions` for backwards compatibility. (Note that `typing.Final` and `typing.Protocol` were already supported and there are tests.) See also python/typeshed#3070
1 parent f34a2d6 commit 73c69e5

File tree

13 files changed

+98
-45
lines changed

13 files changed

+98
-45
lines changed

mypy/message_registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,5 @@
141141

142142
# Protocol
143143
RUNTIME_PROTOCOL_EXPECTED = \
144-
'Only @runtime protocols can be used with instance and class checks' # type: Final
144+
'Only @runtime_checkable protocols can be used with instance and class checks' # type: Final
145145
CANNOT_INSTANTIATE_PROTOCOL = 'Cannot instantiate protocol class "{}"' # type: Final

mypy/newsemanal/semanal.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT,
7474
nongen_builtins, get_member_expr_fullname, REVEAL_TYPE,
7575
REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions,
76-
EnumCallExpr
76+
EnumCallExpr, RUNTIME_PROTOCOL_DECOS
7777
)
7878
from mypy.tvar_scope import TypeVarScope
7979
from mypy.typevars import fill_typevars
@@ -1131,11 +1131,12 @@ def leave_class(self) -> None:
11311131
def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
11321132
decorator.accept(self)
11331133
if isinstance(decorator, RefExpr):
1134-
if decorator.fullname in ('typing.runtime', 'typing_extensions.runtime'):
1134+
if decorator.fullname in RUNTIME_PROTOCOL_DECOS:
11351135
if defn.info.is_protocol:
11361136
defn.info.runtime_protocol = True
11371137
else:
1138-
self.fail('@runtime can only be used with protocol classes', defn)
1138+
self.fail('@runtime_checkable can only be used with protocol classes',
1139+
defn)
11391140
elif decorator.fullname in ('typing.final',
11401141
'typing_extensions.final'):
11411142
defn.info.is_final = True

mypy/newsemanal/semanal_typeddict.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,9 @@ def fail_typeddict_arg(self, message: str,
303303
def build_typeddict_typeinfo(self, name: str, items: List[str],
304304
types: List[Type],
305305
required_keys: Set[str]) -> TypeInfo:
306-
# Prefer typing_extensions if available.
307-
fallback = (self.api.named_type_or_none('typing_extensions._TypedDict', []) or
306+
# Prefer typing then typing_extensions if available.
307+
fallback = (self.api.named_type_or_none('typing._TypedDict', []) or
308+
self.api.named_type_or_none('typing_extensions._TypedDict', []) or
308309
self.api.named_type_or_none('mypy_extensions._TypedDict', []))
309310
assert fallback is not None
310311
info = self.api.basic_new_typeinfo(name, fallback)

mypy/nodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ def get_column(self) -> int:
143143
'builtins.enumerate': ''} # type: Final
144144
nongen_builtins.update((name, alias) for alias, name in type_aliases.items())
145145

146+
RUNTIME_PROTOCOL_DECOS = ('typing.runtime_checkable',
147+
'typing_extensions.runtime',
148+
'typing_extensions.runtime_checkable') # type: Final
149+
146150

147151
class Node(Context):
148152
"""Common base class for all non-type parse tree nodes."""

mypy/semanal.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr,
5757
IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName, OverloadPart,
5858
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, nongen_builtins,
59-
get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, is_final_node
59+
get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, is_final_node,
60+
RUNTIME_PROTOCOL_DECOS,
6061
)
6162
from mypy.tvar_scope import TypeVarScope
6263
from mypy.typevars import fill_typevars
@@ -945,11 +946,12 @@ def leave_class(self) -> None:
945946
def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
946947
decorator.accept(self)
947948
if isinstance(decorator, RefExpr):
948-
if decorator.fullname in ('typing.runtime', 'typing_extensions.runtime'):
949+
if decorator.fullname in RUNTIME_PROTOCOL_DECOS:
949950
if defn.info.is_protocol:
950951
defn.info.runtime_protocol = True
951952
else:
952-
self.fail('@runtime can only be used with protocol classes', defn)
953+
self.fail('@runtime_checkable can only be used with protocol classes',
954+
defn)
953955
elif decorator.fullname in ('typing.final',
954956
'typing_extensions.final'):
955957
defn.info.is_final = True

mypy/semanal_typeddict.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,9 @@ def fail_typeddict_arg(self, message: str,
271271
def build_typeddict_typeinfo(self, name: str, items: List[str],
272272
types: List[Type],
273273
required_keys: Set[str]) -> TypeInfo:
274-
# Prefer typing_extensions if available.
275-
fallback = (self.api.named_type_or_none('typing_extensions._TypedDict', []) or
274+
# Prefer typing then typing_extensions if available.
275+
fallback = (self.api.named_type_or_none('typing._TypedDict', []) or
276+
self.api.named_type_or_none('typing_extensions._TypedDict', []) or
276277
self.api.named_type_or_none('mypy_extensions._TypedDict', []))
277278
assert fallback is not None
278279
info = self.api.basic_new_typeinfo(name, fallback)

mypy/types.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@
7070
)
7171

7272
# Supported names of TypedDict type constructors.
73-
TPDICT_NAMES = ('mypy_extensions.TypedDict', 'typing_extensions.TypedDict') # type: Final
73+
TPDICT_NAMES = ('typing.TypedDict',
74+
'typing_extensions.TypedDict',
75+
'mypy_extensions.TypedDict') # type: Final
7476

7577
# Supported fallback instance type names for TypedDict types.
76-
TPDICT_FB_NAMES = ('mypy_extensions._TypedDict', 'typing_extensions._TypedDict') # type: Final
78+
TPDICT_FB_NAMES = ('typing._TypedDict',
79+
'typing_extensions._TypedDict',
80+
'mypy_extensions._TypedDict') # type: Final
7781

7882
# A placeholder used for Bogus[...] parameters
7983
_dummy = object() # type: Final[Any]

test-data/unit/check-literal.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ reveal_type(f) # N: Revealed type is 'def (x: Any)'
3636
reveal_type(g) # N: Revealed type is 'def (x: Literal['A['])'
3737
[out]
3838

39+
[case testLiteralFromTypingWorks]
40+
from typing import Literal
41+
42+
x: Literal[42]
43+
x = 43 # E: Incompatible types in assignment (expression has type "Literal[43]", variable has type "Literal[42]")
44+
45+
y: Literal[43]
46+
y = 43
47+
[typing fixtures/typing-full.pyi]
48+
3949
[case testLiteralParsingPython2]
4050
# flags: --python-version 2.7
4151
from typing import Optional

test-data/unit/check-protocols.test

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,9 @@ class B2(P2):
359359
x2: P2 = B2() # OK
360360

361361
[case testProtocolAndRuntimeAreDefinedAlsoInTypingExtensions]
362-
from typing_extensions import Protocol, runtime
362+
from typing_extensions import Protocol, runtime_checkable
363363

364-
@runtime
364+
@runtime_checkable
365365
class P(Protocol):
366366
def meth(self) -> int:
367367
pass
@@ -1296,9 +1296,9 @@ reveal_type(last(L[int]())) # N: Revealed type is '__main__.Box*[builtins.int*]'
12961296
reveal_type(last(L[str]()).content) # N: Revealed type is 'builtins.str*'
12971297

12981298
[case testOverloadOnProtocol]
1299-
from typing import overload, Protocol, runtime
1299+
from typing import overload, Protocol, runtime_checkable
13001300

1301-
@runtime
1301+
@runtime_checkable
13021302
class P1(Protocol):
13031303
attr1: int
13041304
class P2(Protocol):
@@ -1317,7 +1317,7 @@ def f(x: P2) -> str: ...
13171317
def f(x):
13181318
if isinstance(x, P1):
13191319
return P1.attr1
1320-
if isinstance(x, P2): # E: Only @runtime protocols can be used with instance and class checks
1320+
if isinstance(x, P2): # E: Only @runtime_checkable protocols can be used with instance and class checks
13211321
return P1.attr2
13221322

13231323
reveal_type(f(C1())) # N: Revealed type is 'builtins.int'
@@ -1480,28 +1480,28 @@ class C(Protocol):
14801480
Logger.log(cls) #OK for classmethods
14811481
[builtins fixtures/classmethod.pyi]
14821482

1483-
-- isinstance() with @runtime protocols
1484-
-- ------------------------------------
1483+
-- isinstance() with @runtime_checkable protocols
1484+
-- ----------------------------------------------
14851485

14861486
[case testSimpleRuntimeProtocolCheck]
1487-
from typing import Protocol, runtime
1487+
from typing import Protocol, runtime_checkable
14881488

1489-
@runtime
1490-
class C: # E: @runtime can only be used with protocol classes
1489+
@runtime_checkable
1490+
class C: # E: @runtime_checkable can only be used with protocol classes
14911491
pass
14921492

14931493
class P(Protocol):
14941494
def meth(self) -> None:
14951495
pass
14961496

1497-
@runtime
1497+
@runtime_checkable
14981498
class R(Protocol):
14991499
def meth(self) -> int:
15001500
pass
15011501

15021502
x: object
15031503

1504-
if isinstance(x, P): # E: Only @runtime protocols can be used with instance and class checks
1504+
if isinstance(x, P): # E: Only @runtime_checkable protocols can be used with instance and class checks
15051505
reveal_type(x) # N: Revealed type is '__main__.P'
15061506

15071507
if isinstance(x, R):
@@ -1521,19 +1521,19 @@ if isinstance(x, Iterable):
15211521
[typing fixtures/typing-full.pyi]
15221522

15231523
[case testConcreteClassesInProtocolsIsInstance]
1524-
from typing import Protocol, runtime, TypeVar, Generic
1524+
from typing import Protocol, runtime_checkable, TypeVar, Generic
15251525

15261526
T = TypeVar('T')
15271527

1528-
@runtime
1528+
@runtime_checkable
15291529
class P1(Protocol):
15301530
def meth1(self) -> int:
15311531
pass
1532-
@runtime
1532+
@runtime_checkable
15331533
class P2(Protocol):
15341534
def meth2(self) -> int:
15351535
pass
1536-
@runtime
1536+
@runtime_checkable
15371537
class P(P1, P2, Protocol):
15381538
pass
15391539

@@ -1581,15 +1581,15 @@ else:
15811581
[typing fixtures/typing-full.pyi]
15821582

15831583
[case testConcreteClassesUnionInProtocolsIsInstance]
1584-
from typing import Protocol, runtime, TypeVar, Generic, Union
1584+
from typing import Protocol, runtime_checkable, TypeVar, Generic, Union
15851585

15861586
T = TypeVar('T')
15871587

1588-
@runtime
1588+
@runtime_checkable
15891589
class P1(Protocol):
15901590
def meth1(self) -> int:
15911591
pass
1592-
@runtime
1592+
@runtime_checkable
15931593
class P2(Protocol):
15941594
def meth2(self) -> int:
15951595
pass
@@ -2193,12 +2193,12 @@ y: PBad = None # E: Incompatible types in assignment (expression has type "None
21932193
[out]
21942194

21952195
[case testOnlyMethodProtocolUsableWithIsSubclass]
2196-
from typing import Protocol, runtime, Union, Type
2197-
@runtime
2196+
from typing import Protocol, runtime_checkable, Union, Type
2197+
@runtime_checkable
21982198
class P(Protocol):
21992199
def meth(self) -> int:
22002200
pass
2201-
@runtime
2201+
@runtime_checkable
22022202
class PBad(Protocol):
22032203
x: str
22042204

0 commit comments

Comments
 (0)