← Back to index

protocols_runtime_checkable.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 2
FP: 0
FN: 4
Optional: 0 / 0
1"""
2Tests the handling of the @runtime_checkable decorator for protocols.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/protocol.html#runtime-checkable-decorator-and-narrowing-types-by-isinstance
6
7# > A protocol can be used as a second argument in isinstance() and
8# > issubclass() only if it is explicitly opt-in by @runtime_checkable decorator.
9
10from typing import Any, Protocol, runtime_checkable
13class Proto1(Protocol):
14 name: str
17@runtime_checkable
18class Proto2(Protocol):
19 name: str
22def func1(a: Any):
23 if isinstance(a, Proto1): # E: not runtime_checkable
[isinstance-against-protocol] Class `Proto1` cannot be used as the second argument to `isinstance`: This call will raise `TypeError` at runtime
24 return
26 if isinstance(a, Proto2): # OK
27 return
30# > isinstance() can be used with both data and non-data protocols, while
31# > issubclass() can be used only with non-data protocols.
34@runtime_checkable
35class DataProtocol(Protocol):
36 name: str
38 def method1(self) -> int:
39 ...
42@runtime_checkable
43class NonDataProtocol(Protocol):
44 def method1(self) -> int:
45 ...
48def func2(a: Any):
49 if isinstance(a, DataProtocol): # OK
50 return
52 if isinstance(a, NonDataProtocol): # OK
53 return
55 if issubclass(a, DataProtocol): # E
[isinstance-against-protocol] `DataProtocol` cannot be used as the second argument to `issubclass` as it is a protocol with non-method members
56 return
58 if issubclass(a, NonDataProtocol): # OK
59 return
61 if issubclass(a, (NonDataProtocol, DataProtocol)): # E
Expected a ty diagnostic for this line
62 return
65# > Type checkers should reject an isinstance() or issubclass() call if there
66# > is an unsafe overlap between the type of the first argument and the protocol.
69@runtime_checkable
70class Proto3(Protocol):
71 def method1(self, a: int) -> int:
72 ...
75class Concrete3A:
76 def method1(self, a: str) -> None:
77 pass
80class Concrete3B:
81 method1: int = 1
84def func3():
85 if isinstance(Concrete3A(), Proto2): # OK
86 pass
88 if isinstance(Concrete3A(), Proto3): # E: unsafe overlap
Expected a ty diagnostic for this line
89 pass
91 if isinstance(
92 Concrete3B(), (Proto3, NonDataProtocol) # E: unsafe overlap
Expected a ty diagnostic for this line
93 ):
94 pass
96 if issubclass(Concrete3A, (Proto3, NonDataProtocol)): # E: unsafe overlap
Expected a ty diagnostic for this line
97 pass