Repository: quora/pyanalyze
Pull request #463 (2022-02-06 04:46:01)

Fix bug that led to some overloaded calls incorrectly resolving to `Any`
Specifically, in cases where the default was Any and the default was used. This is most likely to happen with stubs, where the default value is always `...`.


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index a5780cb..365819b 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Fix bug that led to some overloaded calls incorrectly
+  resolving to `Any` (#462)
 - Support `__init__` and `__new__` signatures from typeshed (#429)
 - Fix incorrect type inferred for indexing operations on
   subclasses of `list` and `tuple` (#461)
diff --git a/pyanalyze/name_check_visitor.py b/pyanalyze/name_check_visitor.py
index eb48ee0..3946d3d 100644
--- a/pyanalyze/name_check_visitor.py
+++ b/pyanalyze/name_check_visitor.py
@@ -2173,7 +2173,7 @@ class NameCheckVisitor(node_visitor.ReplacingNodeVisitor):
             f.flush()
             f.seek(0)
             try:
-                pseudo_module = importer.import_module(pseudo_module_name, f.name)
+                pseudo_module = importer.import_module(pseudo_module_name, Path(f.name))
             except Exception:
                 # sets the name of the imported module to Any so we don't get further
                 # errors
diff --git a/pyanalyze/signature.py b/pyanalyze/signature.py
index 8dfdc4d..2091306 100644
--- a/pyanalyze/signature.py
+++ b/pyanalyze/signature.py
@@ -598,6 +598,8 @@ class Signature:
             bounds_map, used_any = can_assign_and_used_any(
                 param_typ, composite.value, ctx.can_assign_ctx
             )
+            if composite.value is param.default:
+                used_any = False
             if isinstance(bounds_map, CanAssignError):
                 if composite.value is param.default:
                     bounds_map = {}
diff --git a/pyanalyze/typeshed.py b/pyanalyze/typeshed.py
index bcce563..fbc6324 100644
--- a/pyanalyze/typeshed.py
+++ b/pyanalyze/typeshed.py
@@ -1064,7 +1064,14 @@ class TypeshedFinder:
                         return val
                     if info.ast.value:
                         return self._parse_expr(info.ast.value, module)
-                elif isinstance(info.ast, (ast.FunctionDef, ast.AsyncFunctionDef)):
+                elif isinstance(
+                    info.ast,
+                    (
+                        ast.FunctionDef,
+                        ast.AsyncFunctionDef,
+                        typeshed_client.OverloadedName,
+                    ),
+                ):
                     sig = self._get_signature_from_info(info, None, fq_name, module)
                     if sig is not None:
                         return CallableValue(sig)


Test patch:
diff --git a/pyanalyze/stubs/_pyanalyze_tests-stubs/overloaded.pyi b/pyanalyze/stubs/_pyanalyze_tests-stubs/overloaded.pyi
new file mode 100644
index 0000000..ec7a1e6
--- /dev/null
+++ b/pyanalyze/stubs/_pyanalyze_tests-stubs/overloaded.pyi
@@ -0,0 +1,9 @@
+from typing import overload
+from typing_extensions import Literal
+
+@overload
+def func(x: int, y: Literal[1] = ..., z: str = ...) -> int: ...
+@overload
+def func(x: int, y: int, z: str = ...) -> str: ...
+@overload
+def func(x: str, y: int = ..., z: str = ...) -> float: ...
diff --git a/pyanalyze/test_signature.py b/pyanalyze/test_signature.py
index d89cfee..d37c76a 100644
--- a/pyanalyze/test_signature.py
+++ b/pyanalyze/test_signature.py
@@ -1110,3 +1110,14 @@ class TestOverload(TestNameCheckVisitorBase):
             f(1.0)  # E: incompatible_argument
             f(1, 1)  # E: incompatible_argument
             f(1, 1, 1)  # E: incompatible_call
+
+    @assert_passes()
+    def test_ellipsis_default(self):
+        from pyanalyze.extensions import assert_type
+
+        def wrapper():
+            from _pyanalyze_tests.overloaded import func
+
+            assert_type(func(1), int)
+            assert_type(func(1, 1), int)
+            assert_type(func("x"), float)
diff --git a/pyanalyze/test_typeshed.py b/pyanalyze/test_typeshed.py
index b3ed628..fdc648e 100644
--- a/pyanalyze/test_typeshed.py
+++ b/pyanalyze/test_typeshed.py
@@ -21,10 +21,11 @@ from .extensions import evaluated
 from .test_config import TEST_OPTIONS
 from .test_name_check_visitor import TestNameCheckVisitorBase
 from .test_node_visitor import assert_passes
-from .signature import SigParameter, Signature
+from .signature import OverloadedSignature, SigParameter, Signature
 from .test_arg_spec import ClassWithCall
 from .typeshed import TypeshedFinder
 from .value import (
+    CallableValue,
     SubclassValue,
     TypedDictValue,
     assert_is_value,
@@ -196,6 +197,13 @@ class TestBundledStubs(TestNameCheckVisitorBase):
         assert tsf.resolve_name(mod, "aliased_constant") == TypedValue(int)
         assert tsf.resolve_name(mod, "explicitly_aliased_constant") == TypedValue(int)
 
+    def test_overloaded(self):
+        tsf = TypeshedFinder.make(Checker(), TEST_OPTIONS, verbose=True)
+        mod = "_pyanalyze_tests.overloaded"
+        val = tsf.resolve_name(mod, "func")
+        assert isinstance(val, CallableValue)
+        assert isinstance(val.signature, OverloadedSignature)
+
     def test_typeddict(self):
         tsf = TypeshedFinder.make(Checker(), TEST_OPTIONS, verbose=True)
         mod = "_pyanalyze_tests.typeddict"