Repository: python/mypy

Issue:
Don't verify overrides for dynamically typed methods
This should probably not be flagged as an error, because the method in B has no signature.

```
class A:
    def f(self) -> None: pass

class B(A):
    def f(self, x): pass
```

Overrides like this exist in existing Python code, and mypy should only complain when both the methods are statically typed (the idea being that dynamically typed code can do almost anything).



Discussion:
Alternatively, complain about incompatible signatures if the overriding method is statically typed. It should not matter whether the overridden method has a signature (just use the implied dynamically typed signature if there is no explicit one).


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -444,7 +444,8 @@ def visit_func_def(self, defn: FuncDef) -> Type:
         """Type check a function definition."""
         self.check_func_item(defn, name=defn.name())
         if defn.info:
-            self.check_method_override(defn)
+            if not defn.is_dynamic():
+                self.check_method_override(defn)
             self.check_inplace_operator_method(defn)
         if defn.original_def:
             if not is_same_type(self.function_type(defn),
@@ -465,7 +466,7 @@ def check_func_item(self, defn: FuncItem,
             fdef = defn
 
         self.function_stack.append(defn)
-        self.dynamic_funcs.append(defn.type is None and not type_override)
+        self.dynamic_funcs.append(defn.is_dynamic() and not type_override)
 
         if fdef:
             self.errors.push_function(fdef.name())
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -298,6 +298,9 @@ def init_expressions(self) -> List[Node]:
                 res.append(None)
         return res
 
+    def is_dynamic(self):
+        return self.type is None
+
 
 class FuncDef(FuncItem):
     """Function definition.


Test patch:
diff --git a/mypy/test/data/check-abstract.test b/mypy/test/data/check-abstract.test
--- a/mypy/test/data/check-abstract.test
+++ b/mypy/test/data/check-abstract.test
@@ -293,11 +293,24 @@ class I(metaclass=ABCMeta):
     def g(self, x): pass
 class A(I):
     def f(self, x): pass
-    def g(self, x, y): pass \
+    def g(self, x, y) -> None: pass \
         # E: Signature of "g" incompatible with supertype "I"
 [out]
 main: In class "A":
 
+[case testAbstractClassWithAllDynamicTypes2]
+from abc import abstractmethod, ABCMeta
+import typing
+class I(metaclass=ABCMeta):
+    @abstractmethod
+    def f(self, x): pass
+    @abstractmethod
+    def g(self, x): pass
+class A(I):
+    def f(self, x): pass
+    def g(self, x, y): pass
+[out]
+
 [case testAbstractClassWithImplementationUsingDynamicTypes]
 from abc import abstractmethod, ABCMeta
 import typing
@@ -308,10 +321,8 @@ class I(metaclass=ABCMeta):
     def g(self, x: int) -> None: pass
 class A(I):
     def f(self, x): pass
-    def g(self, x, y): pass \
-        # E: Signature of "g" incompatible with supertype "I"
+    def g(self, x, y): pass
 [out]
-main: In class "A":
 
 
 -- Special cases
diff --git a/mypy/test/data/check-dynamic-typing.test b/mypy/test/data/check-dynamic-typing.test
--- a/mypy/test/data/check-dynamic-typing.test
+++ b/mypy/test/data/check-dynamic-typing.test
@@ -630,11 +630,9 @@ import typing
 class B:
     def f(self, x: A) -> None: pass
 class A(B):
-    def f(self, x, y): # Fail
+    def f(self, x, y): # dynamic function not type checked
         x()
 [out]
-main: In class "A":
-main, line 5: Signature of "f" incompatible with supertype "B"
 
 [case testInvalidOverrideArgumentCountWithImplicitSignature2]
 import typing
@@ -645,3 +643,13 @@ class A(B):
         pass
 [out]
 main: In class "A":
+
+[case testInvalidOverrideArgumentCountWithImplicitSignature3]
+import typing
+class B:
+    def f(self, x: A) -> None: pass
+class A(B):
+    def f(self, x, y) -> None: # E: Signature of "f" incompatible with supertype "B"
+        x()
+[out]
+main: In class "A":