Skip to content

Commit

Permalink
Use a dedicated error code for abstract type object type error (#13785)
Browse files Browse the repository at this point in the history
Ref #4717 

This will allow people who consider this check too strict to opt-out
easily using `--disable-error-code=type-abstract`.
  • Loading branch information
ilevkivskyi authored Oct 1, 2022
1 parent 55ee086 commit 8345d22
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 1 deletion.
26 changes: 26 additions & 0 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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]
---------------------------------------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
4 changes: 3 additions & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit 8345d22

Please sign in to comment.