From 3ac5a7f81d3cb3cf1ab9b4fef9f6c94e341f06d7 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Mon, 23 Jan 2023 13:31:02 +0000 Subject: [PATCH] Don't consider 'object' always truthy There are two reasons I'm proposing this change. First, we know that many subclasses of 'object' can be falsy. Second, mypy sometimes simplifies `object | Any` into just `object`. The latter was considered always truthy, while the prior one wasn't. Now both of them are treated consistently. An alternative fix would be to not simplify unions like `object | Any`, but this seems a bit ad hoc. Fixes #14480. This doesn't just fix the regression but fixes a more general issue. --- mypy/checker.py | 1 + test-data/unit/check-errorcodes.test | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 61104756b297..46200f5813cc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5151,6 +5151,7 @@ def _is_truthy_type(self, t: ProperType) -> bool: and bool(t.type) and not t.type.has_readable_member("__bool__") and not t.type.has_readable_member("__len__") + and t.type.fullname != "builtins.object" ) or isinstance(t, FunctionLike) or ( diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index d966eb44b6e3..19ce56057ff5 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -803,12 +803,15 @@ from typing_extensions import TypedDict Foo = TypedDict("Bar", {}) # E: First argument "Bar" to TypedDict() does not match variable name "Foo" [name-match] [builtins fixtures/dict.pyi] + [case testTruthyBool] # flags: --enable-error-code truthy-bool -from typing import List, Union +from typing import List, Union, Any class Foo: pass +class Bar: + pass foo = Foo() if foo: # E: "__main__.foo" has type "Foo" which does not implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] @@ -836,15 +839,30 @@ if good_union: if not good_union: pass -bad_union: Union[Foo, object] = Foo() -if bad_union: # E: "__main__.bad_union" has type "Union[Foo, object]" of which no members implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] +bad_union: Union[Foo, Bar] = Foo() +if bad_union: # E: "__main__.bad_union" has type "Union[Foo, Bar]" of which no members implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] + pass +if not bad_union: # E: "__main__.bad_union" has type "Union[Foo, Bar]" of which no members implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] + pass + +# 'object' is special and is treated as potentially falsy +obj: object = Foo() +if obj: pass -if not bad_union: # E: "__main__.bad_union" has type "object" which does not implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] +if not obj: pass lst: List[int] = [] if lst: pass + +a: Any +if a: + pass + +any_or_object: Union[object, Any] +if any_or_object: + pass [builtins fixtures/list.pyi] [case testTruthyFunctions]