Skip to content

Commit 22e73d5

Browse files
committed
Tuple length checks should narrow to Never
1 parent ca11151 commit 22e73d5

File tree

2 files changed

+33
-47
lines changed

2 files changed

+33
-47
lines changed

mypy/checker.py

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6271,14 +6271,8 @@ def find_isinstance_check_helper(
62716271
):
62726272
# Combine a `len(x) > 0` check with the default logic below.
62736273
yes_type, no_type = self.narrow_with_len(self.lookup_type(node), ">", 0)
6274-
if yes_type is not None:
6275-
yes_type = true_only(yes_type)
6276-
else:
6277-
yes_type = UninhabitedType()
6278-
if no_type is not None:
6279-
no_type = false_only(no_type)
6280-
else:
6281-
no_type = UninhabitedType()
6274+
yes_type = true_only(yes_type)
6275+
no_type = false_only(no_type)
62826276
if_map = {node: yes_type} if not isinstance(yes_type, UninhabitedType) else None
62836277
else_map = {node: no_type} if not isinstance(no_type, UninhabitedType) else None
62846278
return if_map, else_map
@@ -6952,8 +6946,8 @@ def find_tuple_len_narrowing(self, node: ComparisonExpr) -> list[tuple[TypeMap,
69526946
continue
69536947
for tpl in tuples:
69546948
yes_type, no_type = self.narrow_with_len(self.lookup_type(tpl), op, size)
6955-
yes_map = None if yes_type is None else {tpl: yes_type}
6956-
no_map = None if no_type is None else {tpl: no_type}
6949+
yes_map: TypeMap = {tpl: yes_type}
6950+
no_map: TypeMap = {tpl: no_type}
69576951
type_maps.append((yes_map, no_map))
69586952
else:
69596953
left, right = items
@@ -6970,12 +6964,12 @@ def find_tuple_len_narrowing(self, node: ComparisonExpr) -> list[tuple[TypeMap,
69706964
yes_type, no_type = self.narrow_with_len(
69716965
self.lookup_type(left.args[0]), op, r_size
69726966
)
6973-
yes_map = None if yes_type is None else {left.args[0]: yes_type}
6974-
no_map = None if no_type is None else {left.args[0]: no_type}
6967+
yes_map = {left.args[0]: yes_type}
6968+
no_map = {left.args[0]: no_type}
69756969
type_maps.append((yes_map, no_map))
69766970
return type_maps
69776971

6978-
def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type | None, Type | None]:
6972+
def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type, Type]:
69796973
"""Dispatch tuple type narrowing logic depending on the kind of type we got."""
69806974
typ = get_proper_type(typ)
69816975
if isinstance(typ, TupleType):
@@ -6991,36 +6985,28 @@ def narrow_with_len(self, typ: Type, op: str, size: int) -> tuple[Type | None, T
69916985
other_types.append(t)
69926986
continue
69936987
yt, nt = self.narrow_with_len(t, op, size)
6994-
if yt is not None:
6995-
yes_types.append(yt)
6996-
if nt is not None:
6997-
no_types.append(nt)
6988+
yes_types.append(yt)
6989+
no_types.append(nt)
6990+
69986991
yes_types += other_types
69996992
no_types += other_types
7000-
if yes_types:
7001-
yes_type = make_simplified_union(yes_types)
7002-
else:
7003-
yes_type = None
7004-
if no_types:
7005-
no_type = make_simplified_union(no_types)
7006-
else:
7007-
no_type = None
6993+
6994+
yes_type = make_simplified_union(yes_types)
6995+
no_type = make_simplified_union(no_types)
70086996
return yes_type, no_type
70096997
else:
70106998
assert False, "Unsupported type for len narrowing"
70116999

7012-
def refine_tuple_type_with_len(
7013-
self, typ: TupleType, op: str, size: int
7014-
) -> tuple[Type | None, Type | None]:
7000+
def refine_tuple_type_with_len(self, typ: TupleType, op: str, size: int) -> tuple[Type, Type]:
70157001
"""Narrow a TupleType using length restrictions."""
70167002
unpack_index = find_unpack_in_list(typ.items)
70177003
if unpack_index is None:
70187004
# For fixed length tuple situation is trivial, it is either reachable or not,
70197005
# depending on the current length, expected length, and the comparison op.
70207006
method = int_op_to_method[op]
70217007
if method(typ.length(), size):
7022-
return typ, None
7023-
return None, typ
7008+
return typ, UninhabitedType()
7009+
return UninhabitedType(), typ
70247010
unpack = typ.items[unpack_index]
70257011
assert isinstance(unpack, UnpackType)
70267012
unpacked = get_proper_type(unpack.type)
@@ -7032,7 +7018,7 @@ def refine_tuple_type_with_len(
70327018
if op in ("==", "is"):
70337019
if min_len <= size:
70347020
return typ, typ
7035-
return None, typ
7021+
return UninhabitedType(), typ
70367022
elif op in ("<", "<="):
70377023
if op == "<=":
70387024
size += 1
@@ -7042,7 +7028,7 @@ def refine_tuple_type_with_len(
70427028
# TODO: also record max_len to avoid false negatives?
70437029
unpack = UnpackType(unpacked.copy_modified(min_len=size - typ.length() + 1))
70447030
return typ, typ.copy_modified(items=prefix + [unpack] + suffix)
7045-
return None, typ
7031+
return UninhabitedType(), typ
70467032
else:
70477033
yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size)
70487034
return no_type, yes_type
@@ -7057,7 +7043,7 @@ def refine_tuple_type_with_len(
70577043
if min_len <= size:
70587044
# TODO: return fixed union + prefixed variadic tuple for no_type?
70597045
return typ.copy_modified(items=prefix + [arg] * (size - min_len) + suffix), typ
7060-
return None, typ
7046+
return UninhabitedType(), typ
70617047
elif op in ("<", "<="):
70627048
if op == "<=":
70637049
size += 1
@@ -7072,14 +7058,14 @@ def refine_tuple_type_with_len(
70727058
for n in range(size - min_len):
70737059
yes_items.append(typ.copy_modified(items=prefix + [arg] * n + suffix))
70747060
return UnionType.make_union(yes_items, typ.line, typ.column), no_type
7075-
return None, typ
7061+
return UninhabitedType(), typ
70767062
else:
70777063
yes_type, no_type = self.refine_tuple_type_with_len(typ, neg_ops[op], size)
70787064
return no_type, yes_type
70797065

70807066
def refine_instance_type_with_len(
70817067
self, typ: Instance, op: str, size: int
7082-
) -> tuple[Type | None, Type | None]:
7068+
) -> tuple[Type, Type]:
70837069
"""Narrow a homogeneous tuple using length restrictions."""
70847070
base = map_instance_to_supertype(typ, self.lookup_typeinfo("builtins.tuple"))
70857071
arg = base.args[0]
@@ -7095,14 +7081,14 @@ def refine_instance_type_with_len(
70957081
size += 1
70967082
if allow_precise:
70977083
unpack = UnpackType(self.named_generic_type("builtins.tuple", [arg]))
7098-
no_type: Type | None = TupleType(items=[arg] * size + [unpack], fallback=typ)
7084+
no_type: Type = TupleType(items=[arg] * size + [unpack], fallback=typ)
70997085
else:
71007086
no_type = typ
71017087
if allow_precise:
71027088
items = []
71037089
for n in range(size):
71047090
items.append(TupleType([arg] * n, fallback=typ))
7105-
yes_type: Type | None = UnionType.make_union(items, typ.line, typ.column)
7091+
yes_type: Type = UnionType.make_union(items, typ.line, typ.column)
71067092
else:
71077093
yes_type = typ
71087094
return yes_type, no_type

test-data/unit/check-narrowing.test

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,13 +1665,13 @@ from typing import Tuple, Union
16651665
x: Union[Tuple[int, int], Tuple[int, int, int]]
16661666
if len(x) >= 4:
16671667
reveal_type(x) # E: Statement is unreachable \
1668-
# N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
1668+
# N: Revealed type is "Never"
16691669
else:
16701670
reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
16711671

16721672
if len(x) < 2:
16731673
reveal_type(x) # E: Statement is unreachable \
1674-
# N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
1674+
# N: Revealed type is "Never"
16751675
else:
16761676
reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
16771677
[builtins fixtures/len.pyi]
@@ -1750,26 +1750,26 @@ Ts = TypeVarTuple("Ts")
17501750
def foo(x: Tuple[int, Unpack[Ts], str]) -> None:
17511751
if len(x) == 1:
17521752
reveal_type(x) # E: Statement is unreachable \
1753-
# N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
1753+
# N: Revealed type is "Never"
17541754
else:
17551755
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
17561756

17571757
if len(x) != 1:
17581758
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
17591759
else:
17601760
reveal_type(x) # E: Statement is unreachable \
1761-
# N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
1761+
# N: Revealed type is "Never"
17621762

17631763
def bar(x: Tuple[int, Unpack[Ts], str]) -> None:
17641764
if len(x) >= 2:
17651765
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
17661766
else:
17671767
reveal_type(x) # E: Statement is unreachable \
1768-
# N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
1768+
# N: Revealed type is "Never"
17691769

17701770
if len(x) < 2:
17711771
reveal_type(x) # E: Statement is unreachable \
1772-
# N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
1772+
# N: Revealed type is "Never"
17731773
else:
17741774
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`-1], builtins.str]"
17751775
[builtins fixtures/len.pyi]
@@ -1816,26 +1816,26 @@ from typing_extensions import Unpack
18161816
def foo(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None:
18171817
if len(x) == 1:
18181818
reveal_type(x) # E: Statement is unreachable \
1819-
# N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
1819+
# N: Revealed type is "Never"
18201820
else:
18211821
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
18221822

18231823
if len(x) != 1:
18241824
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
18251825
else:
18261826
reveal_type(x) # E: Statement is unreachable \
1827-
# N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
1827+
# N: Revealed type is "Never"
18281828

18291829
def bar(x: Tuple[int, Unpack[Tuple[float, ...]], str]) -> None:
18301830
if len(x) >= 2:
18311831
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
18321832
else:
18331833
reveal_type(x) # E: Statement is unreachable \
1834-
# N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
1834+
# N: Revealed type is "Never"
18351835

18361836
if len(x) < 2:
18371837
reveal_type(x) # E: Statement is unreachable \
1838-
# N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
1838+
# N: Revealed type is "Never"
18391839
else:
18401840
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.str]"
18411841
[builtins fixtures/len.pyi]

0 commit comments

Comments
 (0)