From c2f94827e497aea62540192e583beed4e26cd7eb Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Fri, 27 Dec 2024 11:53:48 -0800 Subject: [PATCH 1/3] Remove TypeType exception for protocol instantiation This disallows direct instantiation of `cls: type[Proto]` Discussed in https://discuss.python.org/t/compatibility-of-protocol-class-object-with-type-t-and-type-any/48442/2 Users should use `Callable[..., Proto]` instead. This helps with `__init__` unsoundness. Subset of https://github.com/python/mypy/pull/18094, which also touched abstract classes. --- mypy/checkexpr.py | 7 +------ test-data/unit/check-protocols.test | 10 +++++----- test-data/unit/check-selftype.test | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 577576a4e5f8..587d9cf38e1b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1668,12 +1668,7 @@ def check_callable_call( # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). return callee.ret_type, callee - if ( - callee.is_type_obj() - and callee.type_object().is_protocol - # Exception for Type[...] - and not callee.from_type_type - ): + if callee.is_type_obj() and callee.type_object().is_protocol: self.chk.fail( message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(callee.type_object().name), context, diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 0367be3dde65..711609cfb0d7 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1603,7 +1603,7 @@ class C: pass def f(cls: Type[P]) -> P: - return cls() # OK + return cls() # E: Cannot instantiate protocol class "P" def g() -> P: return P() # E: Cannot instantiate protocol class "P" @@ -1625,7 +1625,7 @@ class C: pass def f(cls: Type[P]) -> P: - return cls() # OK + return cls() # E: Cannot instantiate protocol class "P" Alias = P GoodAlias = C @@ -1646,14 +1646,14 @@ class C: pass var: Type[P] -var() +var() # E: Cannot instantiate protocol class "P" if int(): var = P # E: Can only assign concrete classes to a variable of type "Type[P]" var = B # OK var = C # OK var_old = None # type: Type[P] # Old syntax for variable annotations -var_old() +var_old() # E: Cannot instantiate protocol class "P" if int(): var_old = P # E: Can only assign concrete classes to a variable of type "Type[P]" var_old = B # OK @@ -1669,7 +1669,7 @@ class Logger: class C(Protocol): @classmethod def action(cls) -> None: - cls() #OK for classmethods + cls() # E: Cannot instantiate protocol class "C" Logger.log(cls) #OK for classmethods [builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index fa853ac48e5a..563003d67fd4 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1023,7 +1023,7 @@ T = TypeVar('T', bound=HasX) class Meta(type): def do_x(cls: Type[T]) -> T: cls.x - return cls() + return cls() # E: Cannot instantiate protocol class "HasX" class Good(metaclass=Meta): x: int From 858c066db565e65d1f54cd4e783b66bc5f7cbb8e Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Fri, 27 Dec 2024 12:31:58 -0800 Subject: [PATCH 2/3] add note --- mypy/checkexpr.py | 9 ++++++++- test-data/unit/check-protocols.test | 15 ++++++++++----- test-data/unit/check-selftype.test | 3 ++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 587d9cf38e1b..9c8796889edc 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1669,10 +1669,17 @@ def check_callable_call( return callee.ret_type, callee if callee.is_type_obj() and callee.type_object().is_protocol: + protocol_name = callee.type_object().name self.chk.fail( - message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(callee.type_object().name), + message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(protocol_name), context, ) + if callee.from_type_type: + self.chk.note( + f'Consider using "Callable[..., {protocol_name}]" ' + f'instead of "type[{protocol_name}]"', + context, + ) elif ( callee.is_type_obj() and callee.type_object().is_abstract diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 711609cfb0d7..e00f727ec710 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1603,7 +1603,8 @@ class C: pass def f(cls: Type[P]) -> P: - return cls() # E: Cannot instantiate protocol class "P" + return cls() # E: Cannot instantiate protocol class "P" \ + # N: Consider using "Callable[..., P]" instead of "type[P]" def g() -> P: return P() # E: Cannot instantiate protocol class "P" @@ -1625,7 +1626,8 @@ class C: pass def f(cls: Type[P]) -> P: - return cls() # E: Cannot instantiate protocol class "P" + return cls() # E: Cannot instantiate protocol class "P" \ + # N: Consider using "Callable[..., P]" instead of "type[P]" Alias = P GoodAlias = C @@ -1646,14 +1648,16 @@ class C: pass var: Type[P] -var() # E: Cannot instantiate protocol class "P" +var() # E: Cannot instantiate protocol class "P" \ + # N: Consider using "Callable[..., P]" instead of "type[P]" if int(): var = P # E: Can only assign concrete classes to a variable of type "Type[P]" var = B # OK var = C # OK var_old = None # type: Type[P] # Old syntax for variable annotations -var_old() # E: Cannot instantiate protocol class "P" +var_old() # E: Cannot instantiate protocol class "P" \ + # N: Consider using "Callable[..., P]" instead of "type[P]" if int(): var_old = P # E: Can only assign concrete classes to a variable of type "Type[P]" var_old = B # OK @@ -1669,7 +1673,8 @@ class Logger: class C(Protocol): @classmethod def action(cls) -> None: - cls() # E: Cannot instantiate protocol class "C" + cls() # E: Cannot instantiate protocol class "C" \ + # N: Consider using "Callable[..., C]" instead of "type[C]" Logger.log(cls) #OK for classmethods [builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 563003d67fd4..e97fa7d81657 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1023,7 +1023,8 @@ T = TypeVar('T', bound=HasX) class Meta(type): def do_x(cls: Type[T]) -> T: cls.x - return cls() # E: Cannot instantiate protocol class "HasX" + return cls() # E: Cannot instantiate protocol class "HasX" \ + # N: Consider using "Callable[..., HasX]" instead of "type[HasX]" class Good(metaclass=Meta): x: int From d6790955636a8dac780346fe4c1463dd7e4adad5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:35:43 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checkexpr.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9c8796889edc..7c205eb89dfe 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1671,8 +1671,7 @@ def check_callable_call( if callee.is_type_obj() and callee.type_object().is_protocol: protocol_name = callee.type_object().name self.chk.fail( - message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(protocol_name), - context, + message_registry.CANNOT_INSTANTIATE_PROTOCOL.format(protocol_name), context ) if callee.from_type_type: self.chk.note(