From 0fc26dc1affab3c63b650eb344f2601a42370d97 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Thu, 21 Jul 2022 11:26:58 +0300
Subject: [PATCH 1/3] Warn when abstract or protocol type is assigned to a
 callable, refs #13171

---
 mypy/checker.py                     | 12 ++++++++----
 test-data/unit/check-abstract.test  | 19 +++++++++++++++++++
 test-data/unit/check-protocols.test | 17 +++++++++++++++++
 3 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/mypy/checker.py b/mypy/checker.py
index c131e80d47f0..6645a56a68fa 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -2431,10 +2431,14 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_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)):
+                        ((isinstance(lvalue_type, TypeType) and
+                          isinstance(lvalue_type.item, Instance) and
+                          (lvalue_type.item.type.is_abstract or
+                           lvalue_type.item.type.is_protocol)) or
+                         (isinstance(lvalue_type, CallableType) and
+                          isinstance(lvalue_type.ret_type, Instance) and
+                          (lvalue_type.ret_type.type.is_abstract or
+                           lvalue_type.ret_type.type.is_protocol)))):
                     self.msg.concrete_only_assign(lvalue_type, rvalue)
                     return
                 if rvalue_type and infer_lvalue_type and not isinstance(lvalue_type, PartialType):
diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test
index beb2d9397e43..db79422885c0 100644
--- a/test-data/unit/check-abstract.test
+++ b/test-data/unit/check-abstract.test
@@ -250,6 +250,25 @@ 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
+
 [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..0de5f26125f8 100644
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -1619,6 +1619,23 @@ 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
+
 [case testInstantiationProtocolInTypeForClassMethods]
 from typing import Type, Protocol
 

From d21151319e99771c5917fcfad90feb1f2f9200a2 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Thu, 21 Jul 2022 11:47:43 +0300
Subject: [PATCH 2/3] Fix CI

---
 mypy/checker.py                     | 47 ++++++++++++++++++++---------
 test-data/unit/check-abstract.test  |  8 +++++
 test-data/unit/check-protocols.test |  8 +++++
 3 files changed, 49 insertions(+), 14 deletions(-)

diff --git a/mypy/checker.py b/mypy/checker.py
index 6645a56a68fa..74f084eac7e6 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -2426,20 +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)) or
-                         (isinstance(lvalue_type, CallableType) and
-                          isinstance(lvalue_type.ret_type, Instance) and
-                          (lvalue_type.ret_type.type.is_abstract or
-                           lvalue_type.ret_type.type.is_protocol)))):
-                    self.msg.concrete_only_assign(lvalue_type, rvalue)
+                if not self.check_concrete_only_assign(lvalue_type, 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.
@@ -2457,6 +2444,38 @@ 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],
+                                   rvalue_type: Type, rvalue: Expression) -> bool:
+        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 db79422885c0..d5e95a8d85b1 100644
--- a/test-data/unit/check-abstract.test
+++ b/test-data/unit/check-abstract.test
@@ -269,6 +269,14 @@ var = A # E: Can only assign concrete classes to a variable of type "Callable[[]
 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
+
 [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 0de5f26125f8..ee63f206f61c 100644
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -1636,6 +1636,14 @@ var = P # E: Can only assign concrete classes to a variable of type "Callable[[]
 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
+
 [case testInstantiationProtocolInTypeForClassMethods]
 from typing import Type, Protocol
 

From 6a06e369aefe281ae25385e87d3cc8e7178c5555 Mon Sep 17 00:00:00 2001
From: sobolevn <mail@sobolevn.me>
Date: Thu, 21 Jul 2022 15:27:20 +0300
Subject: [PATCH 3/3] Allow self assignment

---
 mypy/checker.py                     | 14 +++++++++++---
 test-data/unit/check-abstract.test  |  5 +++++
 test-data/unit/check-protocols.test |  5 +++++
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/mypy/checker.py b/mypy/checker.py
index 74f084eac7e6..bf08767d25ab 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -2426,7 +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.
-                if not self.check_concrete_only_assign(lvalue_type, rvalue_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.
@@ -2444,8 +2444,16 @@ 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],
-                                   rvalue_type: Type, rvalue: Expression) -> bool:
+    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 (
diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test
index d5e95a8d85b1..21cc3466ef35 100644
--- a/test-data/unit/check-abstract.test
+++ b/test-data/unit/check-abstract.test
@@ -277,6 +277,11 @@ var = A1 # E: Can only assign concrete classes to a variable of type "Callable[[
 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 ee63f206f61c..9c3021a058ab 100644
--- a/test-data/unit/check-protocols.test
+++ b/test-data/unit/check-protocols.test
@@ -1644,6 +1644,11 @@ var = P1 # E: Can only assign concrete classes to a variable of type "Callable[[
 var = B1 # OK
 var = C1 # OK
 
+# Self assign:
+P = P    # OK
+B = B    # OK
+C = C    # OK
+
 [case testInstantiationProtocolInTypeForClassMethods]
 from typing import Type, Protocol