From 896e657f0ab29f46dc063c685fda23fbe480a474 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 30 Sep 2022 23:57:21 +0100 Subject: [PATCH] Use a dedicated error code for abstract type object type error --- docs/source/error_code_list.rst | 26 ++++++++++++++++++++++++++ mypy/errorcodes.py | 3 +++ mypy/messages.py | 4 +++- test-data/unit/check-errorcodes.test | 12 ++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index a5dafe71970d..264badc03107 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -564,6 +564,32 @@ Example: # Error: Cannot instantiate abstract class "Thing" with abstract attribute "save" [abstract] t = Thing() +Safe handling of abstract type object types [type-abstract] +----------------------------------------------------------- + +Mypy always allows instantiating (calling) type objects typed as ``Type[t]``, +even if it is not known that ``t`` is non-abstract, since it is a common +pattern to create functions that act as object factories (custom constructors). +Therefore, to prevent issues described in the above section, when an abstract +type object is passed where ``Type[t]`` is expected, mypy will give an error. +Example: + +.. code-block:: python + + from abc import ABCMeta, abstractmethod + from typing import List, Type, TypeVar + + class Config(metaclass=ABCMeta): + @abstractmethod + def get_value(self, attr: str) -> str: ... + + T = TypeVar("T") + def make_many(typ: Type[T], n: int) -> List[T]: + return [typ() for _ in range(n)] # This will raise if typ is abstract + + # Error: Only concrete class can be given where "Type[Config]" is expected [type-abstract] + make_many(Config, 5) + Check that call to an abstract method via super is valid [safe-super] --------------------------------------------------------------------- diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 897cb593a032..0d6a328693d4 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -80,6 +80,9 @@ def __str__(self) -> str: ABSTRACT: Final = ErrorCode( "abstract", "Prevent instantiation of classes with abstract attributes", "General" ) +TYPE_ABSTRACT: Final = ErrorCode( + "type-abstract", "Require only concrete classes where Type[...] is expected", "General" +) VALID_NEWTYPE: Final = ErrorCode( "valid-newtype", "Check that argument 2 to NewType is valid", "General" ) diff --git a/mypy/messages.py b/mypy/messages.py index cc9728f99e1d..f3aa1898bfd8 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1748,7 +1748,9 @@ def concrete_only_assign(self, typ: Type, context: Context) -> None: def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail( - f"Only concrete class can be given where {format_type(typ)} is expected", context + f"Only concrete class can be given where {format_type(typ)} is expected", + context, + code=codes.TYPE_ABSTRACT, ) def cannot_use_function_with_type( diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 613186e1b8a5..4cd8e58f037d 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -952,3 +952,15 @@ def bar() -> None: ... # This is inconsistent with how --warn-no-return behaves in general # but we want to minimize fallout of finally handling empty bodies. def baz() -> Optional[int]: ... # OK + +[case testDedicatedErrorCodeTypeAbstract] +import abc +from typing import TypeVar, Type + +class C(abc.ABC): + @abc.abstractmethod + def foo(self) -> None: ... + +T = TypeVar("T") +def test(tp: Type[T]) -> T: ... +test(C) # E: Only concrete class can be given where "Type[C]" is expected [type-abstract]