Skip to content

Commit cb3bddd

Browse files
tyrallailevkivskyi
andauthored
Improve the support for promotions inside unions. (#19245)
Fixes #14987 I was puzzled as to why my previous attempts to avoid false `unreachable` warnings for loops failed for issue #14987. After some debugging, I realised that the underlying problem is that type narrowing does not work with promotions if both the declared type and the constraining type are unions: ```python x: float | None y: int | None x = y reveal_type(x) # None !!! ``` The fix seems straightforward (but let's see what the Mypy primer says) and is checked by the test cases `testNarrowPromotionsInsideUnions1` and `testNarrowPromotionsInsideUnions2`. --------- Co-authored-by: Ivan Levkivskyi <levkivskyi@gmail.com>
1 parent 6600073 commit cb3bddd

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

mypy/meet.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,28 @@ def narrow_declared_type(declared: Type, narrowed: Type) -> Type:
128128
if declared == narrowed:
129129
return original_declared
130130
if isinstance(declared, UnionType):
131+
declared_items = declared.relevant_items()
132+
if isinstance(narrowed, UnionType):
133+
narrowed_items = narrowed.relevant_items()
134+
else:
135+
narrowed_items = [narrowed]
131136
return make_simplified_union(
132137
[
133-
narrow_declared_type(x, narrowed)
134-
for x in declared.relevant_items()
138+
narrow_declared_type(d, n)
139+
for d in declared_items
140+
for n in narrowed_items
135141
# This (ugly) special-casing is needed to support checking
136142
# branches like this:
137143
# x: Union[float, complex]
138144
# if isinstance(x, int):
139145
# ...
146+
# And assignments like this:
147+
# x: float | None
148+
# y: int | None
149+
# x = y
140150
if (
141-
is_overlapping_types(x, narrowed, ignore_promotions=True)
142-
or is_subtype(narrowed, x, ignore_promotions=False)
151+
is_overlapping_types(d, n, ignore_promotions=True)
152+
or is_subtype(n, d, ignore_promotions=False)
143153
)
144154
]
145155
)

test-data/unit/check-narrowing.test

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,39 @@ while x is not None and b():
24462446
x = f()
24472447
[builtins fixtures/primitives.pyi]
24482448

2449+
[case testNarrowPromotionsInsideUnions1]
2450+
2451+
from typing import Union
2452+
2453+
x: Union[str, float, None]
2454+
y: Union[int, str]
2455+
x = y
2456+
reveal_type(x) # N: Revealed type is "Union[builtins.str, builtins.int]"
2457+
z: Union[complex, str]
2458+
z = x
2459+
reveal_type(z) # N: Revealed type is "Union[builtins.int, builtins.str]"
2460+
2461+
[builtins fixtures/primitives.pyi]
2462+
2463+
[case testNarrowPromotionsInsideUnions2]
2464+
# flags: --warn-unreachable
2465+
2466+
from typing import Optional
2467+
2468+
def b() -> bool: ...
2469+
def i() -> int: ...
2470+
x: Optional[float]
2471+
2472+
while b():
2473+
x = None
2474+
while b():
2475+
reveal_type(x) # N: Revealed type is "Union[None, builtins.int]"
2476+
if x is None or b():
2477+
x = i()
2478+
reveal_type(x) # N: Revealed type is "builtins.int"
2479+
2480+
[builtins fixtures/bool.pyi]
2481+
24492482
[case testAvoidFalseUnreachableInFinally]
24502483
# flags: --allow-redefinition-new --local-partial-types --warn-unreachable
24512484
def f() -> None:

0 commit comments

Comments
 (0)