Repository: quora/pyanalyze
Pull request #388 (2022-01-09 18:33:58)

Do not error on boolean operations on values typed as `object` 


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 831ad57..ed1038e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Do not error on boolean operations on values typed
+  as `object` (#388)
 - Support some imports from stub-only modules (#386)
 - Support type evaluation functions in stubs (#386)
 - Support `TypedDict` in stubs (#386)
diff --git a/pyanalyze/boolability.py b/pyanalyze/boolability.py
index bba08fb..e1790e9 100644
--- a/pyanalyze/boolability.py
+++ b/pyanalyze/boolability.py
@@ -142,7 +142,7 @@ def _get_boolability_no_mvv(value: Value) -> Boolability:
                 return Boolability.value_always_true_mutable
             else:
                 return Boolability.value_always_false_mutable
-        type_boolability = _get_type_boolability(type(value.val))
+        type_boolability = _get_type_boolability(type(value.val), is_exact=True)
         if boolean_value:
             if type_boolability is Boolability.boolable:
                 return Boolability.value_always_true
@@ -169,7 +169,11 @@ def _get_boolability_no_mvv(value: Value) -> Boolability:
         assert False, f"unhandled value {value!r}"
 
 
-def _get_type_boolability(typ: type) -> Boolability:
+def _get_type_boolability(typ: type, *, is_exact: bool = False) -> Boolability:
+    # Special-case object as boolable because it could easily be a subtype
+    # that does support __bool__.
+    if typ is object and not is_exact:
+        return Boolability.boolable
     if safe_hasattr(typ, "__len__"):
         return Boolability.boolable
     dunder_bool = safe_getattr(typ, "__bool__", None)


Test patch:
diff --git a/pyanalyze/test_boolability.py b/pyanalyze/test_boolability.py
index c05408f..7c7be40 100644
--- a/pyanalyze/test_boolability.py
+++ b/pyanalyze/test_boolability.py
@@ -86,7 +86,7 @@ def test_get_boolability() -> None:
     # TypedValue
     assert Boolability.boolable == get_boolability(TypedValue(HasLen))
     assert Boolability.erroring_bool == get_boolability(future)
-    assert Boolability.type_always_true == get_boolability(TypedValue(object))
+    assert Boolability.boolable == get_boolability(TypedValue(object))
     assert Boolability.boolable == get_boolability(TypedValue(int))
 
     # MultiValuedValue and AnnotatedValue
@@ -166,12 +166,14 @@ class TestConditionAlwaysTrue(TestNameCheckVisitorBase):
 
     @assert_passes()
     def test_object():
+        obj = object()
+
         def capybara():
-            True if object() else False  # E: type_always_true
-            object() and False  # E: type_always_true
-            [] and object() and False  # E: type_always_true
-            object() or True  # E: type_always_true
-            not object()  # E: type_always_true
+            True if obj else False  # E: type_always_true
+            obj and False  # E: type_always_true
+            [] and obj and False  # E: type_always_true
+            obj or True  # E: type_always_true
+            not obj  # E: type_always_true
 
     @assert_passes()
     def test_async_yield_or(self):