diff --git a/mypy/types.py b/mypy/types.py index 15ae47e341d4..39c0d8f64011 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1526,12 +1526,31 @@ class TupleType(ProperType): def __init__(self, items: List[Type], fallback: Instance, line: int = -1, column: int = -1, implicit: bool = False) -> None: - super().__init__(line, column) - self.items = items self.partial_fallback = fallback + self.items = items self.implicit = implicit - self.can_be_true = len(self.items) > 0 - self.can_be_false = len(self.items) == 0 + super().__init__(line, column) + + def can_be_true_default(self) -> bool: + if self.can_be_any_bool(): + # Corner case: it is a `NamedTuple` with `__bool__` method defined. + # It can be anything: both `True` and `False`. + return True + return self.length() > 0 + + def can_be_false_default(self) -> bool: + if self.can_be_any_bool(): + # Corner case: it is a `NamedTuple` with `__bool__` method defined. + # It can be anything: both `True` and `False`. + return True + return self.length() == 0 + + def can_be_any_bool(self) -> bool: + return bool( + self.partial_fallback.type + and self.partial_fallback.type.fullname != 'builtins.tuple' + and self.partial_fallback.type.names.get('__bool__') + ) def length(self) -> int: return len(self.items) diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 5d28d78013f4..b95cc96f8115 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -1081,3 +1081,42 @@ t: T y: List[T] = [t] [builtins fixtures/tuple.pyi] [typing fixtures/typing-namedtuple.pyi] + +[case testNamedTupleWithBoolNarrowsToBool] +# flags: --warn-unreachable +from typing import NamedTuple + +class C(NamedTuple): + x: int + + def __bool__(self) -> bool: + pass + +def foo(c: C) -> None: + if c: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]" + else: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]" + +def bar(c: C) -> None: + if not c: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]" + else: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]" + +class C1(NamedTuple): + x: int + +def foo1(c: C1) -> None: + if c: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C1]" + else: + c # E: Statement is unreachable + +def bar1(c: C1) -> None: + if not c: + c # E: Statement is unreachable + else: + reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C1]" +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-namedtuple.pyi]