Repository: python/mypy

Issue:
`TypeError: unorderable types: NoneType() < str()` using `-i` on Zulip codebase
I get this traceback trying to run mypy against the Zulip codebase with `-i`:

```
scripts/lib/pythonrc.py: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues
Traceback (most recent call last):
  File "/srv/zulip-py3-venv/bin/mypy", line 6, in <module>
    main(__file__)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/main.py", line 40, in main
    res = type_check_only(sources, bin_dir, options)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/main.py", line 81, in type_check_only
    options=options)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 185, in build
    dispatch(sources, manager)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1482, in dispatch
    process_graph(graph, manager)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1658, in process_graph
    process_stale_scc(graph, scc)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1733, in process_stale_scc
    graph[id].type_check()
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1435, in type_check
    manager.report_file(self.tree)
  File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
    self.gen.throw(type, value, traceback)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1272, in wrap_context
    yield
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1430, in type_check
    self._patch_indirect_dependencies(manager.type_checker.module_refs)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1444, in _patch_indirect_dependencies
    for dep in sorted(extra):
TypeError: unorderable types: NoneType() < str()
```

I haven't had an easy time making a small reproducer, but you can use this branch with `tools/run-mypy` to reproduce: https://github.com/timabbott/zulip/tree/run-mypy

(This is using `master` as of yesterday for the `mypy` version)

`TypeError: unorderable types: NoneType() < str()` using `-i` on Zulip codebase
I get this traceback trying to run mypy against the Zulip codebase with `-i`:

```
scripts/lib/pythonrc.py: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues
Traceback (most recent call last):
  File "/srv/zulip-py3-venv/bin/mypy", line 6, in <module>
    main(__file__)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/main.py", line 40, in main
    res = type_check_only(sources, bin_dir, options)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/main.py", line 81, in type_check_only
    options=options)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 185, in build
    dispatch(sources, manager)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1482, in dispatch
    process_graph(graph, manager)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1658, in process_graph
    process_stale_scc(graph, scc)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1733, in process_stale_scc
    graph[id].type_check()
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1435, in type_check
    manager.report_file(self.tree)
  File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
    self.gen.throw(type, value, traceback)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1272, in wrap_context
    yield
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1430, in type_check
    self._patch_indirect_dependencies(manager.type_checker.module_refs)
  File "/srv/zulip-venv-cache/f844631091d10ab032e4ec5922fe35bf26b104e0/zulip-py3-venv/lib/python3.5/site-packages/mypy/build.py", line 1444, in _patch_indirect_dependencies
    for dep in sorted(extra):
TypeError: unorderable types: NoneType() < str()
```

I haven't had an easy time making a small reproducer, but you can use this branch with `tools/run-mypy` to reproduce: https://github.com/timabbott/zulip/tree/run-mypy

(This is using `master` as of yesterday for the `mypy` version)



Discussion:
Wow, interesting. I've pointed a debugger at it and so far I've found that there's a `None` in the `module_refs` passed to `_patch_indirect_dependencies()` in `mypy/build.py`. If I just remove the `None` things go swimmingly. The file that's being processed at this point is `scripts/lib/pythonrc.py` and that file has a suspicious `# type: ignore` on and `import *`. It seems that the duplicate import for which that is suppressing an error message is a type variable. The `None` causing the problem is probably the result of an error path. I propose to just call `extras.discard(None)` at this point and close the issue, though this isn't very principled -- but I don't have time right now for a longer investigation.

I think that sounds like a good working solution. I can also maybe look into playing around with mypy + Tim's branch sometime later this week and see if I can't track down the root cause. 

To add some context, I believe the contents of the `extras` set basically come from [mypy.indirection.TypeIndirectionVisitor](https://github.com/python/mypy/blob/master/mypy/indirection.py) class I added that traverses the types and looks for module names.

In theory, that visitor should never return any None items because every type we reference should have a corresponding module name, but I can sort of see how some combination of a type ignore, an `import *`, and a suppressed error path might potentially end up complicating things.

A little further investigation suggests the cause is that in `mypy/checkexpr.py` there's a function `extract_refexpr_names()` that produces the `module_refs` value, and there's a case where it can add `None` to its `output` variable. This occurs exactly once in the zulip checking, and the expression is `bugdown` which appears to be imported by `scripts/lib/pythonrc.py` from `zerver.lib.actions`. I'm not sure why its full name is `None` at this point but I suspect it's due to an import cycle. An amended proposal is to change `extract_refexpr_names()` to avoid adding `None`. The logic there looks like this might have been an oversight -- @Michael0x2a do you remember?

I think you're right -- that sounds like an oversight on my part. When I was writing that function, I made the assumption that `expr.fullname` and such would never be `None`.

It would be nice if we could track down why exactly the full name is None and fix that, but patching the function (and maybe logging cases where the fullname is None?) would probably be a good idea anyways.

My suspicion is the giant import cycle involving both actions and
bugdown(and 17 other modules, according to the logs), but I haven't tracked
it down further. So I'll just patch the module and be done with it.

Wow, interesting. I've pointed a debugger at it and so far I've found that there's a `None` in the `module_refs` passed to `_patch_indirect_dependencies()` in `mypy/build.py`. If I just remove the `None` things go swimmingly. The file that's being processed at this point is `scripts/lib/pythonrc.py` and that file has a suspicious `# type: ignore` on and `import *`. It seems that the duplicate import for which that is suppressing an error message is a type variable. The `None` causing the problem is probably the result of an error path. I propose to just call `extras.discard(None)` at this point and close the issue, though this isn't very principled -- but I don't have time right now for a longer investigation.

I think that sounds like a good working solution. I can also maybe look into playing around with mypy + Tim's branch sometime later this week and see if I can't track down the root cause. 

To add some context, I believe the contents of the `extras` set basically come from [mypy.indirection.TypeIndirectionVisitor](https://github.com/python/mypy/blob/master/mypy/indirection.py) class I added that traverses the types and looks for module names.

In theory, that visitor should never return any None items because every type we reference should have a corresponding module name, but I can sort of see how some combination of a type ignore, an `import *`, and a suppressed error path might potentially end up complicating things.

A little further investigation suggests the cause is that in `mypy/checkexpr.py` there's a function `extract_refexpr_names()` that produces the `module_refs` value, and there's a case where it can add `None` to its `output` variable. This occurs exactly once in the zulip checking, and the expression is `bugdown` which appears to be imported by `scripts/lib/pythonrc.py` from `zerver.lib.actions`. I'm not sure why its full name is `None` at this point but I suspect it's due to an import cycle. An amended proposal is to change `extract_refexpr_names()` to avoid adding `None`. The logic there looks like this might have been an oversight -- @Michael0x2a do you remember?

I think you're right -- that sounds like an oversight on my part. When I was writing that function, I made the assumption that `expr.fullname` and such would never be `None`.

It would be nice if we could track down why exactly the full name is None and fix that, but patching the function (and maybe logging cases where the fullname is None?) would probably be a good idea anyways.

My suspicion is the giant import cycle involving both actions and
bugdown(and 17 other modules, according to the logs), but I haven't tracked
it down further. So I'll just patch the module and be done with it.


Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -53,7 +53,7 @@ def extract_refexpr_names(expr: RefExpr) -> Set[str]:
     MemberExpr."""
     output = set()  # type: Set[str]
     while expr.kind == MODULE_REF or expr.fullname is not None:
-        if expr.kind == MODULE_REF:
+        if expr.kind == MODULE_REF and expr.fullname is not None:
             output.add(expr.fullname)
 
         if isinstance(expr, NameExpr):


Test patch: