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/test/testtypes.py b/mypy/test/testtypes.py index 6af1c18145cfe..9fb1f458856bc 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -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))) @@ -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: + 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 diff --git a/mypy/typeops.py b/mypy/typeops.py index 88d4bc744bdf8..f6fb1bbdd5d58 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -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) @@ -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): @@ -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)