Repository: python/mypy

Issue:
Overloaded functions at nested scopes are not analyzed, causing crashes
Consider this simple example that crashes:
```python
from typing import overload

def f() -> None:
    @overload
    def g(x: str) -> str: ...
    @overload
    def g(x: int) -> int: ...
    def g(x):
        pass

    g(str())
```
Adding a `reveal_type(g)` shows that overload variants are not analyzed at all, it shows:
```
Revealed type is 'Overload(def (x: str?) -> str?, def (x: int?) -> int?)'
```



Discussion:
OK, the root of the problem is that there is two-phase function semantic analysis (to support things like `testMutuallyRecursiveNestedFunctions`) and it interferes badly with semantic analysis of overloads. There are two ways now:
* Just fix the crash (this is simple)
* Fix the crash right way (i.e. supporting mutual recursion for overloaded functions)

I will try to go the second way, but if it will be too hard, then I will make a simple fix.

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -416,6 +416,26 @@ def _visit_func_def(self, defn: FuncDef) -> None:
                         if not self.set_original_def(symbol.node, defn):
                             # Report error.
                             self.check_no_global(defn.name(), defn, True)
+
+            # Analyze function signature and initializers in the first phase
+            # (at least this mirrors what happens at runtime).
+            with self.tvar_scope_frame(self.tvar_scope.method_frame()):
+                if defn.type:
+                    self.check_classvar_in_signature(defn.type)
+                    assert isinstance(defn.type, CallableType)
+                    # Signature must be analyzed in the surrounding scope so that
+                    # class-level imported names and type variables are in scope.
+                    analyzer = self.type_analyzer()
+                    defn.type = analyzer.visit_callable_type(defn.type, nested=False)
+                    self.add_type_alias_deps(analyzer.aliases_used)
+                    self.check_function_signature(defn)
+                    if isinstance(defn, FuncDef):
+                        assert isinstance(defn.type, CallableType)
+                        defn.type = set_callable_name(defn.type, defn)
+                for arg in defn.arguments:
+                    if arg.initializer:
+                        arg.initializer.accept(self)
+
             if phase_info == FUNCTION_FIRST_PHASE_POSTPONE_SECOND:
                 # Postpone this function (for the second phase).
                 self.postponed_functions_stack[-1].append(defn)
@@ -646,21 +666,6 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
     def analyze_function(self, defn: FuncItem) -> None:
         is_method = self.is_class_scope()
         with self.tvar_scope_frame(self.tvar_scope.method_frame()):
-            if defn.type:
-                self.check_classvar_in_signature(defn.type)
-                assert isinstance(defn.type, CallableType)
-                # Signature must be analyzed in the surrounding scope so that
-                # class-level imported names and type variables are in scope.
-                analyzer = self.type_analyzer()
-                defn.type = analyzer.visit_callable_type(defn.type, nested=False)
-                self.add_type_alias_deps(analyzer.aliases_used)
-                self.check_function_signature(defn)
-                if isinstance(defn, FuncDef):
-                    assert isinstance(defn.type, CallableType)
-                    defn.type = set_callable_name(defn.type, defn)
-            for arg in defn.arguments:
-                if arg.initializer:
-                    arg.initializer.accept(self)
             # Bind the type variables again to visit the body.
             if defn.type:
                 a = self.type_analyzer()


Test patch:
diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test
--- a/test-data/unit/check-overloading.test
+++ b/test-data/unit/check-overloading.test
@@ -4186,3 +4186,74 @@ def g(x: str) -> int: ...
 [builtins fixtures/list.pyi]
 [typing fixtures/typing-full.pyi]
 [out]
+
+[case testNestedOverloadsNoCrash]
+from typing import overload
+
+def f() -> None:
+    @overload
+    def g(x: str) -> str: ...
+    @overload
+    def g(x: int) -> int: ...
+    def g(x):
+        pass
+    g(str())
+[out]
+
+[case testNestedOverloadsTypeVar]
+from typing import overload, TypeVar
+
+T = TypeVar('T')
+
+def f() -> None:
+    @overload
+    def g(x: str) -> str: ...
+    @overload
+    def g(x: T, y: int) -> T: ...
+    def g(x):
+        pass
+
+    g(str(), str())  # E: No overload variant of "g" matches argument types "str", "str" \
+                     # N: Possible overload variant: \
+                     # N:     def [T] g(x: T, y: int) -> T \
+                     # N:     <1 more non-matching overload not shown>
+    reveal_type(g(str(), int()))  # E: Revealed type is 'builtins.str*'
+[out]
+
+[case testNestedOverloadsTypeVarOverlap]
+from typing import overload, TypeVar
+
+T = TypeVar('T')
+
+def f() -> None:
+    @overload
+    def g(x: str) -> int: ...  # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
+    @overload
+    def g(x: T) -> T: ...
+    def g(x):
+        pass
+[out]
+
+[case testNestedOverloadsMutuallyRecursive]
+from typing import overload, TypeVar, Dict, Any
+
+class C: ...
+T = TypeVar('T')
+
+def f() -> None:
+    @overload
+    def g() -> None: ...
+    @overload
+    def g(x: T) -> Dict[int, T]: ...
+    def g(*args, **kwargs) -> Any:
+        reveal_type(h(C()))  # E: Revealed type is 'builtins.dict[builtins.str, __main__.C*]'
+
+    @overload
+    def h() -> None: ...
+    @overload
+    def h(x: T) -> Dict[str, T]: ...
+    def h(*args, **kwargs) -> Any:
+        reveal_type(g(C()))  # E: Revealed type is 'builtins.dict[builtins.int, __main__.C*]'
+
+[builtins fixtures/dict.pyi]
+[out]