False positive `unreachable` when variable is set and reset inside loop
Bug Report
mypy warns about the right side of a condition being unreachable when a variable is reset in an outer loop, then set again inside the inner loop.
To Reproduce
from typing import Optional
test: Optional[float] = None
reveal_type(test) # Revealed type: Union[builtins.float, None]
for i in range(10):
test = None
reveal_type(test) # Revealed type: None (???)
for k in range(10):
if test is None or k > test:
test = k
Expected Behavior
No error.
Actual Behavior
mypy outputs error: Right operand of "or" is never evaluated. This is clearly because the type of test is re-evaluated at some point, even though it was declared with a type and no statement narrows that type.
This appears related to #7204, #8721, #8865, #13973, #14120, except it can't be solved by declaring the type of the variable before the loop in which it's updated (because that violates no-redef).
Note: I'm not sure why anyone would want to actually do this in practice. But I thought it might provide a useful test case.
Your Environment
- Mypy version used: 1.1.1
- Mypy command-line flags: --disable-error-code name-defined, plus a few others that I don't believe could possibly have any relevance.
- Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.10.10
Similar problem here while using mypy==1.3.0.
My example code:
data = """
{foo}
bar >
baz
"""
prompt = None
reponse = None
lines = data.splitlines()
for line in lines:
if prompt and reponse: # mypy error: Right operand of "and" is never evaluated [unreachable]
print("Break reached!") # mypy error: Statement is unreachable [unreachable]
break
if not prompt and line.endswith(">"):
prompt = line
continue
if not reponse and line.endswith("}"):
reponse = line
continue
Run output:
$ python example.py
Break reached!
$
Here looking at this issue after my PR fails on CI.
+1
Here is a smaller example:
state = None
for x in 'ab':
if state is not None:
print('reachable')
state = 'x'
The script output:
reachable
Mypy error:
main.py:4:9: error: Statement is unreachable [unreachable]
print('reachable')
^~~~~~~~~~~~~~~~~~
Found 1 error in 1 file (checked 1 source file)
✕ mypy failed.
@grihabor and @Melebius your issues were fixed by https://github.com/python/mypy/pull/18433, though the initial example was not.
Nice, thanks for the ping
I tend to think the problem underlying the initial example has nothing to do with loops but with promotions, because both float and int are involved.
So, maybe this is a simpler repro:
x: float | None
y: int | None
z: int
reveal_type(x) # float | None
x = y
reveal_type(x) # None !!!!!!!!!
x = z
reveal_type(x) # int
With "normal" subtypes, the same type of type narrowing works:
class A: ...
class B(A): ...
a: A | None
b: B | None
c: B
reveal_type(a) # A | None
a = b
reveal_type(a) # B | None
a = c
reveal_type(a) # B
Likely, an attempt to solve this could start here:
https://github.com/python/mypy/blob/5a0fa556be741270e25c95a923ef8450d28dd448/mypy/meet.py#L130C1-L145C10