Skip to content

Commit 52f1dd3

Browse files
authored
Allow generic decorators on abstarct classes (#13398)
Fixes #5374 As discussed in the issue, we should allow this common use case, although it may be technically unsafe (e.g. if one would instantiate a class in a class decorator body).
1 parent 816aeb3 commit 52f1dd3

File tree

3 files changed

+21
-0
lines changed

3 files changed

+21
-0
lines changed

mypy/checker.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,11 @@ def __init__(
409409
# of giving a note on possibly missing "await". It is used to avoid infinite recursion.
410410
self.checking_missing_await = False
411411

412+
# While this is True, allow passing an abstract class where Type[T] is expected.
413+
# although this is technically unsafe, this is desirable in some context, for
414+
# example when type-checking class decorators.
415+
self.allow_abstract_call = False
416+
412417
@property
413418
def type_context(self) -> List[Optional[Type]]:
414419
return self.expr_checker.type_context
@@ -2027,9 +2032,12 @@ def visit_class_def(self, defn: ClassDef) -> None:
20272032

20282033
# TODO: Figure out how to have clearer error messages.
20292034
# (e.g. "class decorator must be a function that accepts a type."
2035+
old_allow_abstract_call = self.allow_abstract_call
2036+
self.allow_abstract_call = True
20302037
sig, _ = self.expr_checker.check_call(
20312038
dec, [temp], [nodes.ARG_POS], defn, callable_name=fullname
20322039
)
2040+
self.allow_abstract_call = old_allow_abstract_call
20332041
# TODO: Apply the sig to the actual TypeInfo so we can handle decorators
20342042
# that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]])
20352043
if typ.is_protocol and typ.defn.type_vars:

mypy/checkexpr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,7 @@ def check_arg(
19141914
and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol)
19151915
and isinstance(callee_type.item, Instance)
19161916
and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)
1917+
and not self.chk.allow_abstract_call
19171918
):
19181919
self.msg.concrete_only_call(callee_type, context)
19191920
elif not is_subtype(caller_type, callee_type, options=self.chk.options):

test-data/unit/check-abstract.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,3 +1018,15 @@ d = my_abstract_types['B']() # E: Cannot instantiate abstract class "MyAbstract
10181018
d.do()
10191019

10201020
[builtins fixtures/dict.pyi]
1021+
1022+
[case testAbstractClassesWorkWithGenericDecorators]
1023+
from abc import abstractmethod, ABCMeta
1024+
from typing import Type, TypeVar
1025+
1026+
T = TypeVar("T")
1027+
def deco(cls: Type[T]) -> Type[T]: ...
1028+
1029+
@deco
1030+
class A(metaclass=ABCMeta):
1031+
@abstractmethod
1032+
def foo(self, x: int) -> None: ...

0 commit comments

Comments
 (0)