Skip to content

Commit be6573e

Browse files
committed
Narrow things based on key types even if the type is Never
1 parent 28894ce commit be6573e

File tree

2 files changed

+32
-18
lines changed

2 files changed

+32
-18
lines changed

mypy/checker.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6093,7 +6093,7 @@ def find_isinstance_check(
60936093
for example, some errors are suppressed.
60946094
60956095
May return {}, {}.
6096-
Can return None, None in situations involving NoReturn.
6096+
Can return None, None in situations involving Never.
60976097
"""
60986098
if_map, else_map = self.find_isinstance_check_helper(
60996099
node, in_boolean_context=in_boolean_context
@@ -6628,21 +6628,22 @@ def replay_lookup(new_parent_type: ProperType) -> Type | None:
66286628
# Take each element in the parent union and replay the original lookup procedure
66296629
# to figure out which parents are compatible.
66306630
new_parent_types = []
6631+
expr_type_p = get_proper_type(expr_type)
66316632
for item in flatten_nested_unions(parent_type.items):
66326633
member_type = replay_lookup(get_proper_type(item))
66336634
if member_type is None:
66346635
# We were unable to obtain the member type. So, we give up on refining this
66356636
# parent type entirely and abort.
66366637
return output
66376638

6638-
if is_overlapping_types(member_type, expr_type):
6639-
new_parent_types.append(item)
6639+
# note: this is not unconditionally setting `UninhabitedType`
6640+
# as there might be X.a which is a Never
6641+
if isinstance(expr_type_p, UninhabitedType):
6642+
if isinstance(get_proper_type(member_type), UninhabitedType):
6643+
new_parent_types.append(item)
66406644

6641-
# If none of the parent types overlap (if we derived an empty union), something
6642-
# went wrong. We should never hit this case, but deriving the uninhabited type or
6643-
# reporting an error both seem unhelpful. So we abort.
6644-
if not new_parent_types:
6645-
return output
6645+
elif is_overlapping_types(member_type, expr_type):
6646+
new_parent_types.append(item)
66466647

66476648
expr = parent_expr
66486649
expr_type = output[parent_expr] = make_simplified_union(new_parent_types)

test-data/unit/check-narrowing.test

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ else:
264264

265265
if x.key is Key.D:
266266
# TODO: this should narrow to Never
267-
reveal_type(x) # E: Statement is unreachable \
268-
# N: Revealed type is "Union[__main__.Object1, __main__.Object2]"
267+
reveal_type(x) # E: Statement is unreachable \
268+
# N: Revealed type is "Never"
269269
else:
270270
reveal_type(x) # N: Revealed type is "Union[__main__.Object1, __main__.Object2]"
271271
[builtins fixtures/tuple.pyi]
@@ -291,8 +291,8 @@ else:
291291
reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]"
292292

293293
if x['key'] == 'D':
294-
reveal_type(x) # E: Statement is unreachable \
295-
# N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]"
294+
reveal_type(x) # E: Statement is unreachable \
295+
# N: Revealed type is "Never"
296296
else:
297297
reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key': Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key': Union[Literal['B'], Literal['C']]})]"
298298
[builtins fixtures/primitives.pyi]
@@ -319,8 +319,8 @@ else:
319319
reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]"
320320

321321
if x['key'] == 'D':
322-
reveal_type(x) # E: Statement is unreachable \
323-
# N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]"
322+
reveal_type(x) # E: Statement is unreachable \
323+
# N: Revealed type is "Never"
324324
else:
325325
reveal_type(x) # N: Revealed type is "Union[TypedDict('__main__.TypedDict1', {'key'?: Union[Literal['A'], Literal['C']]}), TypedDict('__main__.TypedDict2', {'key'?: Union[Literal['B'], Literal['C']]})]"
326326
[builtins fixtures/primitives.pyi]
@@ -611,8 +611,8 @@ else:
611611
y: Union[Parent1, Parent2]
612612
if y["model"]["key"] is Key.C:
613613
reveal_type(y) # E: Statement is unreachable \
614-
# N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]"
615-
reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]"
614+
# N: Revealed type is "Never"
615+
reveal_type(y["model"]) # N: Revealed type is "Never"
616616
else:
617617
reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]}), 'bar': builtins.str})]"
618618
reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal[__main__.Key.A]}), TypedDict('__main__.Model2', {'key': Literal[__main__.Key.B]})]"
@@ -648,8 +648,8 @@ else:
648648
y: Union[Parent1, Parent2]
649649
if y["model"]["key"] == 'C':
650650
reveal_type(y) # E: Statement is unreachable \
651-
# N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]"
652-
reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]"
651+
# N: Revealed type is "Never"
652+
reveal_type(y["model"]) # N: Revealed type is "Never"
653653
else:
654654
reveal_type(y) # N: Revealed type is "Union[TypedDict('__main__.Parent1', {'model': TypedDict('__main__.Model1', {'key': Literal['A']}), 'foo': builtins.int}), TypedDict('__main__.Parent2', {'model': TypedDict('__main__.Model2', {'key': Literal['B']}), 'bar': builtins.str})]"
655655
reveal_type(y["model"]) # N: Revealed type is "Union[TypedDict('__main__.Model1', {'key': Literal['A']}), TypedDict('__main__.Model2', {'key': Literal['B']})]"
@@ -2445,3 +2445,16 @@ def foo(x: T) -> T:
24452445
reveal_type(x) # N: Revealed type is "T`-1"
24462446
return x
24472447
[builtins fixtures/isinstance.pyi]
2448+
2449+
[case testNarrowingToClassWithNeverProperty]
2450+
# flags: --warn-unreachable
2451+
from typing import Never
2452+
2453+
class X:
2454+
a: Never
2455+
2456+
x: X
2457+
if x.a is 5:
2458+
reveal_type(x) # N: Revealed type is "__main__.X"
2459+
else:
2460+
reveal_type(x) # N: Revealed type is "__main__.X"

0 commit comments

Comments
 (0)