Skip to content

Commit c0c86d0

Browse files
authored
NamedTuple now narrows to bool correctly, when __bool__ is defined (#11822)
1 parent 378119f commit c0c86d0

File tree

2 files changed

+62
-4
lines changed

2 files changed

+62
-4
lines changed

mypy/types.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,12 +1526,31 @@ class TupleType(ProperType):
15261526

15271527
def __init__(self, items: List[Type], fallback: Instance, line: int = -1,
15281528
column: int = -1, implicit: bool = False) -> None:
1529-
super().__init__(line, column)
1530-
self.items = items
15311529
self.partial_fallback = fallback
1530+
self.items = items
15321531
self.implicit = implicit
1533-
self.can_be_true = len(self.items) > 0
1534-
self.can_be_false = len(self.items) == 0
1532+
super().__init__(line, column)
1533+
1534+
def can_be_true_default(self) -> bool:
1535+
if self.can_be_any_bool():
1536+
# Corner case: it is a `NamedTuple` with `__bool__` method defined.
1537+
# It can be anything: both `True` and `False`.
1538+
return True
1539+
return self.length() > 0
1540+
1541+
def can_be_false_default(self) -> bool:
1542+
if self.can_be_any_bool():
1543+
# Corner case: it is a `NamedTuple` with `__bool__` method defined.
1544+
# It can be anything: both `True` and `False`.
1545+
return True
1546+
return self.length() == 0
1547+
1548+
def can_be_any_bool(self) -> bool:
1549+
return bool(
1550+
self.partial_fallback.type
1551+
and self.partial_fallback.type.fullname != 'builtins.tuple'
1552+
and self.partial_fallback.type.names.get('__bool__')
1553+
)
15351554

15361555
def length(self) -> int:
15371556
return len(self.items)

test-data/unit/check-namedtuple.test

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,3 +1081,42 @@ t: T
10811081
y: List[T] = [t]
10821082
[builtins fixtures/tuple.pyi]
10831083
[typing fixtures/typing-namedtuple.pyi]
1084+
1085+
[case testNamedTupleWithBoolNarrowsToBool]
1086+
# flags: --warn-unreachable
1087+
from typing import NamedTuple
1088+
1089+
class C(NamedTuple):
1090+
x: int
1091+
1092+
def __bool__(self) -> bool:
1093+
pass
1094+
1095+
def foo(c: C) -> None:
1096+
if c:
1097+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]"
1098+
else:
1099+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]"
1100+
1101+
def bar(c: C) -> None:
1102+
if not c:
1103+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]"
1104+
else:
1105+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C]"
1106+
1107+
class C1(NamedTuple):
1108+
x: int
1109+
1110+
def foo1(c: C1) -> None:
1111+
if c:
1112+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C1]"
1113+
else:
1114+
c # E: Statement is unreachable
1115+
1116+
def bar1(c: C1) -> None:
1117+
if not c:
1118+
c # E: Statement is unreachable
1119+
else:
1120+
reveal_type(c) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.C1]"
1121+
[builtins fixtures/tuple.pyi]
1122+
[typing fixtures/typing-namedtuple.pyi]

0 commit comments

Comments
 (0)