Skip to content

Commit

Permalink
NamedTuple now narrows to bool correctly, when __bool__ is defined (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Jan 9, 2022
1 parent 378119f commit c0c86d0
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
27 changes: 23 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 39 additions & 0 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit c0c86d0

Please sign in to comment.