diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6df02870d104..c0614a7a8bc9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -44,6 +44,7 @@ LITERAL_TYPE, REVEAL_LOCALS, REVEAL_TYPE, + RUNTIME_PROTOCOL_DECOS, UNBOUND_IMPORTED, ArgKind, AssertTypeExpr, @@ -772,9 +773,16 @@ def check_runtime_protocol_test(self, e: CallExpr) -> None: and tp.is_type_obj() and tp.type_object().is_protocol and not tp.type_object().runtime_protocol + and not self._is_runtime_checkable_call(expr) ): self.chk.fail(message_registry.RUNTIME_PROTOCOL_EXPECTED, e) + def _is_runtime_checkable_call(self, expr: Expression) -> bool: + """Check if expr is a call to runtime_checkable() wrapping a protocol.""" + if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr): + return expr.callee.fullname in RUNTIME_PROTOCOL_DECOS + return False + def check_protocol_issubclass(self, e: CallExpr) -> None: for expr in mypy.checker.flatten(e.args[1]): tp = get_proper_type(self.chk.lookup_type(expr)) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 447989082088..7323be061e29 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1718,6 +1718,24 @@ if isinstance(x, R): [builtins fixtures/isinstance.pyi] [typing fixtures/typing-full.pyi] +[case testRuntimeCheckableCalledAsFunctionInIsinstance] +from typing import Protocol, runtime_checkable + +class P(Protocol): + def meth(self) -> None: + pass + +x: object + +# runtime_checkable() used as a function call rather than a decorator +if isinstance(x, runtime_checkable(P)): + reveal_type(x) # N: Revealed type is "__main__.P" + +if issubclass(type(x), runtime_checkable(P)): + pass +[builtins fixtures/isinstance.pyi] +[typing fixtures/typing-full.pyi] + [case testRuntimeIterableProtocolCheck] from typing import Iterable, List, Union