Repository: quora/pyanalyze
Pull request #481 (2022-02-12 20:20:48)

Do not treat attributes that raise as nonexistent


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 34a0b54..aacfc6d 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Do not treat attributes that raise an exception on access
+  as nonexistent (#481)
 - Improve detection of unhashable dict keys and set members (#469)
 - The `in` and `not in` operators always return 
   booleans (#480)
diff --git a/pyanalyze/attributes.py b/pyanalyze/attributes.py
index 45f9f8b..f624929 100644
--- a/pyanalyze/attributes.py
+++ b/pyanalyze/attributes.py
@@ -447,12 +447,16 @@ def _get_attribute_from_mro(
                 if typeshed_type is not UNINITIALIZED_VALUE:
                     return typeshed_type, base_cls, False
 
+            try:
+                base_dict = base_cls.__dict__
+            except Exception:
+                continue
+
             try:
                 # Make sure to use only __annotations__ that are actually on this
                 # class, not ones inherited from a base class.
-                annotations = base_cls.__dict__["__annotations__"]
+                annotations = base_dict["__annotations__"]
             except Exception:
-                # no __annotations__, or it's not a dict, or the attr isn't there
                 pass
             else:
                 attr_type = type_from_annotations(
@@ -460,13 +464,19 @@ def _get_attribute_from_mro(
                 )
                 if attr_type is not None:
                     return (attr_type, base_cls, False)
+
             try:
                 # Make sure we use only the object from this class, but do invoke
                 # the descriptor protocol with getattr.
-                base_cls.__dict__[ctx.attr]
-                return KnownValue(getattr(typ, ctx.attr)), base_cls, True
+                base_dict[ctx.attr]
             except Exception:
                 pass
+            else:
+                try:
+                    val = KnownValue(getattr(typ, ctx.attr))
+                except Exception:
+                    val = AnyValue(AnySource.inference)
+                return val, base_cls, True
 
             if not ctx.prefer_typeshed:
                 typeshed_type = ctx.get_attribute_from_typeshed(
@@ -483,8 +493,11 @@ def _get_attribute_from_mro(
         # Even if we didn't find it any __dict__, maybe getattr() finds it directly.
         try:
             return KnownValue(getattr(typ, ctx.attr)), typ, True
-        except Exception:
+        except AttributeError:
             pass
+        except Exception:
+            # It exists, but has a broken __getattr__ or something
+            return AnyValue(AnySource.inference), typ, True
 
     return UNINITIALIZED_VALUE, object, False
 


Test patch:
diff --git a/pyanalyze/test_attributes.py b/pyanalyze/test_attributes.py
index 5206820..85c1abd 100644
--- a/pyanalyze/test_attributes.py
+++ b/pyanalyze/test_attributes.py
@@ -257,6 +257,18 @@ class TestAttributes(TestNameCheckVisitorBase):
                 name = "x"
             assert_is_value(name, TypedValue(str) | KnownValue("x"))
 
+    @assert_passes()
+    def test_raising_prop(self):
+        class HasProp:
+            @property
+            def does_it_really(self) -> int:
+                raise Exception("fooled you")
+
+        has_prop = HasProp()
+
+        def capybara():
+            assert_is_value(has_prop.does_it_really, AnyValue(AnySource.inference))
+
 
 class TestHasAttrExtension(TestNameCheckVisitorBase):
     @assert_passes()