Skip to content

[possibly-undefined] False-positive with nested try block and finally #14309

Closed

Description

@cdce8p

Bug Report
Small false-positive I noticed after #14114.
Seems to be a scoping issue with finally.

/CC: @ilinum

To Reproduce

try:
    try:
        msg = some_func()
    except Exception:
        raise

    print(msg)  # Name "msg" may be undefined  [possibly-undefined]

finally:
    pass

print(msg)  # <-- should probably be emitted here
# EDIT: exception is re-raised - no error should be emitted

The first print will always safely execute since the inner Try block raises. If it throws an uncaught exception, the print will be skipped to the finally block and only the second print will execute which would then raise an error itself as msg would be undefined.

Your Environment

  • Mypy version used: mypy 1.0.0+dev.96ac8b3e71c743b02ea4e3c84da0248659e40f82 (compiled: no)
  • Mypy command-line flags: --enable-error-code possibly-undefined
  • Python version used: 3.11

Activity

  1. erictraut commented on Dec 17, 2022

    @erictraut

    If some_func raises an exception, the second print will never be executed because the exception will be re-raised at the end of the finally block. So I think that means no error should be generated for the final line of the code sample.

  2. ilinum commented on Dec 19, 2022

    @ilinum
    Collaborator

    Yep, good find! This only occurs with nested statements because we have to do a hack clever trick with disable_branch_skip.

    We can probably do something smarter here and only report errors in finally blocks when we're in disable_branch_skip mode.

    I don't really have time to work on this right now, so happy for someone else to pick it up. Otherwise, I could probably start in February.

  3. Dreamsorcerer commented on Jul 27, 2023

    @Dreamsorcerer
    Contributor

    Not entirely sure if this is the same issue or a separate issue, but we are also seeing this where an except then breaks from a loop:
    https://github.com/aio-libs/aiohttp/blob/63f83a0f916ee5030ee0f2dad3ba999b325c167c/aiohttp/web_protocol.py#L540-L549

  4. erictraut commented on Aug 14, 2023

    @erictraut

    Is there still an issue here? If so, could someone provide a self-contained minimal code sample?

  5. Dreamsorcerer commented on Aug 14, 2023

    @Dreamsorcerer
    Contributor

    The original example still reproduces (again, remember to enable the error code). It currently produces possibly-undefined on line 7, but not on line 12. As you mentioned above, there should be no errors, as the exception is reraised.

    In the linked example from aiohttp, we do a break in the exception handler, so also should not get any errors. However, a minimal example with break doesn't produce an error, so not sure why it behaves differently on our code base.

  6. andersk commented on Aug 14, 2023

    @andersk
    Contributor
  7. Dreamsorcerer commented on Aug 14, 2023

    @Dreamsorcerer
    Contributor

    Ah, the presence of the finally affects the output. Here's the aiohttp version with a break:

    while True:
        try:
            try:
                msg = some_func()
            except Exception:
                break
            print(msg)  # Name "msg" may be undefined  [possibly-undefined]
        except Exception:
            pass
        finally:
            pass
    
  8. asottile-sentry commented on Apr 3, 2024

    @asottile-sentry

    here's a much simpler example that appears to be an exact duplicate:

    def f(x: int) -> None:
        try:
            if x >= 5:
                y = 2
            else:
                raise AssertionError('unreachable')
            print(y)
        finally:
            print('finally!')
    $ mypy t.py --enable-error-code=possibly-undefined
    t.py:7: error: Name "y" may be undefined  [possibly-undefined]
    Found 1 error in 1 file (checked 1 source file)
  9. srittau commented on Jul 12, 2024

    @srittau
    Contributor

    Another instance from python/typeshed#12151 (when using --platform win32):

    import sys
    
    try:
        if sys.platform == "win32":
            pip_exe = ""
            reveal_type(pip_exe)  # Revealed type is "builtins.str"
        else:
            pip_exe = ""
        reveal_type(pip_exe)  # Revealed type is "builtins.str"
                              # Name "pip_exe" may be undefined  [possibly-undefined]
    finally:
        pass
  10. added a commit that references this issue on Nov 20, 2025
    3f1c0f8
  11. added a commit that references this issue on Nov 23, 2025
    eb49662
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions