Skip to content

NamedTuple now narrows to bool correctly, when __bool__ is defined #11822

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

Merged
merged 2 commits into from
Jan 9, 2022
Merged
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
27 changes: 23 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1500,12 +1500,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]