Repository: python/mypy

Issue:
Crash on reverse arithmetic method definitions with no arguments.
Run mypy (0.550) on the following code:

```python
class A: ...

class B:
    def __rmul__(self) -> A: ...

```

This results in the traceback below.

This seems to occur with the other reverse arithmetic methods as well  (__radd__, __rtruediv__, etc), but  only if no arguments are specified in the method definition.

```
rmul_crash.py:4: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.550
Traceback (most recent call last):
  File "/Users/joshstaiger/anaconda/bin/mypy", line 11, in <module>
    sys.exit(console_entry())
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/__main__.py", line 7, in console_entry
    main(None)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/main.py", line 66, in main
    res = type_check_only(sources, bin_dir, options)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/main.py", line 119, in type_check_only
    options=options)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/build.py", line 214, in build
    graph = dispatch(sources, manager)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/build.py", line 1935, in dispatch
    process_graph(graph, manager)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/build.py", line 2199, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/build.py", line 2327, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/build.py", line 1836, in type_check_first_pass
    self.type_checker.check_first_pass()
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 196, in check_first_pass
    self.accept(d)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 283, in accept
    stmt.accept(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/nodes.py", line 690, in accept
    return visitor.visit_class_def(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 1185, in visit_class_def
    self.accept(defn.defs)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 283, in accept
    stmt.accept(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/nodes.py", line 750, in accept
    return visitor.visit_block(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 1305, in visit_block
    self.accept(s)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 283, in accept
    stmt.accept(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/nodes.py", line 496, in accept
    return visitor.visit_func_def(self)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 531, in visit_func_def
    self.check_func_item(defn, name=defn.name())
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 591, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 641, in check_func_def
    self.check_reverse_op_method(item, typ, name)
  File "/Users/joshstaiger/anaconda/lib/python3.6/site-packages/mypy/checker.py", line 841, in check_reverse_op_method
    arg_type = typ.arg_types[1]
IndexError: list index out of range
rmul_crash.py:4: : note: use --pdb to drop into pdb
```


Discussion:
Confirmed. And it's not new either.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -641,7 +641,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
 
                 if name:  # Special method names
                     if name in nodes.reverse_op_method_set:
-                        self.check_reverse_op_method(item, typ, name)
+                        self.check_reverse_op_method(item, typ, name, defn)
                     elif name in ('__getattr__', '__getattribute__'):
                         self.check_getattr_method(typ, defn, name)
                     elif name == '__setattr__':
@@ -816,13 +816,24 @@ def is_trivial_body(self, block: Block) -> bool:
                  isinstance(stmt.expr, EllipsisExpr)))
 
     def check_reverse_op_method(self, defn: FuncItem, typ: CallableType,
-                                method: str) -> None:
+                                method: str, context: Context) -> None:
         """Check a reverse operator method such as __radd__."""
 
         # This used to check for some very obscure scenario.  It now
         # just decides whether it's worth calling
         # check_overlapping_op_methods().
 
+        # First check for a valid signature
+        method_type = CallableType([AnyType(TypeOfAny.special_form),
+                                    AnyType(TypeOfAny.special_form)],
+                                   [nodes.ARG_POS, nodes.ARG_POS],
+                                   [None, None],
+                                   AnyType(TypeOfAny.special_form),
+                                   self.named_type('builtins.function'))
+        if not is_subtype(typ, method_type):
+            self.msg.invalid_signature(typ, context)
+            return
+
         if method in ('__eq__', '__ne__'):
             # These are defined for all objects => can't cause trouble.
             return
@@ -835,9 +846,8 @@ def check_reverse_op_method(self, defn: FuncItem, typ: CallableType,
         if isinstance(ret_type, Instance):
             if ret_type.type.fullname() == 'builtins.object':
                 return
-        # Plausibly the method could have too few arguments, which would result
-        # in an error elsewhere.
-        if len(typ.arg_types) <= 2:
+
+        if len(typ.arg_types) == 2:
             # TODO check self argument kind
 
             # Check for the issue described above.


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -1639,6 +1639,18 @@ class C:
     def __radd__(self, x: Any) -> int: pass
 [out]
 
+[case testReverseOperatorMethodInvalid]
+from foo import *
+[file foo.pyi]
+class A: ...
+class B:
+    def __rmul__(self) -> A: ...
+class C:
+    def __radd__(self, other, oops) -> int: ...
+[out]
+tmp/foo.pyi:3: error: Invalid signature "def (foo.B) -> foo.A"
+tmp/foo.pyi:5: error: Invalid signature "def (foo.C, Any, Any) -> builtins.int"
+
 [case testReverseOperatorMethodForwardIsAny]
 from typing import Any
 def deco(f: Any) -> Any: return f