Skip to content

--warn-unreachable false positive with minimal example without types explicitly set #7204

Closed

Description

@LefterisJP

Hello,

I just downloaded the new mypy v0.720 and am playing with the --warn-unreachable-flag in my projects.

I noticed one occasion where there is something that seems like a false positive, so I am making an issue for it with a minimal reproducible example.

Version

mypy==0.720
mypy-extensions==0.4.1

source code example

import sys
from typing import Optional

found_id = None

symbols_list = [{'symbol': 'A', 'id': 1}, {'symbol': 'B', 'id': 2},  {'symbol': 'B', 'id': 3}]

for entry in symbols_list:
    if entry['symbol'] == 'B':
        if found_id:
            print('error, symbol found multiple times!')
            sys.exit(1)

        found_id = entry['id']

This when tested with mypy test.py --warn-unreachable returns:

test.py:10: error: Statement is unreachable

So essentially telling us that if found_id is never entered due to the found_id type being None. But in reality the type should be Optional[int] since you can see that it's set to entry['id'] later.

If you explicitly set the types mypy no longer complains:

import sys
from typing import Optional

found_id: Optional[int] = None

symbols_list = [{'symbol': 'A', 'id': 1}, {'symbol': 'B', 'id': 2},  {'symbol': 'B', 'id': 3}]

for entry in symbols_list:
    if entry['symbol'] == 'B':
        if found_id:
            print('error, symbol found multiple times!')
            sys.exit(1)

        assert isinstance(entry['id'], int)
        found_id = entry['id']

Bug or not

Essentially I am not sure if this is intended behaviour or not. After writing this issue down I realize it all boils down to the matter of mypy not correctly inferring type of found_id.

Activity

  1. changed the title [-]--warn-unreachable false positive with minimal example without types explicityly set[/-] [+]--warn-unreachable false positive with minimal example without types explicitly set[/+] on Jul 13, 2019
  2. JelleZijlstra commented on Jul 13, 2019

    @JelleZijlstra
    Member

    I encountered something similar in our codebase. I agree that it's a bug.

  3. ilevkivskyi commented on Jul 16, 2019

    @ilevkivskyi
    Member

    This actually may be not easy to fix.

    The problem here is how binder interacts with partial types. We have this nice thing called conditional type binder that does our version of abstract interpretation (thanks to Pyre team for the official term for this). In particular, it repeatedly checks loop bodies until no types have changed. Here is a problem however: it looks like it doesn't detect a change of type if it happens because of partial types (partial None type in this example).

    We have an item in our roadmap to refactor/rethink the binder. Maybe we should actually do this, maybe this should be the next big refactoring after we are done with semantic analyzer?

    My ideal large scale view of this is like we have fine grained targets (module top-levels and top level functions). These targets can be deferred (currently only functions), but unlike in semantic analyzer we process them in mixed order because some attributes inferred in constructor may be needed at a top level. In every target we have an "abstract interpreter" that interprets expressions in domain of types instead of domain of values. It combines the functionality currently divided between partial types and type binder.

    It seems to me such model may be also more reasonable if we are going to move --allow-redefinition to something more SSA-like, plus we will be able to generalize partial types to being able infer union types after a conditional definition.

    A downside however is that we might need to make --local-partial-types the default and only supported way.

  4. JukkaL commented on Aug 12, 2019

    @JukkaL
    Collaborator

    I think that we should first try to come up with a simple fix. I suspect that we don't need a big redesign to fix this, but I'm not sure.

    @ilevkivskyi Can you create a separate issue about the binder refactoring? I agree that it's a good candidate for our next big refactoring. The first step might be a pure refactor that just cleans up and documents the existing behavior.

  5. ilevkivskyi commented on Aug 12, 2019

    @ilevkivskyi
    Member

    @JukkaL

    Can you create a separate issue about the binder refactoring?

    Opened #7324

  6. Herst commented on Sep 4, 2019

    @Herst
    Contributor

    Another testcase.

    This one causes mypy 0.720 the show false positive warning:

    from typing import Optional, Tuple
    
    def f() -> Tuple[str, str]:
        return ("a", "b")
    
    a = None
    
    while a is None or a == "a":
        a, b = f()

    "Workaround" by making the variable explicitly optional:

    from typing import Optional, Tuple
    
    def f() -> Tuple[str, str]:
        return ("a", "b")
    
    a = None # type: Optional[str]
    
    while a is None or a == "a":
        a, b = f()

    Even more minimal:

    a = None
    while a is None or a == "a":
        a = "a"
  7. patrickelectric commented on Sep 1, 2020

    @patrickelectric

    I have the same scenario described by @Herst, it looks indeed a bug.

  8. added a commit that references this issue on Jan 9, 2025
    53d894a
  9. 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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions