Skip to content

Unreachability depends on statement ordering #18901

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
A5rocks opened this issue Apr 8, 2025 · 2 comments · May be fixed by #18944 or coderabbit-test/mypy#1
Open

Unreachability depends on statement ordering #18901

A5rocks opened this issue Apr 8, 2025 · 2 comments · May be fixed by #18944 or coderabbit-test/mypy#1
Labels
bug mypy got something wrong topic-reachability Detecting unreachable code

Comments

@A5rocks
Copy link
Collaborator

A5rocks commented Apr 8, 2025

Bug Report

X and sys.platform == "win32" does not mark the block as unreachable, but sys.platform == "win32" and X does. This probably has been reported before but I'm not sure how to phrase this let alone search issues.

To Reproduce

import sys

X = True

if X and sys.platform == "win32":
    reveal_type(False)  # N: Revealed type is "Literal[False]?"

Expected Behavior

I expect the branch to be unreachable, so the reveal_type should not show anything

Your Environment

Reproduced in mypy play.

  • Mypy version used: v1.15
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.12
@A5rocks A5rocks added bug mypy got something wrong topic-reachability Detecting unreachable code labels Apr 8, 2025
@sterliakov
Copy link
Collaborator

Ough. infer_condition_value is severely broken...

mypy/mypy/reachability.py

Lines 111 to 160 in c7ea011

def infer_condition_value(expr: Expression, options: Options) -> int:
"""Infer whether the given condition is always true/false.
Return ALWAYS_TRUE if always true, ALWAYS_FALSE if always false,
MYPY_TRUE if true under mypy and false at runtime, MYPY_FALSE if
false under mypy and true at runtime, else TRUTH_VALUE_UNKNOWN.
"""
pyversion = options.python_version
name = ""
negated = False
alias = expr
if isinstance(alias, UnaryExpr):
if alias.op == "not":
expr = alias.expr
negated = True
result = TRUTH_VALUE_UNKNOWN
if isinstance(expr, NameExpr):
name = expr.name
elif isinstance(expr, MemberExpr):
name = expr.name
elif isinstance(expr, OpExpr) and expr.op in ("and", "or"):
left = infer_condition_value(expr.left, options)
if (left in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == "and") or (
left in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == "or"
):
# Either `True and <other>` or `False or <other>`: the result will
# always be the right-hand-side.
return infer_condition_value(expr.right, options)
else:
# The result will always be the left-hand-side (e.g. ALWAYS_* or
# TRUTH_VALUE_UNKNOWN).
return left
else:
result = consider_sys_version_info(expr, pyversion)
if result == TRUTH_VALUE_UNKNOWN:
result = consider_sys_platform(expr, options.platform)
if result == TRUTH_VALUE_UNKNOWN:
if name == "PY2":
result = ALWAYS_FALSE
elif name == "PY3":
result = ALWAYS_TRUE
elif name == "MYPY" or name == "TYPE_CHECKING":
result = MYPY_TRUE
elif name in options.always_true:
result = ALWAYS_TRUE
elif name in options.always_false:
result = ALWAYS_FALSE
if negated:
result = inverted_truth_mapping[result]
return result

Not only it fails to account for your case, but also forgets the negation sometimes, e.g. if not (sys.platform == 'win32' or sys.platform == 'win32') will be considered unreachable on Linux, if I'm not too bad at reading this snippet. This may cause a rather funny problem to debug down the road, given that --warn-unreachable does not warn on platform-dependent unreachable code, symbols defined there will just be ignored silently along with any errors...

This should be priority-1-normal IMO, but I'm not sure I'm supposed to add priority labels, let's leave this to maintainers?

@sterliakov
Copy link
Collaborator

Yeah, the negation is indeed lost, playground:

import sys

if sys.platform == 'linux':
    # sanity check: ensure playground runs on linux
    1 + a  # E

if not sys.platform == 'win32':
    # also good, linux is not win32
    1 + 'a'  # E

if not (sys.platform == 'win32' or sys.platform == 'win32'):
    # well, check it twice and you're doomed
    1 + 'a'

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug mypy got something wrong topic-reachability Detecting unreachable code
Projects
None yet
2 participants