Repository: quora/pyanalyze
Pull request #526 (2022-04-18 04:47:49)

Retrieve attributes from stubs even when a runtime equivalent exists


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index fc2673c..92682f0 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Retrieve attributes from stubs even when a runtime
+  equivalent exists (#526)
 - Fix attribute access to stub-only names (#525)
 - Remove a number of unnecessary special-cased signatures
   (#499)
diff --git a/pyanalyze/attributes.py b/pyanalyze/attributes.py
index bcd920c..eb23a44 100644
--- a/pyanalyze/attributes.py
+++ b/pyanalyze/attributes.py
@@ -441,11 +441,16 @@ def _get_attribute_from_mro(
             if ctx.skip_mro and base_cls is not typ:
                 continue
 
-            if ctx.prefer_typeshed:
-                typeshed_type = ctx.get_attribute_from_typeshed(
-                    base_cls, on_class=on_class or ctx.skip_unwrap
-                )
-                if typeshed_type is not UNINITIALIZED_VALUE:
+            typeshed_type = ctx.get_attribute_from_typeshed(
+                base_cls, on_class=on_class or ctx.skip_unwrap
+            )
+            if typeshed_type is not UNINITIALIZED_VALUE:
+                if ctx.prefer_typeshed:
+                    return typeshed_type, base_cls, False
+                # If it's a callable, we'll probably do better
+                # getting the attribute from the type ourselves,
+                # because we may have our own implementation.
+                if not isinstance(typeshed_type, CallableValue):
                     return typeshed_type, base_cls, False
 
             try:
@@ -479,12 +484,8 @@ def _get_attribute_from_mro(
                     val = AnyValue(AnySource.inference)
                 return val, base_cls, True
 
-            if not ctx.prefer_typeshed:
-                typeshed_type = ctx.get_attribute_from_typeshed(
-                    base_cls, on_class=on_class or ctx.skip_unwrap
-                )
-                if typeshed_type is not UNINITIALIZED_VALUE:
-                    return typeshed_type, base_cls, False
+            if typeshed_type is not UNINITIALIZED_VALUE:
+                return typeshed_type, base_cls, False
 
     attrs_type = get_attrs_attribute(typ, ctx)
     if attrs_type is not None:


Test patch:
diff --git a/pyanalyze/test_attributes.py b/pyanalyze/test_attributes.py
index 8b064ba..5194490 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
+            name = 1  # E: incompatible_override
             no_name = 2
 
         def capybara():
diff --git a/pyanalyze/test_typeshed.py b/pyanalyze/test_typeshed.py
index d8b622b..9df4641 100644
--- a/pyanalyze/test_typeshed.py
+++ b/pyanalyze/test_typeshed.py
@@ -234,6 +234,14 @@ class TestBundledStubs(TestNameCheckVisitorBase):
                     | TypedValue("ctypes._CData"),
                 )
 
+    @assert_passes()
+    def test_ast(self):
+        import ast
+
+        def capybara(x: ast.Yield):
+            assert_is_value(x, TypedValue(ast.Yield))
+            assert_is_value(x.value, TypedValue(ast.expr) | KnownValue(None))
+
     @assert_passes()
     def test_import_typeddicts(self):
         def capybara():