Skip to content
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

Detect impossible condition caused by user defined __bool__ #7008

Closed
graingert opened this issue Jun 17, 2019 · 9 comments
Closed

Detect impossible condition caused by user defined __bool__ #7008

graingert opened this issue Jun 17, 2019 · 9 comments

Comments

@graingert
Copy link
Contributor

graingert commented Jun 17, 2019

Eg with the following:

from typing import *

class Foo:
    def __bool__(self) -> Literal[True]:
        return True

def some_foo() -> Foo:
    ...

def throw_error():
    raise Exception


if __name__ == '__main__':
    print(some_foo() or throw_error())
    #                   ^^^^^^^^^^^^^  impossible condition, unreachable

mypy should be able to detect that the call to throw_error is unreachable

@ilevkivskyi
Copy link
Member

Well mypy doesn't detect even simpler unreachable function calls like True or print(). It may be a useful feature to warn about unreachable code.

Related to #2395

@ilevkivskyi
Copy link
Member

#6990 is another example where this may be useful.

@Michael0x2a
Copy link
Collaborator

FWIW I've started sort of looking into adding in some kind of flag for this during the past week (I'm already poking around this corner of the codebase for literal and enum reachability stuff anyways).

I think in principle it should be straightforward: to avoid false-positives with things like sys.platform checks, we don't alert on blocks that were marked as unreachable as a part of semantic analysis and only look at results from the type-checking phase.

We'll also probably want to adjust mypy so it distinguishes between intentionally and unintentionally unreachable blocks. E.g. if somebody does if False: ... that was probably intentional so we'll probably need to do something different there. Probably we'll need to refactor find_isinstance_check a bit to make this all work cleanly, but I was maybe thinking of doing that anyways.

TBH I think the hardest/most annoying part will be constructing an understandable error message for subexpressions that were skipped due to being unreachable, just due to the "line-by-line" nature of error reporting in mypy.

Possibly adding in a "fyi this expression is always True/always False" error message might be sufficient instead, as opposed to indicating specific ignored subexpressions. Not sure, still need to do more poking around.

@ilevkivskyi
Copy link
Member

TBH I think the hardest/most annoying part will be constructing an understandable error message for subexpressions that were skipped due to being unreachable, just due to the "line-by-line" nature of error reporting in mypy.

On Python 3.8 there is a simple solution for that: ast.get_source_segment() gives exact source code for every AST node. But maybe a more generic error message would be sufficient.

E.g. if somebody does if False: ... that was probably intentional

On the other hand adding a # type: ignore to that code of switching it to if typing.TYPE_CHECKING: ... is probably a fine solution too.

@graingert
Copy link
Contributor Author

graingert commented Jun 19, 2019 via email

Michael0x2a added a commit to Michael0x2a/mypy that referenced this issue Jun 24, 2019
This diff adds a `--disallow-inferred-unreachable` flag that
reports an error if:

1. Any branch is inferred to be unreachable
2. Any subexpression in boolean expressions, inline if statements,
   and list/set/generator/dict comprehensions are always unreachable
   or redundant.

This only takes into account the results of *type* analysis. We do not
report errors for branches that are inferred to be unreachable in the
semantic analysis phase (e.g. due to `sys.platform` checks and the
like).

Those checks are all intentional/deliberately flag certain branches
as unreachable, so error messages would be annoying in those cases.

A few additional notes:

1. I didn't spend a huge amount of time trying to come up with error
   messages. They could probably do with some polishing/rewording.

2. I thought about enabling this flag for default with mypy, but
   unfortunately that ended up producing ~40 to 50 (apparently
   legitimate) error messages.

   About a fourth of them were due to runtime checks checking to see
   if some type is None (despite not being declared to be Optional),
   about a fourth seemed to be due to mypy not quite understanding how
   to handle things like traits and Bogus[...], and about a fourth
   seemed to be legitimately unnecessary checks we could safely remove.

   The final checks were a mixture of typeshed bugs and misc errors with
   the reachability checks. (e.g. see python#7048)

3. For these reasons, I decided against adding this flag to `--strict`
   and against documenting it yet. We'll probably need to flush out
   a few bugs in mypy first/get mypy to the state where we can at least
   honestly dogfood this.

Resolves python#2395; partially
addresses python#7008.
JukkaL pushed a commit that referenced this issue Jun 28, 2019
This diff adds a `--warn-unreachable` flag that reports an error if:

1. Any branch is inferred to be unreachable
2. Any subexpression in boolean expressions, inline if statements,
   and list/set/generator/dict comprehensions are always unreachable
   or redundant.

This only takes into account the results of *type* analysis. We do not
report errors for branches that are inferred to be unreachable in the
semantic analysis phase (e.g. due to `sys.platform` checks and the
like).

Those checks are all intentional/deliberately flag certain branches
as unreachable, so error messages would be annoying in those cases.

A few additional notes:

1. I thought about enabling this flag for default with mypy, but
   unfortunately that ended up producing ~40 to 50 (apparently
   legitimate) error messages.

   About a fourth of them were due to runtime checks checking to see
   if some type is None (despite not being declared to be Optional),
   about a fourth seemed to be due to mypy not quite understanding how
   to handle things like traits and Bogus[...], and about a fourth
   seemed to be legitimately unnecessary checks we could safely remove.

   The final checks were a mixture of typeshed bugs and misc errors with
   the reachability checks. (e.g. see #7048)

2. For these reasons, I decided against adding this flag to `--strict`
   and against documenting it yet. We'll probably need to flush out
   a few bugs in mypy first/get mypy to the state where we can at least
   honestly dogfood this.

Resolves #2395; partially
addresses #7008.
@ilevkivskyi
Copy link
Member

It looks like #7454 will help with this. @Michael0x2a is there something else apart from better error location needed for this issue? If not, I would propose to close this one.

@ilevkivskyi
Copy link
Member

Actually I think -> Literal[True] (currently not supported by --warn-unreachable) part is enough to keep this open.

@AlexWaygood AlexWaygood added the topic-reachability Detecting unreachable code label Mar 28, 2022
@intgr
Copy link
Contributor

intgr commented Mar 21, 2023

FWIW mypy is now capable of warning on ilevkivskyi's case with --warn-unreachable (Playground link)

from typing import *

def some_foo() -> Literal[True]:
    return True

def throw_error():
    raise Exception

if __name__ == '__main__':
    print(some_foo() or throw_error())
    #                   ^^^^^^^^^^^^^ error: Right operand of "or" is never evaluated  [unreachable]

It doesn't handle def __bool__(self) -> Literal[True] for objects.

Should we close this or keep it open for extra __bool__ handling?

@ilevkivskyi
Copy link
Member

Yeah, let's keep this open for __bool__ support.

@ilevkivskyi ilevkivskyi changed the title detect impossible condition Detect impossible condition caused by user defined __bool__ Mar 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants