Repository: quora/pyanalyze
Pull request #619 (2023-05-05 03:56:58)

Enable implicit_any on first file


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index d58896b..a1cc3dc 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Infer the signature for built-in static methods, such as `dict.fromkeys` (#619)
 - Fix type inference for subscripting on `Sequence` (#618)
 - Improve support for Cythonized methods (#617)
 - Add support for the PEP 698 `@override` decorator (#614)
diff --git a/pyanalyze/arg_spec.py b/pyanalyze/arg_spec.py
index 7a8ef7f..d5315b7 100644
--- a/pyanalyze/arg_spec.py
+++ b/pyanalyze/arg_spec.py
@@ -800,7 +800,14 @@ class ArgSpecCache:
             return bound_sig
 
         if inspect.isbuiltin(obj):
-            if not isinstance(obj.__self__, ModuleType):
+            if isinstance(obj.__self__, ModuleType):
+                inspect_sig = self._safe_get_signature(obj)
+                if inspect_sig is not None:
+                    return self.from_signature(
+                        inspect_sig, function_object=obj, callable_object=obj
+                    )
+                return self._make_any_sig(obj)
+            else:
                 cls = type(obj.__self__)
                 try:
                     method = getattr(cls, obj.__name__)
@@ -812,12 +819,6 @@ class ArgSpecCache:
                     method, impl, is_asynq, in_overload_resolution
                 )
                 return make_bound_method(argspec, Composite(KnownValue(obj.__self__)))
-            inspect_sig = self._safe_get_signature(obj)
-            if inspect_sig is not None:
-                return self.from_signature(
-                    inspect_sig, function_object=obj, callable_object=obj
-                )
-            return self._make_any_sig(obj)
 
         if hasattr_static(obj, "__call__"):
             # we could get an argspec here in some cases, but it's impossible to figure out
diff --git a/pyanalyze/implementation.py b/pyanalyze/implementation.py
index 10e1b1b..9ff65b9 100644
--- a/pyanalyze/implementation.py
+++ b/pyanalyze/implementation.py
@@ -1380,22 +1380,25 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     annotation=TypedValue(bool),
                 ),
             ],
-            KnownValue(None),
+            return_annotation=KnownValue(None),
             impl=_assert_is_value_impl,
             callable=assert_is_value,
         ),
         Signature.make(
             [SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
-            TypeVarValue(T),
+            return_annotation=TypeVarValue(T),
             impl=_reveal_type_impl,
             callable=reveal_type,
         ),
         Signature.make(
-            [], KnownValue(None), impl=_reveal_locals_impl, callable=reveal_locals
+            [],
+            return_annotation=KnownValue(None),
+            impl=_reveal_locals_impl,
+            callable=reveal_locals,
         ),
         Signature.make(
             [SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
-            TypeVarValue(T),
+            return_annotation=TypeVarValue(T),
             impl=_dump_value_impl,
             callable=dump_value,
         ),
@@ -1404,11 +1407,13 @@ def get_default_argspecs() -> Dict[object, Signature]:
             [SigParameter("self", _POS_ONLY)],
             callable=type.__subclasses__,
             impl=_subclasses_impl,
+            return_annotation=GenericValue(list, [TypedValue(type)]),
         ),
         Signature.make(
             [SigParameter("obj", _POS_ONLY), SigParameter("class_or_tuple", _POS_ONLY)],
             impl=_isinstance_impl,
             callable=isinstance,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [
@@ -1423,11 +1428,13 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_issubclass_impl,
             callable=issubclass,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [SigParameter("obj"), SigParameter("class_or_tuple")],
             impl=_isinstance_impl,
             callable=safe_isinstance,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [
@@ -1442,6 +1449,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_issubclass_impl,
             callable=safe_issubclass,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [
@@ -1449,7 +1457,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                 SigParameter("name", _POS_ONLY, annotation=TypedValue(str)),
                 SigParameter("default", _POS_ONLY, default=_NO_ARG_SENTINEL),
             ],
-            AnyValue(AnySource.inference),
+            return_annotation=AnyValue(AnySource.inference),
             callable=getattr,
         ),
         Signature.make(
@@ -1459,6 +1467,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_hasattr_impl,
             callable=hasattr,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [
@@ -1467,6 +1476,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_hasattr_impl,
             callable=hasattr_static,
+            return_annotation=TypedValue(bool),
         ),
         Signature.make(
             [
@@ -1476,6 +1486,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_setattr_impl,
             callable=setattr,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1484,26 +1495,25 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_super_impl,
             callable=super,
+            return_annotation=TypedValue(super),
         ),
         Signature.make(
             [SigParameter("iterable", _POS_ONLY, default=_NO_ARG_SENTINEL)],
             impl=_tuple_impl,
             callable=tuple,
-        ),
-        Signature.make(
-            [SigParameter("iterable", _POS_ONLY, default=_NO_ARG_SENTINEL)],
-            impl=_tuple_impl,
-            callable=tuple,
+            return_annotation=TypedValue(tuple),
         ),
         Signature.make(
             [SigParameter("iterable", _POS_ONLY, default=_NO_ARG_SENTINEL)],
             impl=_list_impl,
             callable=list,
+            return_annotation=TypedValue(list),
         ),
         Signature.make(
             [SigParameter("iterable", _POS_ONLY, default=_NO_ARG_SENTINEL)],
             impl=_set_impl,
             callable=set,
+            return_annotation=TypedValue(set),
         ),
         Signature.make(
             [
@@ -1512,6 +1522,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=list.append,
             impl=_list_append_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1520,6 +1531,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=list.__add__,
             impl=_list_add_impl,
+            return_annotation=TypedValue(list),
         ),
         Signature.make(
             [
@@ -1530,6 +1542,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=list.__iadd__,
             impl=_list_iadd_impl,
+            return_annotation=TypedValue(list),
         ),
         Signature.make(
             [
@@ -1542,6 +1555,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=list.extend,
             impl=_list_extend_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1550,6 +1564,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=list.__getitem__,
             impl=_list_getitem_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1558,6 +1573,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=tuple.__getitem__,
             impl=_tuple_getitem_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1568,6 +1584,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=collections.abc.Sequence.__getitem__,
             impl=_sequence_getitem_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1576,6 +1593,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=set.add,
             impl=_set_add_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1585,14 +1603,20 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=dict.__setitem__,
             impl=_dict_setitem_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
-                SigParameter("self", _POS_ONLY, annotation=TypedValue(dict)),
+                SigParameter(
+                    "self",
+                    _POS_ONLY,
+                    annotation=GenericValue(dict, [TypeVarValue(K), TypeVarValue(V)]),
+                ),
                 SigParameter("k", _POS_ONLY),
             ],
             callable=dict.__getitem__,
             impl=_dict_getitem_impl,
+            return_annotation=TypeVarValue(V),
         ),
         Signature.make(
             [
@@ -1602,6 +1626,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=dict.get,
             impl=_dict_get_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1611,6 +1636,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=dict.setdefault,
             impl=_dict_setdefault_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1620,6 +1646,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=dict.pop,
             impl=_dict_pop_impl,
+            return_annotation=AnyValue(AnySource.inference),
         ),
         Signature.make(
             [
@@ -1627,7 +1654,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                 SigParameter("m", _POS_ONLY, default=_NO_ARG_SENTINEL),
                 SigParameter("kwargs", ParameterKind.VAR_KEYWORD),
             ],
-            KnownValue(None),
+            return_annotation=KnownValue(None),
             callable=dict.update,
             impl=_dict_update_impl,
         ),
@@ -1639,7 +1666,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     annotation=GenericValue(dict, [TypeVarValue(K), TypeVarValue(V)]),
                 )
             ],
-            DictIncompleteValue(
+            return_annotation=DictIncompleteValue(
                 dict, [KVPair(TypeVarValue(K), TypeVarValue(V), is_many=True)]
             ),
             callable=dict.copy,
@@ -1685,7 +1712,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     "errors", annotation=TypedValue(str), default=KnownValue("")
                 ),
             ],
-            TypedValue(str),
+            return_annotation=TypedValue(str),
             callable=bytes.decode,
             allow_call=True,
         ),
@@ -1697,7 +1724,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     "errors", annotation=TypedValue(str), default=KnownValue("")
                 ),
             ],
-            TypedValue(bytes),
+            return_annotation=TypedValue(bytes),
             callable=str.encode,
             allow_call=True,
         ),
@@ -1709,6 +1736,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             impl=_str_format_impl,
             callable=str.format,
+            return_annotation=TypedValue(str),
         ),
         Signature.make(
             [SigParameter("typ", _POS_ONLY), SigParameter("val", _POS_ONLY)],
@@ -1720,7 +1748,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                 SigParameter("val", _POS_ONLY, annotation=TypeVarValue(T)),
                 SigParameter("typ", _POS_ONLY),
             ],
-            TypeVarValue(T),
+            return_annotation=TypeVarValue(T),
             callable=assert_type,
             impl=_assert_type_impl,
         ),
@@ -1733,6 +1761,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=qcore.asserts.assert_is,
             impl=_assert_is_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1743,6 +1772,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=qcore.asserts.assert_is_not,
             impl=_assert_is_not_impl,
+            return_annotation=KnownValue(None),
         ),
         Signature.make(
             [
@@ -1753,6 +1783,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=qcore.asserts.assert_is_instance,
             impl=_assert_is_instance_impl,
+            return_annotation=KnownValue(None),
         ),
         # Need to override this because the type for the tp parameter in typeshed is too strict
         Signature.make(
@@ -1769,6 +1800,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=len,
             impl=_len_impl,
+            return_annotation=TypedValue(int),
         ),
         Signature.make(
             [
@@ -1778,6 +1810,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             ],
             callable=bool,
             impl=_bool_impl,
+            return_annotation=TypedValue(bool),
         ),
         # Typeshed has it as TypeGuard[Callable[..., object]], which causes some
         # false positives.
@@ -1803,7 +1836,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             # Anticipating https://bugs.python.org/issue46414
             sig = Signature.make(
                 [SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
-                TypeVarValue(T),
+                return_annotation=TypeVarValue(T),
                 impl=_reveal_type_impl,
                 callable=reveal_type_func,
             )
@@ -1819,7 +1852,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     SigParameter("val", _POS_ONLY, annotation=TypeVarValue(T)),
                     SigParameter("typ", _POS_ONLY),
                 ],
-                TypeVarValue(T),
+                return_annotation=TypeVarValue(T),
                 callable=assert_type_func,
                 impl=_assert_type_impl,
             )
diff --git a/pyanalyze/typeshed.py b/pyanalyze/typeshed.py
index 13d08d2..68fd000 100644
--- a/pyanalyze/typeshed.py
+++ b/pyanalyze/typeshed.py
@@ -14,7 +14,7 @@ from abc import abstractmethod
 from collections.abc import Collection, MutableMapping, Set as AbstractSet, Sized
 from dataclasses import dataclass, field, replace
 from enum import Enum, EnumMeta
-from types import GeneratorType, ModuleType
+from types import GeneratorType, MethodDescriptorType, ModuleType
 from typing import (
     Any,
     Callable,
@@ -191,6 +191,19 @@ class TypeshedFinder:
             return
         print(f"{message}: {obj!r}")
 
+    def _get_sig_from_method_descriptor(
+        self, obj: MethodDescriptorType, allow_call: bool
+    ) -> Optional[ConcreteSignature]:
+        objclass = obj.__objclass__
+        fq_name = self._get_fq_name(objclass)
+        if fq_name is None:
+            return None
+        info = self._get_info_for_name(fq_name)
+        sig = self._get_method_signature_from_info(
+            info, obj, fq_name, objclass.__module__, objclass, allow_call=allow_call
+        )
+        return sig
+
     def get_argspec(
         self,
         obj: object,
@@ -204,15 +217,24 @@ class TypeshedFinder:
                 obj, obj, type_params=type_params
             )
         if inspect.ismethoddescriptor(obj) and hasattr_static(obj, "__objclass__"):
-            objclass = obj.__objclass__
-            fq_name = self._get_fq_name(objclass)
-            if fq_name is None:
-                return None
-            info = self._get_info_for_name(fq_name)
-            sig = self._get_method_signature_from_info(
-                info, obj, fq_name, objclass.__module__, objclass, allow_call=allow_call
-            )
-            return sig
+            return self._get_sig_from_method_descriptor(obj, allow_call)
+        if inspect.isbuiltin(obj) and isinstance(obj.__self__, type):
+            # This covers cases like dict.fromkeys and type.__subclasses__. We
+            # want to make sure we get the underlying method descriptor object,
+            # which we can apparently only get out of the __dict__.
+            method = obj.__self__.__dict__.get(obj.__name__)
+            if (
+                method is not None
+                and inspect.ismethoddescriptor(method)
+                and hasattr_static(method, "__objclass__")
+            ):
+                sig = self._get_sig_from_method_descriptor(method, allow_call)
+                if sig is None:
+                    return None
+                bound = make_bound_method(sig, Composite(KnownValue(obj.__self__)))
+                if bound is None:
+                    return None
+                return bound.get_signature(ctx=self.ctx)
 
         if inspect.ismethod(obj):
             self.log("Ignoring method", obj)
diff --git a/pyanalyze/value.py b/pyanalyze/value.py
index 7f786d6..7fee146 100644
--- a/pyanalyze/value.py
+++ b/pyanalyze/value.py
@@ -50,7 +50,7 @@ from typing_extensions import Literal, ParamSpec, Protocol
 
 import pyanalyze
 from pyanalyze.error_code import ErrorCode
-from pyanalyze.extensions import CustomCheck
+from pyanalyze.extensions import CustomCheck, ExternalType
 
 from .safe import all_of_type, safe_equals, safe_isinstance, safe_issubclass
 
@@ -60,9 +60,11 @@ BUILTIN_MODULE = str.__module__
 KNOWN_MUTABLE_TYPES = (list, set, dict, deque)
 ITERATION_LIMIT = 1000
 
-TypeVarLike = Union["TypeVar", "ParamSpec"]
-TypeVarMap = Mapping[TypeVarLike, "Value"]
-BoundsMap = Mapping[TypeVarLike, Sequence["Bound"]]
+TypeVarLike = Union[
+    ExternalType["typing.TypeVar"], ExternalType["typing_extensions.ParamSpec"]
+]
+TypeVarMap = Mapping[TypeVarLike, ExternalType["pyanalyze.value.Value"]]
+BoundsMap = Mapping[TypeVarLike, Sequence[ExternalType["pyanalyze.value.Bound"]]]
 GenericBases = Mapping[Union[type, str], TypeVarMap]
 
 
diff --git a/pyproject.toml b/pyproject.toml
index 5be8e13..4ac6f55 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,3 +37,11 @@ value_always_true = true
 suggested_parameter_type = true
 suggested_return_type = true
 incompatible_override = true
+
+[[tool.pyanalyze.overrides]]
+module = "pyanalyze.typevar"
+implicit_any = true
+
+[[tool.pyanalyze.overrides]]
+module = "pyanalyze.yield_checker"
+implicit_any = true


Test patch:
diff --git a/pyanalyze/test_typeshed.py b/pyanalyze/test_typeshed.py
index 4a0fd30..135528f 100644
--- a/pyanalyze/test_typeshed.py
+++ b/pyanalyze/test_typeshed.py
@@ -143,6 +143,17 @@ class TestTypeshedClient(TestNameCheckVisitorBase):
         def capybara(s: str) -> None:
             assert_is_value(s.count("x"), TypedValue(int))
 
+    @assert_passes()
+    def test_dict_fromkeys(self):
+        def capybara(i: int) -> None:
+            assert_is_value(
+                dict.fromkeys([i]),
+                GenericValue(
+                    dict,
+                    [TypedValue(int), AnyValue(AnySource.explicit) | KnownValue(None)],
+                ),
+            )
+
     def test_has_stubs(self) -> None:
         tsf = TypeshedFinder(Checker(), verbose=True)
         assert tsf.has_stubs(object)