Repository: python/mypy

Issue:
Crash when running mypy on a malformed program with duplicate functions, typevars, decorators, and no imports
This is a bit silly, but mypy crashes when running on the following program:

```
T = TypeVar("T")

def foo(func):
    return func

@foo
def bar(x: T) -> T:
    pass

@foo
def bar(x: T) -> T:
    pass
```

In particular, note that there are no typing imports. This is intentional: the crash goes away if you add the line `from typing import TypeVar`.

What seems to be happening is that under certain pathological conditions, some region of code ends up removing all items from an OverloadedFuncDef node. This causes a crash when we try calling things like the `name` method, which is defined as so:

```
class OverloadedFuncDef(FuncBase, SymbolNode, Statement):
    # ...snip...

    def name(self) -> str:
        return self.items[0].name()
```

Anyways, here's the full output:

```
..\probes\test.py:3: error: Name 'TypeVar' is not defined
..\probes\test.py:12: error: Name 'bar' already defined on line 8
..\probes\test.py:1: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.610+dev-89c73ed2462e18d217bbd73be09d5085ff7f2632
Traceback (most recent call last):
  File "C:\Python3.6\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Python3.6\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Users\Michael\mypy\__main__.py", line 11, in <module>
    main(None)
  File "C:\Users\Michael\mypy\main.py", line 91, in main
    res = type_check_only(sources, bin_dir, options, flush_errors, fscache)  # noqa
  File "C:\Users\Michael\mypy\main.py", line 148, in type_check_only
    fscache=fscache)
  File "C:\Users\Michael\mypy\build.py", line 181, in build
    flush_errors, fscache)
  File "C:\Users\Michael\mypy\build.py", line 286, in _build
    graph = dispatch(sources, manager)
  File "C:\Users\Michael\mypy\build.py", line 2440, in dispatch
    process_graph(graph, manager)
  File "C:\Users\Michael\mypy\build.py", line 2733, in process_graph
    process_stale_scc(graph, scc, manager)
  File "C:\Users\Michael\mypy\build.py", line 2845, in process_stale_scc
    graph[id].semantic_analysis_pass_three()
  File "C:\Users\Michael\mypy\build.py", line 2059, in semantic_analysis_pass_three
    self.options, patches)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 71, in visit_file
    self.accept(file_node)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 96, in accept
    node.accept(self)
  File "C:\Users\Michael\mypy\nodes.py", line 246, in accept
    return visitor.visit_mypy_file(self)
  File "C:\Users\Michael\mypy\traverser.py", line 30, in visit_mypy_file
    d.accept(self)
  File "C:\Users\Michael\mypy\nodes.py", line 432, in accept
    return visitor.visit_overloaded_func_def(self)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 117, in visit_overloaded_func_def
    super().visit_overloaded_func_def(fdef)
  File "C:\Users\Michael\mypy\traverser.py", line 55, in visit_overloaded_func_def
    o.impl.accept(self)
  File "C:\Users\Michael\mypy\nodes.py", line 663, in accept
    return visitor.visit_decorator(self)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 170, in visit_decorator
    dec.func.accept(self)
  File "C:\Users\Michael\mypy\nodes.py", line 586, in accept
    return visitor.visit_func_def(self)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 109, in visit_func_def
    self.analyze(fdef.type, fdef)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 380, in analyze
    type.accept(analyzer)
  File "C:\Users\Michael\mypy\types.py", line 797, in accept
    return visitor.visit_callable_type(self)
  File "C:\Users\Michael\mypy\typeanal.py", line 760, in visit_callable_type
    t.ret_type.accept(self)
  File "C:\Users\Michael\mypy\types.py", line 1486, in accept

    return visitor.visit_forwardref_type(self)
  File "C:\Users\Michael\mypy\typeanal.py", line 819, in visit_forwardref_type
    resolved = self.anal_type(t.unbound)
  File "C:\Users\Michael\mypy\typeanal.py", line 829, in anal_type
    res = tp.accept(tpan)
  File "C:\Users\Michael\mypy\types.py", line 204, in accept
    return visitor.visit_unbound_type(self)
  File "C:\Users\Michael\mypy\typeanal.py", line 178, in visit_unbound_type
    typ = self.visit_unbound_type_nonoptional(t)
  File "C:\Users\Michael\mypy\typeanal.py", line 334, in visit_unbound_type_nonoptional
    self.fail('Invalid type "{}"'.format(name), t)
  File "C:\Users\Michael\mypy\typeanal.py", line 551, in fail

    self.fail_func(msg, ctx)
  File "C:\Users\Michael\mypy\semanal_pass3.py", line 468, in fail
    self.sem.fail(msg, ctx, serious, blocker=blocker)
  File "C:\Users\Michael\mypy\semanal.py", line 3281, in fail

    self.errors.report(ctx.get_line(), ctx.get_column(), msg, blocker=blocker)
  File "C:\Users\Michael\mypy\errors.py", line 247, in report

    function = self.scope.current_function_name()
  File "C:\Users\Michael\mypy\scope.py", line 50, in current_function_name
    return self.function.name() if self.function else None
  File "C:\Users\Michael\mypy\nodes.py", line 429, in name
    return self.items[0].name()
IndexError: list index out of range
..\probes\test.py:1: : note: use --pdb to drop into pdb
```

Given that the only way to trigger this error appears to be by writing deliberately malformed code, I'm proposing we mark this as low-priority?


Discussion:
I think it can be a normal priority, because it is quite easy to get this error with decorators like `@singledispatch` that are supposed to be repeated.

Resolving patch:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -427,17 +427,24 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement):
     """
 
     items = None  # type: List[OverloadPart]
+    unanalyzed_items = None  # type: List[OverloadPart]
     impl = None  # type: Optional[OverloadPart]
 
     def __init__(self, items: List['OverloadPart']) -> None:
         super().__init__()
         assert len(items) > 0
         self.items = items
+        self.unanalyzed_items = items.copy()
         self.impl = None
         self.set_line(items[0].line)
 
     def name(self) -> str:
-        return self.items[0].name()
+        if self.items:
+            return self.items[0].name()
+        else:
+            # This may happen for malformed overload
+            assert self.impl is not None
+            return self.impl.name()
 
     def accept(self, visitor: StatementVisitor[T]) -> T:
         return visitor.visit_overloaded_func_def(self)
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -518,7 +518,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
                     # The first item was already visited
                     item.is_overload = True
                     item.accept(self)
-                # TODO support decorated overloaded functions properly
+                # TODO: support decorated overloaded functions properly
                 if isinstance(item, Decorator):
                     callable = function_type(item.func, self.builtin_type('builtins.function'))
                     assert isinstance(callable, CallableType)
diff --git a/mypy/server/aststrip.py b/mypy/server/aststrip.py
--- a/mypy/server/aststrip.py
+++ b/mypy/server/aststrip.py
@@ -130,10 +130,8 @@ def visit_decorator(self, node: Decorator) -> None:
     def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None:
         if not self.recurse_into_functions:
             return
-        if node.impl:
-            # Revert change made during semantic analysis pass 2.
-            assert node.items[-1] is not node.impl
-            node.items.append(node.impl)
+        # Revert change made during semantic analysis pass 2.
+        node.items = node.unanalyzed_items.copy()
         super().visit_overloaded_func_def(node)
 
     @contextlib.contextmanager


Test patch:
diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -7300,6 +7300,68 @@ def A(x: str) -> str: pass
 ==
 a.py:4: error: Incompatible import of "A" (imported name has type "Callable[[str], str]", local name has type "Type[List[Any]]")
 
+[case testFakeOverloadCrash]
+import b
+[file a.py]
+def dec(fun):
+    pass
+a = 1
+[file a.py.2]
+def dec(fun):
+    pass
+a = 2
+[file b.py]
+from a import dec
+@dec
+def a():
+    pass
+@dec
+def a():
+    pass
+[out]
+b.py:5: error: Name 'a' already defined on line 2
+==
+b.py:5: error: Name 'a' already defined on line 2
+
+[case testFakeOverloadCrash2]
+# this test just should not crash
+import a
+[file a.py]
+T = TypeVar("T")
+
+def foo(func):
+    return func
+
+@foo
+def bar(x: T) -> T:
+    pass
+
+@foo
+def bar(x: T) -> T:
+    pass
+[file a.py.2]
+T = TypeVar("T")
+
+def foo(func):
+    return func
+
+@foo
+def bar(x: T) -> T:
+    pass
+
+@foo
+def bar(x: T) -> T:
+    pass
+x = 1
+[out]
+a.py:1: error: Name 'TypeVar' is not defined
+a.py:10: error: Name 'bar' already defined on line 6
+a.py:11: error: Invalid type "a.T"
+==
+a.py:1: error: Name 'TypeVar' is not defined
+a.py:10: error: Name 'bar' already defined on line 6
+a.py:11: error: Invalid type "a.T"
+
 [case testRefreshForWithTypeComment1]
 [file a.py]
 from typing import List