Skip to content

Commit 5c38427

Browse files
authored
Check for truthy-bool in not ... unary expressions (#17773)
Closes #17769
1 parent 77919cf commit 5c38427

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

mypy/checker.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5651,7 +5651,16 @@ def _is_truthy_type(self, t: ProperType) -> bool:
56515651
)
56525652
)
56535653

5654-
def _check_for_truthy_type(self, t: Type, expr: Expression) -> None:
5654+
def check_for_truthy_type(self, t: Type, expr: Expression) -> None:
5655+
"""
5656+
Check if a type can have a truthy value.
5657+
5658+
Used in checks like::
5659+
5660+
if x: # <---
5661+
5662+
not x # <---
5663+
"""
56555664
if not state.strict_optional:
56565665
return # if everything can be None, all bets are off
56575666

@@ -6145,7 +6154,7 @@ def has_no_custom_eq_checks(t: Type) -> bool:
61456154
if in_boolean_context:
61466155
# We don't check `:=` values in expressions like `(a := A())`,
61476156
# because they produce two error messages.
6148-
self._check_for_truthy_type(original_vartype, node)
6157+
self.check_for_truthy_type(original_vartype, node)
61496158
vartype = try_expanding_sum_type_to_union(original_vartype, "builtins.bool")
61506159

61516160
if_type = true_only(vartype)

mypy/checkexpr.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4256,6 +4256,7 @@ def visit_unary_expr(self, e: UnaryExpr) -> Type:
42564256
op = e.op
42574257
if op == "not":
42584258
result: Type = self.bool_type()
4259+
self.chk.check_for_truthy_type(operand_type, e.expr)
42594260
else:
42604261
method = operators.unary_op_methods[op]
42614262
result, method_type = self.check_method_call_by_name(method, operand_type, [], [], e)

test-data/unit/check-errorcodes.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,53 +846,75 @@ foo = Foo()
846846
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]
847847
pass
848848

849+
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]
850+
849851
zero = 0
850852
if zero:
851853
pass
852854

855+
not zero
856+
853857
false = False
854858
if false:
855859
pass
856860

861+
not false
862+
857863
null = None
858864
if null:
859865
pass
860866

867+
not null
868+
861869
s = ''
862870
if s:
863871
pass
864872

873+
not s
874+
865875
good_union: Union[str, int] = 5
866876
if good_union:
867877
pass
868878
if not good_union:
869879
pass
870880

881+
not good_union
882+
871883
bad_union: Union[Foo, Bar] = Foo()
872884
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]
873885
pass
874886
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]
875887
pass
876888

889+
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]
890+
877891
# 'object' is special and is treated as potentially falsy
878892
obj: object = Foo()
879893
if obj:
880894
pass
881895
if not obj:
882896
pass
883897

898+
not obj
899+
884900
lst: List[int] = []
885901
if lst:
886902
pass
887903

904+
not lst
905+
888906
a: Any
889907
if a:
890908
pass
891909

910+
not a
911+
892912
any_or_object: Union[object, Any]
893913
if any_or_object:
894914
pass
895915

916+
not any_or_object
917+
896918
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]
897919
pass
898920

@@ -909,13 +931,17 @@ if not f: # E: Function "f" could always be true in boolean context [truthy-fu
909931
pass
910932
conditional_result = 'foo' if f else 'bar' # E: Function "f" could always be true in boolean context [truthy-function]
911933

934+
not f # E: Function "f" could always be true in boolean context [truthy-function]
935+
912936
[case testTruthyIterable]
913937
# flags: --enable-error-code truthy-iterable
914938
from typing import Iterable
915939
def func(var: Iterable[str]) -> None:
916940
if var: # E: "var" has type "Iterable[str]" which can always be true in boolean context. Consider using "Collection[str]" instead. [truthy-iterable]
917941
...
918942

943+
not var # E: "var" has type "Iterable[str]" which can always be true in boolean context. Consider using "Collection[str]" instead. [truthy-iterable]
944+
919945
[case testNoOverloadImplementation]
920946
from typing import overload
921947

0 commit comments

Comments
 (0)