From b040060cd8f9dd438c5ce3e51738945c506e8381 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Tue, 15 Feb 2022 17:50:21 -0800 Subject: [PATCH 1/4] change classes without __bool__ to always be true --- mypy/checker.py | 20 ++------------------ mypy/typeops.py | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2f99b9b4fece7..64b51f5e001a5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -58,7 +58,7 @@ try_getting_str_literals_from_type, try_getting_int_literals_from_type, tuple_fallback, is_singleton_type, try_expanding_sum_type_to_union, true_only, false_only, function_type, get_type_vars, custom_special_method, - is_literal_type_like, + is_literal_type_like, is_truthy_type ) from mypy import message_registry from mypy.message_registry import ErrorMessage @@ -4302,27 +4302,12 @@ def conditional_callable_type_map(self, expr: Expression, return None, {} - def _is_truthy_type(self, t: ProperType) -> bool: - return ( - ( - isinstance(t, Instance) and - bool(t.type) and - not t.type.has_readable_member('__bool__') and - not t.type.has_readable_member('__len__') - ) - or isinstance(t, FunctionLike) - or ( - isinstance(t, UnionType) and - all(self._is_truthy_type(t) for t in get_proper_types(t.items)) - ) - ) - def _check_for_truthy_type(self, t: Type, expr: Expression) -> None: if not state.strict_optional: return # if everything can be None, all bets are off t = get_proper_type(t) - if not self._is_truthy_type(t): + if not is_truthy_type(t): return def format_expr_type() -> str: @@ -4686,7 +4671,6 @@ def has_no_custom_eq_checks(t: Type) -> bool: original_vartype = type_map[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) else_type = false_only(vartype) if_map = ( diff --git a/mypy/typeops.py b/mypy/typeops.py index 88d4bc744bdf8..bb8635c34e7b3 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -397,6 +397,22 @@ def make_simplified_union(items: Sequence[Type], return UnionType.make_union(simplified_set, line, column) +def is_truthy_type(t: ProperType) -> bool: + return ( + ( + isinstance(t, Instance) and + bool(t.type) and + not t.type.has_readable_member('__bool__') and + not t.type.has_readable_member('__len__') + ) + or isinstance(t, FunctionLike) + or ( + isinstance(t, UnionType) and + all(is_truthy_type(t) for t in get_proper_types(t.items)) + ) + ) + + def _get_type_special_method_bool_ret_type(t: Type) -> Optional[Type]: t = get_proper_type(t) @@ -419,7 +435,7 @@ def true_only(t: Type) -> ProperType: if not t.can_be_true: # All values of t are False-ish, so there are no true values in it return UninhabitedType(line=t.line, column=t.column) - elif not t.can_be_false: + elif not t.can_be_false or is_truthy_type(t): # All values of t are already True-ish, so true_only is idempotent in this case return t elif isinstance(t, UnionType): @@ -446,7 +462,7 @@ def false_only(t: Type) -> ProperType: """ t = get_proper_type(t) - if not t.can_be_false: + if not t.can_be_false or is_truthy_type(t): if state.strict_optional: # All values of t are True-ish, so there are no false values in it return UninhabitedType(line=t.line) From 88306d436689896f460669aa1db8e78095a96fbc Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Tue, 15 Feb 2022 17:51:46 -0800 Subject: [PATCH 2/4] add test for objects without __bool__ being true --- mypy/test/testtypes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 6af1c18145cfe..5e39ba0e41fbd 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -409,6 +409,12 @@ def test_true_only_of_union(self) -> None: assert not to.items[0].can_be_false assert to.items[1] is tup_type + def test_true_only_of_truthy_type(self) -> None: + to = true_only(self.fx.b) + assert_equal(self.fx.b, to) + un = true_only(self.fx.a) + assert_type(UninhabitedType, un) + def test_false_only_of_true_type_is_uninhabited(self) -> None: with strict_optional_set(True): fo = false_only(self.tuple(AnyType(TypeOfAny.special_form))) @@ -451,6 +457,10 @@ def test_false_only_of_union(self) -> None: assert fo.items[0].can_be_false assert fo.items[1] is tup_type + def test_false_only_of_truthy_type_is_uninhabited(self) -> None: + fo = false_only(self.fx.b) + assert_type(UninhabitedType, fo) + def test_simplified_union(self) -> None: fx = self.fx From fdbb35cb13ccad289f2e002b0a22dcac79b34576 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Tue, 15 Feb 2022 20:23:00 -0800 Subject: [PATCH 3/4] change: only final classes are truthy --- mypy/typeops.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index bb8635c34e7b3..f6fb1bbdd5d58 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -413,6 +413,10 @@ def is_truthy_type(t: ProperType) -> bool: ) +def _is_final_truthy_type(t: ProperType) -> bool: + return is_truthy_type(t) and (not isinstance(t, Instance) or t.type.is_final) + + def _get_type_special_method_bool_ret_type(t: Type) -> Optional[Type]: t = get_proper_type(t) @@ -435,7 +439,7 @@ def true_only(t: Type) -> ProperType: if not t.can_be_true: # All values of t are False-ish, so there are no true values in it return UninhabitedType(line=t.line, column=t.column) - elif not t.can_be_false or is_truthy_type(t): + elif not t.can_be_false or _is_final_truthy_type(t): # All values of t are already True-ish, so true_only is idempotent in this case return t elif isinstance(t, UnionType): @@ -462,7 +466,7 @@ def false_only(t: Type) -> ProperType: """ t = get_proper_type(t) - if not t.can_be_false or is_truthy_type(t): + if not t.can_be_false or _is_final_truthy_type(t): if state.strict_optional: # All values of t are True-ish, so there are no false values in it return UninhabitedType(line=t.line) From 852f4542799b7e481082116aec22ccf506b81428 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Tue, 15 Feb 2022 20:25:33 -0800 Subject: [PATCH 4/4] fix truthy type tests --- mypy/test/testtypes.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 5e39ba0e41fbd..9fb1f458856bc 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -410,10 +410,12 @@ def test_true_only_of_union(self) -> None: assert to.items[1] is tup_type def test_true_only_of_truthy_type(self) -> None: - to = true_only(self.fx.b) - assert_equal(self.fx.b, to) - un = true_only(self.fx.a) - assert_type(UninhabitedType, un) + t = self.fx.d + t.type.is_final = True + dto = true_only(self.fx.d) + assert_equal(self.fx.d, dto) + fto = true_only(self.fx.function) + assert_equal(self.fx.function, fto) def test_false_only_of_true_type_is_uninhabited(self) -> None: with strict_optional_set(True): @@ -458,8 +460,11 @@ def test_false_only_of_union(self) -> None: assert fo.items[1] is tup_type def test_false_only_of_truthy_type_is_uninhabited(self) -> None: - fo = false_only(self.fx.b) - assert_type(UninhabitedType, fo) + t = self.fx.d + t.type.is_final = True + with strict_optional_set(True): + fo = false_only(t) + assert_type(UninhabitedType, fo) def test_simplified_union(self) -> None: fx = self.fx