From afa72b9d455d021f2cbadc8baad3cf17911fc9ee Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 3 Nov 2024 00:53:53 -0700 Subject: [PATCH 1/2] Disallow calling type on Protocol Fixes #16919, fixes #16890 --- mypy/checkexpr.py | 9 +++++++-- test-data/unit/check-protocols.test | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 577576a4e5f8..c8cfd69eec7f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1802,10 +1802,15 @@ def check_callable_call( if ( callee.is_type_obj() - and (len(arg_types) == 1) + and len(arg_types) == 1 and is_equivalent(callee.ret_type, self.named_type("builtins.type")) ): - callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) + proper_arg = get_proper_type(arg_types[0]) + if isinstance(proper_arg, Instance) and proper_arg.type.is_protocol: + self.msg.fail("Calling type() on a protocol class is unsound", context) + callee = callee.copy_modified(ret_type=self.named_type("builtins.object")) + else: + callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) if callable_node: # Store the inferred callable type. diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 5ed2351e33e6..fb81d0f24c52 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4215,3 +4215,22 @@ def g4(a: Input[bytes], b: Output[str]) -> None: f(a, b) # E: Cannot infer type argument 1 of "f" [builtins fixtures/tuple.pyi] + +[case testTypeCallOnProtocol] +from typing import Protocol + +import mod + +class P(Protocol): + def foo(self) -> None: ... + +a: P = mod +value = type(a) # E: Calling type() on a protocol class is unsound +reveal_type(value) # N: Revealed type is "builtins.object" +reveal_type(value.foo) # E: "object" has no attribute "foo" \ + # N: Revealed type is "Any" + +[file mod.py] +def foo() -> None: ... + +[builtins fixtures/tuple.pyi] From 2dc29e60b223a827bf7c9521e3f2f09d11097140 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 3 Nov 2024 01:31:53 -0700 Subject: [PATCH 2/2] okay, fallback because of primer hits --- mypy/checkexpr.py | 11 +++++++++-- test-data/unit/check-protocols.test | 22 ++++++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c8cfd69eec7f..cfb983f99c37 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1807,8 +1807,15 @@ def check_callable_call( ): proper_arg = get_proper_type(arg_types[0]) if isinstance(proper_arg, Instance) and proper_arg.type.is_protocol: - self.msg.fail("Calling type() on a protocol class is unsound", context) - callee = callee.copy_modified(ret_type=self.named_type("builtins.object")) + callee = callee.copy_modified( + ret_type=UnionType( + [ + self.named_type("builtins.type"), + self.named_type("types.ModuleType"), + AnyType(TypeOfAny.special_form), + ] + ) + ) else: callee = callee.copy_modified(ret_type=TypeType.make_normalized(arg_types[0])) diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index fb81d0f24c52..b9a66cf34792 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4217,19 +4217,29 @@ def g4(a: Input[bytes], b: Output[str]) -> None: [builtins fixtures/tuple.pyi] [case testTypeCallOnProtocol] -from typing import Protocol - -import mod +from typing import Any, Protocol, cast +import types class P(Protocol): def foo(self) -> None: ... +import mod + a: P = mod -value = type(a) # E: Calling type() on a protocol class is unsound -reveal_type(value) # N: Revealed type is "builtins.object" -reveal_type(value.foo) # E: "object" has no attribute "foo" \ +value = type(a) +reveal_type(value) # N: Revealed type is "Union[builtins.type, types.ModuleType, Any]" +reveal_type(value.foo) # E: Item "type" of "Union[type, Module, Any]" has no attribute "foo" \ # N: Revealed type is "Any" +class Namespace: ... +n = Namespace() +n.foo = lambda: None # E: "Namespace" has no attribute "foo" + +b: P = cast(Any, n) +value = type(b) +reveal_type(value) # N: Revealed type is "Union[builtins.type, types.ModuleType, Any]" +reveal_type(value.foo) # E: Item "type" of "Union[type, Module, Any]" has no attribute "foo" \ + # N: Revealed type is "Any" [file mod.py] def foo() -> None: ...