Repository: python/mypy

Issue:
Crash in check_overlapping_op_methods() when other method is decorated
Example:

``` python
from typing import *

def deco(f: Any) -> Any:
    return f

class C:

    @deco
    def __lt__(self, other):
        return False

    def __gt__(self, other: C) -> bool:
        return True
```

This produces an internal error with a traceback ending in:

```
  File "/Users/guido/src/mypy/mypy/checker.py", line 449, in visit_func_def
    self.check_func_item(defn, name=defn.name())
  File "/Users/guido/src/mypy/mypy/checker.py", line 505, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/Users/guido/src/mypy/mypy/checker.py", line 545, in check_func_def
    self.check_reverse_op_method(item, typ, name)
  File "/Users/guido/src/mypy/mypy/checker.py", line 639, in check_reverse_op_method
    defn)
  File "/Users/guido/src/mypy/mypy/checker.py", line 716, in check_overlapping_op_methods
    assert False, 'Forward operator method type is not CallableType'
```



Discussion:
Removing the assert or adding `if isinstance(forward_type, AnyType): return` to the beginning of the function prevents the exception -- but is that the right fix?

Also, looks like this crash was previously prevented by the checks I removed for #1101.

A somewhat clearer example:

``` python
from typing import *

def deco(f: Any) -> Any:
    return f

class C:

    @deco
    def __add__(self, other: C) -> C:
        return C()

    def __radd__(self, other: C) -> C:
        return C()
```

This clarifies that the problem occurs when we're analyzing `__radd__`; we end up lookup up the forward operator `__add__` but because of the decorator its type is Any -- the analyzer can't even tell that it's a method. I'm going to fix this as I suggested -- inserting a check for Any and an early return at the top of check_overlapping_op_methods().


Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -711,9 +711,8 @@ def check_overlapping_op_methods(self,
                 self.check_overlapping_op_methods(
                     reverse_type, reverse_name, reverse_class,
                     item, forward_name, forward_base, context)
-        else:
-            # TODO what about this?
-            assert False, 'Forward operator method type is not CallableType'
+        elif not isinstance(forward_type, AnyType):
+            self.msg.forward_operator_not_callable(forward_name, context)
 
     def check_inplace_operator_method(self, defn: FuncBase) -> None:
         """Check an inplace operator method such as __iadd__.
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -777,6 +777,11 @@ def operator_method_signatures_overlap(
                       forward_method, forward_class),
                   context)
 
+    def forward_operator_not_callable(
+            self, forward_method: str, context: Context) -> None:
+        self.fail('Forward operator "{}" is not callable'.format(
+            forward_method), context)
+
     def signatures_incompatible(self, method: str, other_method: str,
                                 context: Context) -> None:
         self.fail('Signatures of "{}" and "{}" are incompatible'.format(


Test patch:
diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test
--- a/mypy/test/data/check-classes.test
+++ b/mypy/test/data/check-classes.test
@@ -1015,6 +1015,33 @@ class C:
     def __radd__(self, x: Any) -> int: pass
 [out]
 
+[case testReverseOperatorMethodForwardIsAny]
+from typing import Any
+def deco(f: Any) -> Any: return f
+class C:
+    @deco
+    def __add__(self, other: C) -> C: return C()
+    def __radd__(self, other: C) -> C: return C()
+[out]
+
+[case testReverseOperatorMethodForwardIsAny2]
+from typing import Any
+def deco(f: Any) -> Any: return f
+class C:
+    __add__ = None  # type: Any
+    def __radd__(self, other: C) -> C: return C()
+[out]
+
+[case testReverseOperatorMethodForwardIsAny3]
+from typing import Any
+def deco(f: Any) -> Any: return f
+class C:
+    __add__ = 42
+    def __radd__(self, other: C) -> C: return C()
+[out]
+main: note: In member "__radd__" of class "C":
+main:5: error: Forward operator "__add__" is not callable
+
 [case testOverloadedReverseOperatorMethodArgumentType]
 from typing import overload, Any
 class A: