Skip to content

False positive unreachable with redefined variable in loop #8865

Closed

Description

@mthuurne

When checking the following code:

from typing import AbstractSet, Iterable, Optional, Set, TypeVar

T = TypeVar('T')

def intersection(seq: Iterable[AbstractSet[T]]) -> Optional[Set[T]]:
    ret = None
    for elem in seq:
        if ret is None:
            ret = set(elem)
        else:
            ret &= elem
    return ret

mypy reports:

$ mypy --warn-unreachable intersection.py
intersection.py:11: error: Statement is unreachable

This statement is the ret &= elem inside the else block.

It seems that mypy is using the initial type of ret to conclude that ret is None is always true, ignoring the fact that inside the loop ret is redefined to a different type.

If I place a reveal_type just before the return, mypy does report Union[builtins.set[T`-1], None] as the type, so it figures out the redefinition eventually.

I'm using mypy 0.770 on Pyton 3.8.2, with the --warn-unreachable flag.

Activity

  1. beezee commented on May 22, 2020

    @beezee
    Contributor

    Does ret: Optional[Set] = None clear the error?

  2. mthuurne commented on May 22, 2020

    @mthuurne
    ContributorAuthor

    Yes, which is further evidence that the problem is in the order in which mypy does things: it seems to flag the code as unreachable before the final type for ret has been deduced.

  3. sbalmer commented on Mar 9, 2021

    @sbalmer

    I stumbled over this and created a minimal example:

    maximum = None
    for x in [2, 1]:
        if maximum is None:
            maximum = x
        else:
            maximum = max(x, "maximum")
    

    The last line contains an error. The variable maximum should be passed, but instead, the string "maximum" is passed. Mypy does not detect this because it assumes the last line is unreachable.

    Tested with Mypy 0.812 on Python 3.6.9.

  4. mthuurne commented on Feb 7, 2024

    @mthuurne
    ContributorAuthor

    I've got a slightly more minimal test case for what I think is the same problem:

    def check_sorted(items: list[int]) -> None:
        prev = None
        for item in items:
            reveal_type(prev)
            assert prev is None or prev <= item
            prev = item
        reveal_type(prev)

    Output of mypy 1.8.0:

    testcase.py:4: note: Revealed type is "None"
    testcase.py:5: error: Right operand of "or" is never evaluated  [unreachable]
                assert prev is None or prev <= item
                                       ^~~~~~~~~~~~
    testcase.py:7: note: Revealed type is "Union[builtins.int, None]"
    

    Expected output:

    testcase.py:4: note: Revealed type is "Union[builtins.int, None]"
    testcase.py:7: note: Revealed type is "Union[builtins.int, None]"
    

    It seems mypy is able to correctly infer the type of prev after the loop (line 7), but doesn't feed this back into the start of the loop body (line 4).

  5. added a commit that references this issue on Jan 9, 2025
    53d894a
  6. added a commit that references this issue on Jan 12, 2025
    9685171
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-reachabilityDetecting unreachable codetopic-type-narrowingConditional type narrowing / binder

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions