Skip to content

warn-unreachable behaves differently on else-return than on bare return #10773

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
sirosen opened this issue Jul 6, 2021 · 4 comments · May be fixed by #18539
Open

warn-unreachable behaves differently on else-return than on bare return #10773

sirosen opened this issue Jul 6, 2021 · 4 comments · May be fixed by #18539
Labels
bug mypy got something wrong topic-error-reporting How we report errors topic-reachability Detecting unreachable code

Comments

@sirosen
Copy link

sirosen commented Jul 6, 2021

Bug Report

mypy --warn-unreachable will error on an unreachable return after an if block but not a semantically equivalent else: return ... after an if block. This shows up in platform checking logic, as an example of "unreachable code" which mypy understands and allows.

Based on what I found in existing warn-unreachable issues, there's no report of this same issue.

To Reproduce

Run mypy --warn-unreachable on the two following samples.

This passes:

def foo():
    if sys.platform != "win32":
        return 1
    else:
        return 0

This fails (on Linux/macOS. Use == "win32" to observe on Windows):

def foo():
    if sys.platform != "win32":
        return 1
    return 0

I also put together a small example dir of this in a github repo, if that makes it easier:
https://github.com/sirosen/repro/tree/2a56d3b/mypy-issues

Expected Behavior

mypy should pass on both of these, as they are the same.

Your Environment

  • Mypy version used: 0.910
@sirosen sirosen added the bug mypy got something wrong label Jul 6, 2021
@hi-ogawa
Copy link
Contributor

hi-ogawa commented Oct 3, 2021

EDIT: Sorry, I noticed that the unit test example in the "original comment" below isn't type-checked since it doesn't have annotations. I can reproduce on master with this example.

  • test.py
import sys

def f() -> int:
    if sys.platform != "win32":
        return 1
    return 0


def g() -> int:
    if sys.platform == "win32":
        return 1
    return 0
  • command
$ mypy --warn-unreachable test.py
test.py:6: error: Statement is unreachable
Found 1 error in 1 file (checked 1 source file)

(Original comment)

I cannot reproduce this on master, so possibly it is already fixed by this PR #10560, which fixed some crash from if-branch with sys.platform.
I'm using this unit test locally:

[case testUnreachableBranchBySys1]
# flags: --warn-unreachable
import sys

def f():
    if sys.platform != "win32":
        return 1
    return 0
[builtins fixtures/dict.pyi]

[case testUnreachableBranchBySys2]
# flags: --warn-unreachable
import sys

def f():
    if sys.platform == "win32":
        return 1
    return 0
[builtins fixtures/dict.pyi]

@intgr
Copy link
Contributor

intgr commented Mar 21, 2023

I experienced this issue as well, mypy 1.1.1 with --warn-unreachable

Unreachable error: (Playground link)

    if sys.platform == "linux":
        return
    print("This is reachable on other OS!")

No unreachable error: (Playground link)

    if sys.platform == "linux":
        return
    else:
        print("This is reachable on other OS!")

EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Mar 10, 2024
This changes the way code throughout the git module checks to see
if it is running on a native Windows system.

Some checks using os.name were already changed to use sys.platform
in dc95a76 and 4191f7d. (See dc95a76 on why the specific question
of whether the system is native Windows can be answered by either
checking os.name or sys.platform, even though in general they
differ in granularity and are not always suited to the same tasks.)

The reasons for this change are:

- Consistency: Due to dc95a76 and 4191f7d, some of these checks use
  sys.platform, so in the absence of a reason to do otherwise, it
  is best that they all do. Otherwise, it creates an appearance
  that the technical reasons behind the difference are stronger
  than they really are, or even that differnt things are being
  checked.

- Better static typing: mypy treats sys.platform as a constant
  (and also allows checking on platform on another via --platform).
  Likewise, typeshed conditions platform-dependent declarations on
  sys.platform checks, rather than os.name or other checks. Really
  this is the original reason for those earlier, more selective
  changes, but here the goal is more general, since this is not
  needed to address any specific preexisting mypy errors.

This is incomplete, for two reasons:

- I'm deliberately not including changes in the tests in this
  commit. Arguably it should be kept as os.name in the tests, on
  the grounds that the test suite is not currently statically typed
  anyway, plus having them differ more compellingly shows that the
  behavior is the same whether an os.name or sys.platform check is
  used. However, it would be confusing to keep them different, and
  also somewhat unnatural; one approach would probably end up
  leaking through. Furthermore, because some tests have to check
  for cygwin specifically, which cannot be done with os.name, it
  may be clearer to use sys.platform for all platform checking in
  the test suite. But to verify and demonstrate that the change
  really is safe, I'm waiting to make the change in the tests until
  after making them in the code under test.

- Some forms of checks against constants produce unreachable code
  errors from mypy (python/mypy#10773).
  This can be worked around, but I have not done that in this
  commit. Furthermore, the new mypy errors this produces--one on
  Windows, and one on non-Windows systems--could be fixed in a way
  that makes type annotations richer, by allowing the return type
  to be a literal on one platform or the other.
@A5rocks A5rocks added topic-reachability Detecting unreachable code topic-error-reporting How we report errors labels Jan 24, 2025
@ammaraskar ammaraskar marked this as a duplicate of #18417 Jan 25, 2025
@ammaraskar
Copy link
Member

ammaraskar commented Jan 26, 2025

#5964 is an old bug that is this issue plus a little more functionality.

Currently the way that sys.version_info and sys.platform checks end up not getting flagged by --warn-unreachable is this code block:

mypy/mypy/checker.py

Lines 3020 to 3025 in 1eb9d4c

if b.is_unreachable:
# This block was marked as being unreachable during semantic analysis.
# It turns out any blocks marked in this way are *intentionally* marked
# as unreachable -- so we don't display an error.
self.binder.unreachable()
return

When reachability.py and its consider_sys_version_info and consider_sys_platform functions get invoked in the first mypy pass semanal_pass1.py, they set this b.is_unreachable property:

mypy/mypy/reachability.py

Lines 327 to 329 in 1eb9d4c

def mark_block_unreachable(block: Block) -> None:
block.is_unreachable = True
block.accept(MarkImportsUnreachableVisitor())


The is_unreachable property is defined at the Block level meaning that the block for those functions looks like:

Block(
  body=[
    IfStmt(
      expr=ComparisonExpr("!=", ...),
      body=Block(body=[ReturnStmt(...)]),
      else_body=...
    ),

    ReturnStmt()
  ]
)

Currently the if/else based check works because unreachable is able to mark the else_body or body as unreachable. However, for this feature we would specifically want to mark the ReturnStmt in the outer block as unreachable and then somehow pick that up in checker.py.

I'm gonna try to see if I can come up with a PR to solve this but that's the root cause and problem of the issue.

@A5rocks
Copy link
Collaborator

A5rocks commented Jan 26, 2025

However, for this feature we would specifically want to mark the ReturnStmt as unreachable and then somehow pick that up in checker.py.

I was thinking it might be easier to have some sort of unreachability kind that frames get marked as (rather than just generally marking them as unreachable) for the binder to check... but I'm not too sure.

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-error-reporting How we report errors topic-reachability Detecting unreachable code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants