Repository: python/mypy

Issue:
Mypy crashes on invalid superclass list
Type checking the following invalid Python code causes mypy to crash:

``` python
class A:
    pass

class B(A):
    pass

class C(B):
    pass

class D(A, B):  # This raises a TypeError
    pass

class E(C, D):
    pass
```

Instead of recognizing and reporting the invalid superclass list of `D`, mypy crashes with the following traceback: 

```
Traceback (most recent call last):
  File "C:\Program Files\Python35\Scripts\mypy", line 6, in <module>
    main(__file__)
  File "C:\Program Files\Python35\lib\site-packages\mypy\main.py", line 54, in main
    res = type_check_only(sources, bin_dir, options)
  File "C:\Program Files\Python35\lib\site-packages\mypy\main.py", line 102, in type_check_only
    python_path=options.python_path)
  File "C:\Program Files\Python35\lib\site-packages\mypy\build.py", line 211, in build
    dispatch(sources, manager)
  File "C:\Program Files\Python35\lib\site-packages\mypy\build.py", line 1336, in dispatch
    process_graph(graph, manager)
  File "C:\Program Files\Python35\lib\site-packages\mypy\build.py", line 1467, in process_graph
    process_stale_scc(graph, scc)
  File "C:\Program Files\Python35\lib\site-packages\mypy\build.py", line 1497, in process_stale_scc
    graph[id].type_check()
  File "C:\Program Files\Python35\lib\site-packages\mypy\build.py", line 1316, in type_check
    manager.type_checker.visit_file(self.tree, self.xpath)
  File "C:\Program Files\Python35\lib\site-packages\mypy\checker.py", line 426, in visit_file
    self.accept(d)
  File "C:\Program Files\Python35\lib\site-packages\mypy\checker.py", line 476, in accept
    typ = node.accept(self)
  File "C:\Program Files\Python35\lib\site-packages\mypy\nodes.py", line 650, in accept
    return visitor.visit_class_def(self)
  File "C:\Program Files\Python35\lib\site-packages\mypy\checker.py", line 1068, in visit_class_def
    self.check_multiple_inheritance(typ)
  File "C:\Program Files\Python35\lib\site-packages\mypy\checker.py", line 1093, in check_multiple_inheritance
    if not (base1 in base2.mro or base2 in base1.mro):
AttributeError: 'NoneType' object has no attribute 'mro'

*** INTERNAL ERROR ***

mypybug.py:20: error: Internal error -- please report a bug at https://github.com/python/mypy/issues

NOTE: you can use "mypy --pdb ..." to drop into the debugger when this happens.
```

I am running mypy 0.4.2 and Python 3.5.2.



Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -781,7 +781,7 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
             # Give it an MRO consisting of just the class itself and object.
             defn.info.mro = [defn.info, self.object_type().type]
             return
-        calculate_class_mro(defn, self.fail)
+        calculate_class_mro(defn, self.fail_blocker)
         # If there are cyclic imports, we may be missing 'object' in
         # the MRO. Fix MRO if needed.
         if defn.info.mro and defn.info.mro[-1].fullname() != 'builtins.object':
@@ -2289,6 +2289,9 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *,
             return
         self.errors.report(ctx.get_line(), msg, blocker=blocker)
 
+    def fail_blocker(self, msg: str, ctx: Context) -> None:
+        self.fail(msg, ctx, blocker=True)
+
     def note(self, msg: str, ctx: Context) -> None:
         if (not self.options.check_untyped_defs and
                 self.function_stack and
@@ -2539,7 +2542,7 @@ def visit_class_def(self, tdef: ClassDef) -> None:
         # import loop. (Only do so if we succeeded the first time.)
         if tdef.info.mro:
             tdef.info.mro = []  # Force recomputation
-            calculate_class_mro(tdef, self.fail)
+            calculate_class_mro(tdef, self.fail_blocker)
         super().visit_class_def(tdef)
 
     def visit_decorator(self, dec: Decorator) -> None:
@@ -2615,9 +2618,12 @@ def analyze(self, type: Type) -> None:
             analyzer = TypeAnalyserPass3(self.fail)
             type.accept(analyzer)
 
-    def fail(self, msg: str, ctx: Context) -> None:
+    def fail(self, msg: str, ctx: Context, *, blocker: bool = False) -> None:
         self.errors.report(ctx.get_line(), msg)
 
+    def fail_blocker(self, msg: str, ctx: Context) -> None:
+        self.fail(msg, ctx, blocker=True)
+
     def builtin_type(self, name: str, args: List[Type] = None) -> Instance:
         names = self.modules['builtins']
         sym = names.names[name]


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
@@ -2052,3 +2052,10 @@ class C(str, str):
     bar = foo
 [out]
 main:1: error: Duplicate base class "str"
+
+[case testCannotDetermineMro]
+class A: pass
+class B(A): pass
+class C(B): pass
+class D(A, B): pass # E: Cannot determine consistent method resolution order (MRO) for "D"
+class E(C, D): pass # E: Cannot determine consistent method resolution order (MRO) for "E"