From d72df74fc7ab1973175479d7b3d91d45e5af8f2e Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 3 Nov 2024 00:39:03 -0700 Subject: [PATCH] Remove TypeType exception for abstract instantiation If A is abstract, it's weird to me that we have a difference in the following two calls: ``` from abc import abstractmethod, ABCMeta class A(metaclass=ABCMeta): @abstractmethod def __init__(self, a: int) -> None: pass def test_a(A_t: type[A]) -> None: A_t(1) A(1) ``` Mypy tries to then enforce soundness by preventing you from passing `A` to a parameter of `type[A]`. But this is very unpopular, since there are legitimate uses of `A` that have nothing to do with instantiation. See https://github.com/python/mypy/issues/4717 As mentioned in https://discuss.python.org/t/compatibility-of-protocol-class-object-with-type-t-and-type-any/48442/2 I think we should switch to disallowing instantiation of `type[Proto]` and `type[Abstract]`. This also makes tackling `__init__` unsoundness more tractable. If people want unsound `__init__`, they can use `Callable[..., P]`. --- mypy/checkexpr.py | 9 +-------- test-data/unit/check-abstract.test | 20 ++++++++------------ test-data/unit/check-classes.test | 3 ++- test-data/unit/check-functools.test | 6 +++--- test-data/unit/check-protocols.test | 10 +++++----- test-data/unit/check-selftype.test | 2 +- 6 files changed, 20 insertions(+), 30 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 577576a4e5f8..967571858d11 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, @@ -1681,8 +1676,6 @@ def check_callable_call( elif ( callee.is_type_obj() and callee.type_object().is_abstract - # Exception for Type[...] - and not callee.from_type_type and not callee.type_object().fallback_to_any ): type = callee.type_object() diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 3b0b9c520b75..e2832be2d6fb 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -187,7 +187,7 @@ class C(B): pass def f(cls: Type[A]) -> A: - return cls() # OK + return cls() # E: Cannot instantiate abstract class "A" with abstract attribute "m" def g() -> A: return A() # E: Cannot instantiate abstract class "A" with abstract attribute "m" @@ -196,7 +196,6 @@ f(B) # E: Only concrete class can be given where "Type[A]" is expected f(C) # OK x: Type[B] f(x) # OK -[out] [case testAbstractTypeInADict] from typing import Dict, Type @@ -229,7 +228,7 @@ class C(B): pass def f(cls: Type[A]) -> A: - return cls() # OK + return cls() # E: Cannot instantiate abstract class "A" with abstract attribute "m" Alias = A GoodAlias = C @@ -237,7 +236,6 @@ Alias() # E: Cannot instantiate abstract class "A" with abstract attribute "m" GoodAlias() f(Alias) # E: Only concrete class can be given where "Type[A]" is expected f(GoodAlias) -[out] [case testInstantiationAbstractsInTypeForVariables] # flags: --no-strict-optional @@ -253,7 +251,7 @@ class C(B): pass var: Type[A] -var() +var() # E: Cannot instantiate abstract class "A" with abstract attribute "m" if int(): var = A # E: Can only assign concrete classes to a variable of type "Type[A]" if int(): @@ -262,7 +260,7 @@ if int(): var = C # OK var_old = None # type: Type[A] # Old syntax for variable annotations -var_old() +var_old() # E: Cannot instantiate abstract class "A" with abstract attribute "m" if int(): var_old = A # E: Can only assign concrete classes to a variable of type "Type[A]" if int(): @@ -278,7 +276,7 @@ class D(A): def __new__(cls, a=None) -> "D": ... if int(): var = D # E: Can only assign concrete classes to a variable of type "Type[A]" -[out] + [case testInstantiationAbstractsInTypeForClassMethods] from typing import Type @@ -291,13 +289,12 @@ class Logger: class C: @classmethod def action(cls) -> None: - cls() #OK for classmethods + cls() # E: Cannot instantiate abstract class "C" with abstract attribute "m" Logger.log(cls) #OK for classmethods @abstractmethod def m(self) -> None: pass [builtins fixtures/classmethod.pyi] -[out] [case testInstantiatingClassWithInheritedAbstractMethodAndSuppression] from abc import abstractmethod, ABCMeta @@ -324,7 +321,6 @@ class A(metaclass=ABCMeta): @abstractmethod def j(self): pass a = A() # E: Cannot instantiate abstract class "A" with abstract attributes "a", "b", ... and "j" (7 methods suppressed) -[out] -- Implementing abstract methods @@ -1085,9 +1081,9 @@ my_abstract_types = { reveal_type(my_concrete_types) # N: Revealed type is "builtins.dict[builtins.str, def () -> __main__.MyAbstractType]" reveal_type(my_abstract_types) # N: Revealed type is "builtins.dict[builtins.str, def () -> __main__.MyAbstractType]" -a = my_concrete_types['A']() +a = my_concrete_types['A']() # E: Cannot instantiate abstract class "MyAbstractType" with abstract attribute "do" a.do() -b = my_concrete_types['B']() +b = my_concrete_types['B']() # E: Cannot instantiate abstract class "MyAbstractType" with abstract attribute "do" b.do() c = my_abstract_types['A']() # E: Cannot instantiate abstract class "MyAbstractType" with abstract attribute "do" diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 5ce80faaee18..ced52ce9a513 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1526,7 +1526,8 @@ import typing class C: @classmethod def foo(cls) -> None: - cls().bar() + c = cls() # E: Cannot instantiate abstract class "C" with abstract attribute "bar" + c.bar() @abstractmethod def bar(self) -> None: pass diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index ea98a902d14b..acd1c7d57bb4 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -586,9 +586,9 @@ class A(ABC): def method(self) -> None: ... def f1(cls: type[A]) -> None: - cls() - partial_cls = partial(cls) - partial_cls() + cls() # E: Cannot instantiate abstract class "A" with abstract attribute "method" + partial_cls = partial(cls) # E: Cannot instantiate abstract class "A" with abstract attribute "method" + partial_cls() # E: Cannot instantiate abstract class "A" with abstract attribute "method" def f2() -> None: A() # E: Cannot instantiate abstract class "A" with abstract attribute "method" diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 5ed2351e33e6..5d9879024f66 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