Skip to content

Commit 8345d22

Browse files
authored
Use a dedicated error code for abstract type object type error (#13785)
Ref #4717 This will allow people who consider this check too strict to opt-out easily using `--disable-error-code=type-abstract`.
1 parent 55ee086 commit 8345d22

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

docs/source/error_code_list.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,32 @@ Example:
564564
# Error: Cannot instantiate abstract class "Thing" with abstract attribute "save" [abstract]
565565
t = Thing()
566566
567+
Safe handling of abstract type object types [type-abstract]
568+
-----------------------------------------------------------
569+
570+
Mypy always allows instantiating (calling) type objects typed as ``Type[t]``,
571+
even if it is not known that ``t`` is non-abstract, since it is a common
572+
pattern to create functions that act as object factories (custom constructors).
573+
Therefore, to prevent issues described in the above section, when an abstract
574+
type object is passed where ``Type[t]`` is expected, mypy will give an error.
575+
Example:
576+
577+
.. code-block:: python
578+
579+
from abc import ABCMeta, abstractmethod
580+
from typing import List, Type, TypeVar
581+
582+
class Config(metaclass=ABCMeta):
583+
@abstractmethod
584+
def get_value(self, attr: str) -> str: ...
585+
586+
T = TypeVar("T")
587+
def make_many(typ: Type[T], n: int) -> List[T]:
588+
return [typ() for _ in range(n)] # This will raise if typ is abstract
589+
590+
# Error: Only concrete class can be given where "Type[Config]" is expected [type-abstract]
591+
make_many(Config, 5)
592+
567593
Check that call to an abstract method via super is valid [safe-super]
568594
---------------------------------------------------------------------
569595

mypy/errorcodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ def __str__(self) -> str:
8080
ABSTRACT: Final = ErrorCode(
8181
"abstract", "Prevent instantiation of classes with abstract attributes", "General"
8282
)
83+
TYPE_ABSTRACT: Final = ErrorCode(
84+
"type-abstract", "Require only concrete classes where Type[...] is expected", "General"
85+
)
8386
VALID_NEWTYPE: Final = ErrorCode(
8487
"valid-newtype", "Check that argument 2 to NewType is valid", "General"
8588
)

mypy/messages.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,9 @@ def concrete_only_assign(self, typ: Type, context: Context) -> None:
17481748

17491749
def concrete_only_call(self, typ: Type, context: Context) -> None:
17501750
self.fail(
1751-
f"Only concrete class can be given where {format_type(typ)} is expected", context
1751+
f"Only concrete class can be given where {format_type(typ)} is expected",
1752+
context,
1753+
code=codes.TYPE_ABSTRACT,
17521754
)
17531755

17541756
def cannot_use_function_with_type(

test-data/unit/check-errorcodes.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,3 +952,15 @@ def bar() -> None: ...
952952
# This is inconsistent with how --warn-no-return behaves in general
953953
# but we want to minimize fallout of finally handling empty bodies.
954954
def baz() -> Optional[int]: ... # OK
955+
956+
[case testDedicatedErrorCodeTypeAbstract]
957+
import abc
958+
from typing import TypeVar, Type
959+
960+
class C(abc.ABC):
961+
@abc.abstractmethod
962+
def foo(self) -> None: ...
963+
964+
T = TypeVar("T")
965+
def test(tp: Type[T]) -> T: ...
966+
test(C) # E: Only concrete class can be given where "Type[C]" is expected [type-abstract]

0 commit comments

Comments
 (0)