Skip to content

Change final classes without __bool__ method to always be True #12187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 2 additions & 18 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = (
Expand Down
15 changes: 15 additions & 0 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,14 @@ 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:
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):
fo = false_only(self.tuple(AnyType(TypeOfAny.special_form)))
Expand Down Expand Up @@ -451,6 +459,13 @@ 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it will be more useful to add tests to some check-*.test file.

I think that these ones can be removed.

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

Expand Down
24 changes: 22 additions & 2 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,26 @@ 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 _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)

Expand All @@ -419,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:
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):
Expand All @@ -446,7 +466,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_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)
Expand Down