diff --git a/mypy/checker.py b/mypy/checker.py index c131e80d47f0..bf08767d25ab 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2426,16 +2426,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type # Special case: only non-abstract non-protocol classes can be assigned to # variables with explicit type Type[A], where A is protocol or abstract. - rvalue_type = get_proper_type(rvalue_type) - lvalue_type = get_proper_type(lvalue_type) - if (isinstance(rvalue_type, CallableType) and rvalue_type.is_type_obj() and - (rvalue_type.type_object().is_abstract or - rvalue_type.type_object().is_protocol) and - isinstance(lvalue_type, TypeType) and - isinstance(lvalue_type.item, Instance) and - (lvalue_type.item.type.is_abstract or - lvalue_type.item.type.is_protocol)): - self.msg.concrete_only_assign(lvalue_type, rvalue) + if not self.check_concrete_only_assign(lvalue_type, lvalue, rvalue_type, rvalue): return if rvalue_type and infer_lvalue_type and not isinstance(lvalue_type, PartialType): # Don't use type binder for definitions of special forms, like named tuples. @@ -2453,6 +2444,46 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue) self.check_assignment_to_slots(lvalue) + def check_concrete_only_assign(self, + lvalue_type: Optional[Type], + lvalue: Expression, + rvalue_type: Type, + rvalue: Expression) -> bool: + if (isinstance(lvalue, NameExpr) and isinstance(rvalue, NameExpr) + and lvalue.node == rvalue.node): + # This means that we reassign abstract class to itself. Like `A = A` + return True + + rvalue_type = get_proper_type(rvalue_type) + lvalue_type = get_proper_type(lvalue_type) + if not ( + isinstance(rvalue_type, CallableType) and + rvalue_type.is_type_obj() and + (rvalue_type.type_object().is_abstract or + rvalue_type.type_object().is_protocol)): + return True + + lvalue_is_a_type = ( + isinstance(lvalue_type, TypeType) and + isinstance(lvalue_type.item, Instance) and + (lvalue_type.item.type.is_abstract or + lvalue_type.item.type.is_protocol) + ) + + lvalue_is_a_callable = False + if isinstance(lvalue_type, CallableType): + ret_type = get_proper_type(lvalue_type.ret_type) + lvalue_is_a_callable = ( + isinstance(ret_type, Instance) and + (ret_type.type.is_abstract or ret_type.type.is_protocol) + ) + + if lvalue_is_a_type or lvalue_is_a_callable: + # `lvalue_type` here is either `TypeType` or `CallableType`: + self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue) + return False + return True + # (type, operator) tuples for augmented assignments supported with partial types partial_type_augmented_ops: Final = { ('builtins.list', '+'), diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index beb2d9397e43..21cc3466ef35 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -250,6 +250,38 @@ if int(): var_old = C # OK [out] +[case testInstantiationAbstractsWithCallables] +from typing import Callable, Type +from abc import abstractmethod + +class A: + @abstractmethod + def m(self) -> None: pass +class B(A): pass +class C(B): + def m(self) -> None: + pass + +var: Callable[[], A] +var() # OK + +var = A # E: Can only assign concrete classes to a variable of type "Callable[[], A]" +var = B # E: Can only assign concrete classes to a variable of type "Callable[[], A]" +var = C # OK + +# Type aliases: +A1 = A +B1 = B +C1 = C +var = A1 # E: Can only assign concrete classes to a variable of type "Callable[[], A]" +var = B1 # E: Can only assign concrete classes to a variable of type "Callable[[], A]" +var = C1 # OK + +# Self assign: +A = A # OK +B = B # OK +C = C # OK + [case testInstantiationAbstractsInTypeForClassMethods] from typing import Type from abc import abstractmethod diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 4c5a0b44d714..9c3021a058ab 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1619,6 +1619,36 @@ if int(): var_old = B # OK var_old = C # OK +[case testInstantiationProtocolWithCallables] +from typing import Callable, Protocol + +class P(Protocol): + def m(self) -> None: pass +class B(P): pass +class C: + def m(self) -> None: + pass + +var: Callable[[], P] +var() # OK + +var = P # E: Can only assign concrete classes to a variable of type "Callable[[], P]" +var = B # OK +var = C # OK + +# Type aliases: +P1 = P +B1 = B +C1 = C +var = P1 # E: Can only assign concrete classes to a variable of type "Callable[[], P]" +var = B1 # OK +var = C1 # OK + +# Self assign: +P = P # OK +B = B # OK +C = C # OK + [case testInstantiationProtocolInTypeForClassMethods] from typing import Type, Protocol