Skip to content

Commit

Permalink
Check for truthy-bool in not ... unary expressions (#17773)
Browse files Browse the repository at this point in the history
Closes #17769
  • Loading branch information
sobolevn authored Sep 15, 2024
1 parent 77919cf commit 5c38427
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 2 deletions.
13 changes: 11 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -846,53 +846,75 @@ 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:
pass
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

Expand All @@ -909,13 +931,17 @@ 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
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

Expand Down

0 comments on commit 5c38427

Please sign in to comment.