Repository: python/mypy

Issue:
AttributeError: 'NoneType' object has no attribute 'bases'
with `mypy-lang==0.3.1`. I think this is in an imported module not my code as `unset MYPYPATH` stops the exception.

```
Traceback (most recent call last):
  File "/home/samuel/code/harrier/env/bin/mypy", line 6, in <module>
    main(__file__)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/main.py", line 54, in main
    type_check_only(sources, bin_dir, options)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/main.py", line 98, in type_check_only
    python_path=options.python_path)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/build.py", line 206, in build
    result = manager.process(initial_states)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/build.py", line 403, in process
    next.process()
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/build.py", line 894, in process
    self.semantic_analyzer_pass3().visit_file(self.tree, self.tree.path)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/semanal.py", line 2341, in visit_file
    file_node.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 171, in accept
    return visitor.visit_mypy_file(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/traverser.py", line 34, in visit_mypy_file
    d.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 673, in accept
    return visitor.visit_if_stmt(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/traverser.py", line 110, in visit_if_stmt
    o.else_body.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 526, in accept
    return visitor.visit_block(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/traverser.py", line 38, in visit_block
    s.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 673, in accept
    return visitor.visit_if_stmt(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/traverser.py", line 110, in visit_if_stmt
    o.else_body.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 526, in accept
    return visitor.visit_block(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/traverser.py", line 38, in visit_block
    s.accept(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/nodes.py", line 485, in accept
    return visitor.visit_class_def(self)
  File "/home/samuel/code/harrier/env/lib/python3.5/site-packages/mypy/semanal.py", line 2350, in visit_class_def
    for type in tdef.info.bases:
AttributeError: 'NoneType' object has no attribute 'bases'
```



Discussion:
It would be helpful if you could narrow this crash down to a small code fragment. It's not obvious how `tdef.info` can be `None` during the 3rd semantic analysis pass. This seems related to a class that is defined within an if statement at the top level of a file.

Also possibly try if this is fixed with the head from the repo?

ok, I've tried with `head` and got the same problem.

I've tracked it down to the [libsass](https://pypi.python.org/pypi/libsass) library and specifically to importing its `_sass.cpython-35m-x86_64-linux-gnu.so` file.

Calling `mypy` for the following script will give the error above:

``` python
import _sass
```

Thanks for reporting this crash! This slightly hacky repro works for me, sidestepping the question of getting MYPYPATH right:

```
env=$(mktemp -d)
virtualenv -p python3.4 "$env"
. "$env"/bin/activate
pip install libsass
cd "$env"/lib/python3.4/site-packages
mypy sass.py
```

I get a traceback that looks just like yours.

I haven't succeeded in reproducing it with that one-line version -- I suspect the trigger is something else in that sass.py file. The traceback also suggests that to me, because it looks like it's a few layers deep in the AST of the file.

We have a mechanism for catching internal errors and reporting the source file and line that triggered them, but this one seems to have slipped past it. I'll file a bug for that. That'll probably be the easiest way to spot exactly what's causing this crash.

Ha, actually on looking at the node types indicated in that traceback, I narrowed it down to a class definition inside a conditional block. There aren't many of those, and it turns out to be [a line in six](https://bitbucket.org/gutworth/six/src/3deee854df8a5f1cc04dd721c18dee2128584f8c/six.py?fileviewer=file-view-default#six.py-60), not anything in libsass itself. Here's a reduced test case:

```
import sys

PY3 = sys.version_info[0] == 3

if PY3:
    pass
else:
    class X(object):
        pass
```

Same traceback.

Thanks for the repro, Greg! This should be easy enough to fix, I expect
it's in the slightly hacky code to deal with conditional definitions. I'm
guessing it's not properly handing the case where one of the branches
leaves the class undefined.

Interestingly this is sensitive to the name of the PY3 variable, which is built into mypy as a known constant (with value dependent on pyversion), and `if PY3: <block1> else: <block2>` marks one or the other block unreachable. The first and second pass skip unreachable blocks. But the third pass doesn't do this. I think I'll add that.


Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2388,6 +2388,11 @@ def visit_file(self, file_node: MypyFile, fnam: str) -> None:
         file_node.accept(self)
         self.errors.set_ignored_lines(set())
 
+    def visit_block(self, b: Block) -> None:
+        if b.is_unreachable:
+            return
+        super().visit_block(b)
+
     def visit_func_def(self, fdef: FuncDef) -> None:
         self.errors.push_function(fdef.name())
         self.analyze(fdef.type)


Test patch:
diff --git a/mypy/test/data/check-unreachable-code.test b/mypy/test/data/check-unreachable-code.test
--- a/mypy/test/data/check-unreachable-code.test
+++ b/mypy/test/data/check-unreachable-code.test
@@ -89,3 +89,12 @@ if MYPY:
 else:
     None + ''
 [builtins fixtures/bool.py]
+
+[case testConditionalClassDefPY3]
+def f(): pass
+PY3 = f()
+if PY3:
+    pass
+else:
+    class X(object):
+        pass