Skip to content

Commit 8bd2fdf

Browse files
ilevkivskyiGuido van Rossum
authored and
Guido van Rossum
committed
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 7cd264a commit 8bd2fdf

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
@@ -1137,11 +1137,12 @@ def leave_class(self) -> None:
11371137
def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
11381138
decorator.accept(self)
11391139
if isinstance(decorator, RefExpr):
1140-
if decorator.fullname in ('typing.runtime', 'typing_extensions.runtime'):
1140+
if decorator.fullname in RUNTIME_PROTOCOL_DECOS:
11411141
if defn.info.is_protocol:
11421142
defn.info.runtime_protocol = True
11431143
else:
1144-
self.fail('@runtime can only be used with protocol classes', defn)
1144+
self.fail('@runtime_checkable can only be used with protocol classes',
1145+
defn)
11451146
elif decorator.fullname in ('typing.final',
11461147
'typing_extensions.final'):
11471148
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

test-data/unit/check-typeddict.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,3 +1600,16 @@ class Point(TypedDict):
16001600
p = Point(x=42, y=1337)
16011601
reveal_type(p) # N: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
16021602
[builtins fixtures/dict.pyi]
1603+
1604+
[case testCanCreateTypedDictWithTypingProper]
1605+
# flags: --python-version 3.8
1606+
from typing import TypedDict
1607+
1608+
class Point(TypedDict):
1609+
x: int
1610+
y: int
1611+
1612+
p = Point(x=42, y=1337)
1613+
reveal_type(p) # N: Revealed type is 'TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})'
1614+
[builtins fixtures/dict.pyi]
1615+
[typing fixtures/typing-full.pyi]

test-data/unit/fixtures/typing-full.pyi

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ NamedTuple = 0
2525
Type = 0
2626
no_type_check = 0
2727
ClassVar = 0
28+
Final = 0
29+
Literal = 0
30+
TypedDict = 0
2831
NoReturn = 0
2932
NewType = 0
3033

@@ -38,23 +41,23 @@ S = TypeVar('S')
3841
# Note: definitions below are different from typeshed, variances are declared
3942
# to silence the protocol variance checks. Maybe it is better to use type: ignore?
4043

41-
@runtime
44+
@runtime_checkable
4245
class Container(Protocol[T_co]):
4346
@abstractmethod
4447
# Use int because bool isn't in the default test builtins
4548
def __contains__(self, arg: object) -> int: pass
4649

47-
@runtime
50+
@runtime_checkable
4851
class Sized(Protocol):
4952
@abstractmethod
5053
def __len__(self) -> int: pass
5154

52-
@runtime
55+
@runtime_checkable
5356
class Iterable(Protocol[T_co]):
5457
@abstractmethod
5558
def __iter__(self) -> 'Iterator[T_co]': pass
5659

57-
@runtime
60+
@runtime_checkable
5861
class Iterator(Iterable[T_co], Protocol):
5962
@abstractmethod
6063
def __next__(self) -> T_co: pass
@@ -88,7 +91,7 @@ class AsyncGenerator(AsyncIterator[T], Generic[T, U]):
8891
@abstractmethod
8992
def __aiter__(self) -> 'AsyncGenerator[T, U]': pass
9093

91-
@runtime
94+
@runtime_checkable
9295
class Awaitable(Protocol[T]):
9396
@abstractmethod
9497
def __await__(self) -> Generator[Any, Any, T]: pass
@@ -106,12 +109,12 @@ class Coroutine(Awaitable[V], Generic[T, U, V]):
106109
@abstractmethod
107110
def close(self) -> None: pass
108111

109-
@runtime
112+
@runtime_checkable
110113
class AsyncIterable(Protocol[T]):
111114
@abstractmethod
112115
def __aiter__(self) -> 'AsyncIterator[T]': pass
113116

114-
@runtime
117+
@runtime_checkable
115118
class AsyncIterator(AsyncIterable[T], Protocol):
116119
def __aiter__(self) -> 'AsyncIterator[T]': return self
117120
@abstractmethod
@@ -137,7 +140,7 @@ class MutableMapping(Mapping[T, U], metaclass=ABCMeta):
137140
class SupportsInt(Protocol):
138141
def __int__(self) -> int: pass
139142

140-
def runtime(cls: T) -> T:
143+
def runtime_checkable(cls: T) -> T:
141144
return cls
142145

143146
class ContextManager(Generic[T]):
@@ -146,3 +149,16 @@ class ContextManager(Generic[T]):
146149
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: pass
147150

148151
TYPE_CHECKING = 1
152+
153+
# Fallback type for all typed dicts (does not exist at runtime).
154+
class _TypedDict(Mapping[str, object]):
155+
# Needed to make this class non-abstract. It is explicitly declared abstract in
156+
# typeshed, but we don't want to import abc here, as it would slow down the tests.
157+
def __iter__(self) -> Iterator[str]: ...
158+
def copy(self: T) -> T: ...
159+
# Using NoReturn so that only calls using the plugin hook can go through.
160+
def setdefault(self, k: NoReturn, default: object) -> object: ...
161+
# Mypy expects that 'default' has a type variable type.
162+
def pop(self, k: NoReturn, default: T = ...) -> object: ...
163+
def update(self: T, __m: T) -> None: ...
164+
def __delitem__(self, k: NoReturn) -> None: ...

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class _SpecialForm:
88
pass
99

1010
Protocol: _SpecialForm = ...
11-
def runtime(x: _T) -> _T: pass
11+
def runtime_checkable(x: _T) -> _T: pass
12+
runtime = runtime_checkable
1213

1314
Final: _SpecialForm = ...
1415
def final(x: _T) -> _T: pass

0 commit comments

Comments
 (0)