Skip to content

Unreachability depends on statement ordering #18901

New issue

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

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

Already on GitHub? Sign in 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'

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