From 774d8c70381327bdd57333fe79da5c1b5823d5fd Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 15 Sep 2024 09:19:49 +0300 Subject: [PATCH] Check for `truthy-bool` in `not ...` unary expressions --- mypy/checker.py | 13 +++++++++++-- mypy/checkexpr.py | 1 + test-data/unit/check-errorcodes.test | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index db65660bbfbd..9c4f4ce88690 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5651,7 +5651,16 @@ def _is_truthy_type(self, t: ProperType) -> bool: ) ) - def _check_for_truthy_type(self, t: Type, expr: Expression) -> None: + def check_for_truthy_type(self, t: Type, expr: Expression) -> None: + """ + Check if a type can have a truthy value. + + Used in checks like:: + + if x: # <--- + + not x # <--- + """ if not state.strict_optional: return # if everything can be None, all bets are off @@ -6145,7 +6154,7 @@ def has_no_custom_eq_checks(t: Type) -> bool: if in_boolean_context: # We don't check `:=` values in expressions like `(a := A())`, # because they produce two error messages. - self._check_for_truthy_type(original_vartype, node) + self.check_for_truthy_type(original_vartype, node) vartype = try_expanding_sum_type_to_union(original_vartype, "builtins.bool") if_type = true_only(vartype) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9dee743ad406..22595c85e702 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4256,6 +4256,7 @@ def visit_unary_expr(self, e: UnaryExpr) -> Type: op = e.op if op == "not": result: Type = self.bool_type() + self.chk.check_for_truthy_type(operand_type, e.expr) else: method = operators.unary_op_methods[op] result, method_type = self.check_method_call_by_name(method, operand_type, [], [], e) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index c4d72388fba9..cca13347dd86 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -846,34 +846,48 @@ 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] pass +not 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] + zero = 0 if zero: pass +not zero + false = False if false: pass +not false + null = None if null: pass +not null + s = '' if s: pass +not s + good_union: Union[str, int] = 5 if good_union: pass if not good_union: pass +not good_union + 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 +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] + # 'object' is special and is treated as potentially falsy obj: object = Foo() if obj: @@ -881,18 +895,26 @@ if obj: if not obj: pass +not obj + lst: List[int] = [] if lst: pass +not lst + a: Any if a: pass +not a + any_or_object: Union[object, Any] if any_or_object: pass +not any_or_object + if (my_foo := Foo()): # E: "__main__.my_foo" has type "Foo" which does not implement __bool__ or __len__ so it could always be true in boolean context [truthy-bool] pass @@ -909,6 +931,8 @@ if not f: # E: Function "f" could always be true in boolean context [truthy-fu pass conditional_result = 'foo' if f else 'bar' # E: Function "f" could always be true in boolean context [truthy-function] +not f # E: Function "f" could always be true in boolean context [truthy-function] + [case testTruthyIterable] # flags: --enable-error-code truthy-iterable from typing import Iterable @@ -916,6 +940,8 @@ def func(var: Iterable[str]) -> None: if var: # E: "var" has type "Iterable[str]" which can always be true in boolean context. Consider using "Collection[str]" instead. [truthy-iterable] ... + not var # E: "var" has type "Iterable[str]" which can always be true in boolean context. Consider using "Collection[str]" instead. [truthy-iterable] + [case testNoOverloadImplementation] from typing import overload