Repository: quora/pyanalyze
Pull request #421 (2022-01-15 02:27:03)

remove _typeshed.SupportsLessThan usage


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index fbb996c..a421e28 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,9 @@
 
 ## Unreleased
 
+- Updates for compatibility with recent changes in typeshed (#421):
+  - Fix override compatibility check for unknown callables 
+  - Fix usage of removed type `_typeshed.SupportsLessThan`
 - Remove old configuration abstraction (#414)
 
 ## Version 0.6.0 (January 12, 2022)
diff --git a/pyanalyze/implementation.py b/pyanalyze/implementation.py
index 1f57fbf..183b726 100644
--- a/pyanalyze/implementation.py
+++ b/pyanalyze/implementation.py
@@ -64,7 +64,18 @@ import qcore
 import inspect
 import warnings
 from types import FunctionType
-from typing import Sequence, TypeVar, cast, Dict, NewType, Callable, Optional, Union
+from typing import (
+    Sequence,
+    TypeVar,
+    cast,
+    Dict,
+    NewType,
+    Callable,
+    Optional,
+    Union,
+    Any,
+)
+from typing_extensions import Protocol
 
 _NO_ARG_SENTINEL = KnownValue(qcore.MarkerObject("no argument given"))
 
@@ -1097,6 +1108,11 @@ K = TypeVar("K")
 V = TypeVar("V")
 
 
+class SupportsLessThan(Protocol):
+    def __lt__(self, __other: Any) -> bool:
+        raise NotImplementedError
+
+
 def get_default_argspecs() -> Dict[object, Signature]:
     signatures = [
         # pyanalyze helpers
@@ -1489,7 +1505,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                     annotation=CallableValue(
                         Signature.make(
                             [SigParameter("arg", ParameterKind.POSITIONAL_ONLY)],
-                            return_annotation=TypedValue("_typeshed.SupportsLessThan"),
+                            return_annotation=TypedValue(SupportsLessThan),
                         )
                     ),
                     default=KnownValue(None),
diff --git a/pyanalyze/signature.py b/pyanalyze/signature.py
index d445af8..53b7bec 100644
--- a/pyanalyze/signature.py
+++ b/pyanalyze/signature.py
@@ -1248,7 +1248,15 @@ class Signature:
                 else:
                     return can_assign
             return CanAssignError("overloaded function is incompatible", errors)
-        if self.is_asynq and not other.is_asynq:
+        # Callable[..., Any] is compatible with an asynq callable too.
+        if (
+            self.is_asynq
+            and not other.is_asynq
+            and not any(
+                param.kind is ParameterKind.ELLIPSIS
+                for param in other.parameters.values()
+            )
+        ):
             return CanAssignError("callable is not asynq")
         if USE_CHECK_CALL_FOR_CAN_ASSIGN:
             return self.can_assign_through_check_call(other, ctx)
@@ -1673,9 +1681,7 @@ class Signature:
 
 
 ELLIPSIS_PARAM = SigParameter("...", ParameterKind.ELLIPSIS)
-ANY_SIGNATURE = Signature.make(
-    [ELLIPSIS_PARAM], AnyValue(AnySource.explicit), is_asynq=True
-)
+ANY_SIGNATURE = Signature.make([ELLIPSIS_PARAM], AnyValue(AnySource.explicit))
 """:class:`Signature` that should be compatible with any other
 :class:`Signature`."""
 


Test patch:
diff --git a/pyanalyze/test_arg_spec.py b/pyanalyze/test_arg_spec.py
index e2e52b5..edbe2d5 100644
--- a/pyanalyze/test_arg_spec.py
+++ b/pyanalyze/test_arg_spec.py
@@ -312,12 +312,14 @@ class TestCoroutines(TestNameCheckVisitorBase):
     @assert_passes()
     def test_coroutine_from_typeshed(self):
         import asyncio
+        import collections.abc
 
         async def capybara():
-            # annotated as def ... -> Future in typeshed
             assert_is_value(
                 asyncio.sleep(3),
-                GenericValue(asyncio.Future, [AnyValue(AnySource.unannotated)]),
+                GenericValue(
+                    collections.abc.Awaitable, [AnyValue(AnySource.unannotated)]
+                ),
             )
             return 42
 
diff --git a/pyanalyze/test_attributes.py b/pyanalyze/test_attributes.py
index 6d89d95..c3fe76a 100644
--- a/pyanalyze/test_attributes.py
+++ b/pyanalyze/test_attributes.py
@@ -224,7 +224,7 @@ class TestAttributes(TestNameCheckVisitorBase):
         import enum
 
         class E(enum.Enum):
-            name = 1  # E: incompatible_override
+            name = 1
             no_name = 2
 
         def capybara():
diff --git a/pyanalyze/test_signature.py b/pyanalyze/test_signature.py
index 4be2914..66e423a 100644
--- a/pyanalyze/test_signature.py
+++ b/pyanalyze/test_signature.py
@@ -23,6 +23,7 @@ from .signature import (
     OverloadedSignature,
     Signature,
     SigParameter as P,
+    ParameterKind as K,
 )
 from .test_value import CTX
 
@@ -78,14 +79,14 @@ class TestCanAssign:
         )
 
     def test_pos_only(self):
-        pos_only_int = P("x", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY)
-        pos_only_object = P("y", annotation=TypedValue(object), kind=P.POSITIONAL_ONLY)
-        pos_only_bool = P("z", annotation=TypedValue(bool), kind=P.POSITIONAL_ONLY)
-        pos_kw_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_OR_KEYWORD)
+        pos_only_int = P("x", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY)
+        pos_only_object = P("y", annotation=TypedValue(object), kind=K.POSITIONAL_ONLY)
+        pos_only_bool = P("z", annotation=TypedValue(bool), kind=K.POSITIONAL_ONLY)
+        pos_kw_int = P("a", annotation=TypedValue(int), kind=K.POSITIONAL_OR_KEYWORD)
         pos_only_with_default = P(
             "d",
             annotation=TypedValue(int),
-            kind=P.POSITIONAL_ONLY,
+            kind=K.POSITIONAL_ONLY,
             default=KnownValue(0),
         )
         pos_only_sig = Signature.make([pos_only_int])
@@ -99,31 +100,31 @@ class TestCanAssign:
         self.cannot(pos_only_sig, Signature.make([pos_only_bool]))
         self.cannot(
             pos_only_sig,
-            Signature.make([P("x", annotation=TypedValue(int), kind=P.KEYWORD_ONLY)]),
+            Signature.make([P("x", annotation=TypedValue(int), kind=K.KEYWORD_ONLY)]),
         )
 
         # *args interaction
         self.can(
             pos_only_sig,
-            Signature.make([P("whatever", annotation=TupleInt, kind=P.VAR_POSITIONAL)]),
+            Signature.make([P("whatever", annotation=TupleInt, kind=K.VAR_POSITIONAL)]),
         )
         self.cannot(
             pos_only_sig,
-            Signature.make([P("x", annotation=TupleBool, kind=P.VAR_POSITIONAL)]),
+            Signature.make([P("x", annotation=TupleBool, kind=K.VAR_POSITIONAL)]),
         )
 
     def test_pos_or_keyword(self) -> None:
-        pos_kw_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_OR_KEYWORD)
-        pos_kw_int_b = P("b", annotation=TypedValue(int), kind=P.POSITIONAL_OR_KEYWORD)
+        pos_kw_int = P("a", annotation=TypedValue(int), kind=K.POSITIONAL_OR_KEYWORD)
+        pos_kw_int_b = P("b", annotation=TypedValue(int), kind=K.POSITIONAL_OR_KEYWORD)
         pos_kw_object = P(
-            "a", annotation=TypedValue(object), kind=P.POSITIONAL_OR_KEYWORD
+            "a", annotation=TypedValue(object), kind=K.POSITIONAL_OR_KEYWORD
         )
-        pos_kw_bool = P("a", annotation=TypedValue(bool), kind=P.POSITIONAL_OR_KEYWORD)
-        pos_only_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY)
+        pos_kw_bool = P("a", annotation=TypedValue(bool), kind=K.POSITIONAL_OR_KEYWORD)
+        pos_only_int = P("a", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY)
         pos_kw_with_default = P(
             "d",
             annotation=TypedValue(int),
-            kind=P.POSITIONAL_OR_KEYWORD,
+            kind=K.POSITIONAL_OR_KEYWORD,
             default=KnownValue(0),
         )
         pos_kw_sig = Signature.make([pos_kw_int])
@@ -136,17 +137,17 @@ class TestCanAssign:
         self.cannot(pos_kw_sig, Signature.make([pos_kw_int_b]))
         self.cannot(
             pos_kw_sig,
-            Signature.make([P("x", annotation=TupleInt, kind=P.VAR_POSITIONAL)]),
+            Signature.make([P("x", annotation=TupleInt, kind=K.VAR_POSITIONAL)]),
         )
         self.cannot(
-            pos_kw_sig, Signature.make([P("x", annotation=DictInt, kind=P.VAR_KEYWORD)])
+            pos_kw_sig, Signature.make([P("x", annotation=DictInt, kind=K.VAR_KEYWORD)])
         )
         self.can(
             pos_kw_sig,
             Signature.make(
                 [
-                    P("x", annotation=TupleInt, kind=P.VAR_POSITIONAL),
-                    P("y", annotation=DictInt, kind=P.VAR_KEYWORD),
+                    P("x", annotation=TupleInt, kind=K.VAR_POSITIONAL),
+                    P("y", annotation=DictInt, kind=K.VAR_KEYWORD),
                 ]
             ),
         )
@@ -154,8 +155,8 @@ class TestCanAssign:
             pos_kw_sig,
             Signature.make(
                 [
-                    P("x", annotation=TupleObject, kind=P.VAR_POSITIONAL),
-                    P("y", annotation=DictInt, kind=P.VAR_KEYWORD),
+                    P("x", annotation=TupleObject, kind=K.VAR_POSITIONAL),
+                    P("y", annotation=DictInt, kind=K.VAR_KEYWORD),
                 ]
             ),
         )
@@ -163,8 +164,8 @@ class TestCanAssign:
             pos_kw_sig,
             Signature.make(
                 [
-                    P("x", annotation=TupleBool, kind=P.VAR_POSITIONAL),
-                    P("y", annotation=DictInt, kind=P.VAR_KEYWORD),
+                    P("x", annotation=TupleBool, kind=K.VAR_POSITIONAL),
+                    P("y", annotation=DictInt, kind=K.VAR_KEYWORD),
                 ]
             ),
         )
@@ -175,22 +176,22 @@ class TestCanAssign:
             "a",
             annotation=seq_int,
             default=KnownValue([]),
-            kind=P.POSITIONAL_OR_KEYWORD,
+            kind=K.POSITIONAL_OR_KEYWORD,
         )
         sig = Signature.make([list_default])
         no_default_sig = Signature.make(
-            [P("a", annotation=seq_int, kind=P.POSITIONAL_OR_KEYWORD)]
+            [P("a", annotation=seq_int, kind=K.POSITIONAL_OR_KEYWORD)]
         )
         self.cannot(sig, no_default_sig)
         self.can(no_default_sig, sig)
 
     def test_kw_only(self) -> None:
-        kw_only_int = P("a", annotation=TypedValue(int), kind=P.KEYWORD_ONLY)
-        kw_only_int_b = P("b", annotation=TypedValue(int), kind=P.KEYWORD_ONLY)
-        kw_only_bool = P("a", annotation=TypedValue(bool), kind=P.KEYWORD_ONLY)
-        kw_only_object = P("a", annotation=TypedValue(object), kind=P.KEYWORD_ONLY)
-        pos_only_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY)
-        pos_kw_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_OR_KEYWORD)
+        kw_only_int = P("a", annotation=TypedValue(int), kind=K.KEYWORD_ONLY)
+        kw_only_int_b = P("b", annotation=TypedValue(int), kind=K.KEYWORD_ONLY)
+        kw_only_bool = P("a", annotation=TypedValue(bool), kind=K.KEYWORD_ONLY)
+        kw_only_object = P("a", annotation=TypedValue(object), kind=K.KEYWORD_ONLY)
+        pos_only_int = P("a", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY)
+        pos_kw_int = P("a", annotation=TypedValue(int), kind=K.POSITIONAL_OR_KEYWORD)
         kw_only_sig = Signature.make([kw_only_int])
         self.can(kw_only_sig, kw_only_sig)
         self.can(kw_only_sig, Signature.make([kw_only_object]))
@@ -200,18 +201,18 @@ class TestCanAssign:
         self.cannot(kw_only_sig, Signature.make([pos_only_int]))
         self.can(
             kw_only_sig,
-            Signature.make([P("x", annotation=DictInt, kind=P.VAR_KEYWORD)]),
+            Signature.make([P("x", annotation=DictInt, kind=K.VAR_KEYWORD)]),
         )
         self.cannot(
             kw_only_sig,
-            Signature.make([P("x", annotation=DictBool, kind=P.VAR_KEYWORD)]),
+            Signature.make([P("x", annotation=DictBool, kind=K.VAR_KEYWORD)]),
         )
         self.cannot(Signature.make([]), kw_only_sig)
 
     def test_var_positional(self) -> None:
-        var_pos_int = P("a", annotation=TupleInt, kind=P.VAR_POSITIONAL)
-        var_pos_object = P("b", annotation=TupleObject, kind=P.VAR_POSITIONAL)
-        var_pos_bool = P("c", annotation=TupleBool, kind=P.VAR_POSITIONAL)
+        var_pos_int = P("a", annotation=TupleInt, kind=K.VAR_POSITIONAL)
+        var_pos_object = P("b", annotation=TupleObject, kind=K.VAR_POSITIONAL)
+        var_pos_bool = P("c", annotation=TupleBool, kind=K.VAR_POSITIONAL)
         var_pos_sig = Signature.make([var_pos_int])
         self.can(var_pos_sig, var_pos_sig)
         self.can(var_pos_sig, Signature.make([var_pos_object]))
@@ -223,7 +224,7 @@ class TestCanAssign:
                     P(
                         "d",
                         annotation=TypedValue(object),
-                        kind=P.POSITIONAL_ONLY,
+                        kind=K.POSITIONAL_ONLY,
                         default=KnownValue(True),
                     ),
                     var_pos_int,
@@ -237,7 +238,7 @@ class TestCanAssign:
                     P(
                         "d",
                         annotation=TypedValue(bool),
-                        kind=P.POSITIONAL_ONLY,
+                        kind=K.POSITIONAL_ONLY,
                         default=KnownValue(True),
                     ),
                     var_pos_int,
@@ -246,9 +247,9 @@ class TestCanAssign:
         )
 
     def test_var_keyword(self) -> None:
-        var_kw_int = P("a", annotation=DictInt, kind=P.VAR_KEYWORD)
-        var_kw_object = P("b", annotation=DictObject, kind=P.VAR_KEYWORD)
-        var_kw_bool = P("c", annotation=DictBool, kind=P.VAR_KEYWORD)
+        var_kw_int = P("a", annotation=DictInt, kind=K.VAR_KEYWORD)
+        var_kw_object = P("b", annotation=DictObject, kind=K.VAR_KEYWORD)
+        var_kw_bool = P("c", annotation=DictBool, kind=K.VAR_KEYWORD)
         var_kw_sig = Signature.make([var_kw_int])
         self.can(var_kw_sig, var_kw_sig)
         self.can(var_kw_sig, Signature.make([var_kw_object]))
@@ -260,7 +261,7 @@ class TestCanAssign:
                     P(
                         "d",
                         annotation=TypedValue(object),
-                        kind=P.KEYWORD_ONLY,
+                        kind=K.KEYWORD_ONLY,
                         default=KnownValue(True),
                     ),
                     var_kw_int,
@@ -274,7 +275,7 @@ class TestCanAssign:
                     P(
                         "d",
                         annotation=TypedValue(bool),
-                        kind=P.KEYWORD_ONLY,
+                        kind=K.KEYWORD_ONLY,
                         default=KnownValue(True),
                     ),
                     var_kw_int,
@@ -285,9 +286,9 @@ class TestCanAssign:
     def test_advanced_var_positional(self) -> None:
         three_ints_sig = Signature.make(
             [
-                P("a", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY),
-                P("b", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY),
-                P("c", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY),
+                P("a", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY),
+                P("b", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY),
+                P("c", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY),
             ]
         )
         object_int = P(
@@ -295,12 +296,12 @@ class TestCanAssign:
             annotation=SequenceIncompleteValue(
                 tuple, [TypedValue(object), TypedValue(int)]
             ),
-            kind=P.VAR_POSITIONAL,
+            kind=K.VAR_POSITIONAL,
         )
         self.can(
             three_ints_sig,
             Signature.make(
-                [P("a", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY), object_int]
+                [P("a", annotation=TypedValue(int), kind=K.POSITIONAL_ONLY), object_int]
             ),
         )
         self.cannot(three_ints_sig, Signature.make([object_int]))
@@ -308,15 +309,15 @@ class TestCanAssign:
     def test_advanced_var_keyword(self) -> None:
         three_ints_sig = Signature.make(
             [
-                P("a", annotation=TypedValue(int), kind=P.KEYWORD_ONLY),
-                P("b", annotation=TypedValue(int), kind=P.KEYWORD_ONLY),
-                P("c", annotation=TypedValue(int), kind=P.KEYWORD_ONLY),
+                P("a", annotation=TypedValue(int), kind=K.KEYWORD_ONLY),
+                P("b", annotation=TypedValue(int), kind=K.KEYWORD_ONLY),
+                P("c", annotation=TypedValue(int), kind=K.KEYWORD_ONLY),
             ]
         )
         dict_int = P(
             "args",
             annotation=GenericValue(dict, [TypedValue(str), TypedValue(int)]),
-            kind=P.VAR_KEYWORD,
+            kind=K.VAR_KEYWORD,
         )
         self.can(three_ints_sig, Signature.make([dict_int]))
         good_td = TypedDictValue(
@@ -328,7 +329,7 @@ class TestCanAssign:
         )
         self.can(
             three_ints_sig,
-            Signature.make([P("a", annotation=good_td, kind=P.VAR_KEYWORD)]),
+            Signature.make([P("a", annotation=good_td, kind=K.VAR_KEYWORD)]),
         )
         smaller_td = TypedDictValue(
             {"a": (True, TypedValue(int)), "b": (True, TypedValue(int))}
@@ -337,14 +338,14 @@ class TestCanAssign:
         # TODO change to can
         self.cannot(
             three_ints_sig,
-            Signature.make([P("a", annotation=smaller_td, kind=P.VAR_KEYWORD)]),
+            Signature.make([P("a", annotation=smaller_td, kind=K.VAR_KEYWORD)]),
         )
         bad_td = TypedDictValue(
             {"a": (True, TypedValue(str)), "b": (True, TypedValue(int))}
         )
         self.cannot(
             three_ints_sig,
-            Signature.make([P("a", annotation=bad_td, kind=P.VAR_KEYWORD)]),
+            Signature.make([P("a", annotation=bad_td, kind=K.VAR_KEYWORD)]),
         )
 
     def test_overloads(self) -> None:
@@ -364,6 +365,37 @@ class TestCanAssign:
         self.can(sig1, overload2)
         self.cannot(sig3, overload)
 
+    def test_asynq(self) -> None:
+        ret = AnyValue(AnySource.unannotated)
+        self.can(Signature.make([], ret), Signature.make([], ret, is_asynq=True))
+        self.cannot(Signature.make([], ret, is_asynq=True), Signature.make([], ret))
+        self.can(
+            Signature.make([], ret, is_asynq=True),
+            Signature.make([P("", K.ELLIPSIS)], ret),
+        )
+        self.can(
+            Signature.make(
+                [P("x", K.POSITIONAL_ONLY, annotation=TypedValue(int))],
+                ret,
+                is_asynq=True,
+            ),
+            Signature.make([P("", K.ELLIPSIS)], ret),
+        )
+        self.cannot(
+            Signature.make(
+                [P("x", K.POSITIONAL_ONLY, annotation=TypedValue(int))],
+                ret,
+                is_asynq=True,
+            ),
+            Signature.make(
+                [
+                    P("x", K.POSITIONAL_ONLY, annotation=TypedValue(str)),
+                    P("", K.ELLIPSIS),
+                ],
+                ret,
+            ),
+        )
+
 
 class TestProperty(TestNameCheckVisitorBase):
     @assert_passes()
diff --git a/pyanalyze/test_typeshed.py b/pyanalyze/test_typeshed.py
index ddd1da0..bf6149f 100644
--- a/pyanalyze/test_typeshed.py
+++ b/pyanalyze/test_typeshed.py
@@ -346,29 +346,24 @@ class TestGetGenericBases:
             # Until 3.8 NamedTuple is actually a class.
             expected = {
                 time.struct_time: [],
-                "time._struct_time": [],
-                typing.NamedTuple: [],
-                # Ideally should be not Any, but we haven't implemented
-                # support for typeshed namedtuples.
-                tuple: [AnyValue(AnySource.explicit)],
-                collections.abc.Collection: [AnyValue(AnySource.explicit)],
-                collections.abc.Reversible: [AnyValue(AnySource.explicit)],
-                collections.abc.Iterable: [AnyValue(AnySource.explicit)],
-                collections.abc.Sequence: [AnyValue(AnySource.explicit)],
-                collections.abc.Container: [AnyValue(AnySource.explicit)],
+                "_typeshed.structseq": [AnyValue(AnySource.explicit) | TypedValue(int)],
+                tuple: [TypedValue(int)],
+                collections.abc.Collection: [TypedValue(int)],
+                collections.abc.Reversible: [TypedValue(int)],
+                collections.abc.Iterable: [TypedValue(int)],
+                collections.abc.Sequence: [TypedValue(int)],
+                collections.abc.Container: [TypedValue(int)],
             }
         else:
             expected = {
                 time.struct_time: [],
-                "time._struct_time": [],
-                # Ideally should be not Any, but we haven't implemented
-                # support for typeshed namedtuples.
-                tuple: [AnyValue(AnySource.generic_argument)],
-                collections.abc.Collection: [AnyValue(AnySource.generic_argument)],
-                collections.abc.Reversible: [AnyValue(AnySource.generic_argument)],
-                collections.abc.Iterable: [AnyValue(AnySource.generic_argument)],
-                collections.abc.Sequence: [AnyValue(AnySource.generic_argument)],
-                collections.abc.Container: [AnyValue(AnySource.generic_argument)],
+                "_typeshed.structseq": [AnyValue(AnySource.explicit) | TypedValue(int)],
+                tuple: [TypedValue(int)],
+                collections.abc.Collection: [TypedValue(int)],
+                collections.abc.Reversible: [TypedValue(int)],
+                collections.abc.Iterable: [TypedValue(int)],
+                collections.abc.Sequence: [TypedValue(int)],
+                collections.abc.Container: [TypedValue(int)],
             }
         self.check(expected, time.struct_time)