Skip to content

Incomplete or inconsistent Literal and Union behavior on conditional checks #9220

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

Closed
kprzybyla opened this issue Jul 27, 2020 · 3 comments · Fixed by #9297
Closed

Incomplete or inconsistent Literal and Union behavior on conditional checks #9220

kprzybyla opened this issue Jul 27, 2020 · 3 comments · Fixed by #9297
Labels
false-positive mypy gave an error on correct code feature priority-1-normal

Comments

@kprzybyla
Copy link
Contributor

kprzybyla commented Jul 27, 2020

Used mypy version: 0.782

I stumbled upon some incomplete behavior related to Literal and Union. In most cases Union is not properly expanded upon conditional check when dealing with literals. Below example should describe clearly the situation.

from typing import Union
from typing_extensions import Literal

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

    @property
    def is_ok(self) -> Literal[True]:
        return True

    def ok(self) -> Literal[True]:
        return True

class B:
    def __bool__(self) -> Literal[False]:
        return False

    @property
    def is_ok(self) -> Literal[False]:
        return False

    def ok(self) -> Literal[False]:
        return False


def get_a_or_b() -> Union[A, B]: ...

thing = get_a_or_b()

if thing:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

if bool(thing):
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

if thing.is_ok:
    reveal_type(thing)  # Revealed type is 'main.A'
else:
    reveal_type(thing)  # Revealed type is 'main.B'

if thing.ok():
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

I would expect that all examples will give the same result as the one with is_ok property. Maybe the bool() example would require some additional thought but it would seem wise to make it also work. Also, walrus operator makes it so that even the is_ok property example does not work. Other examples behave the same as before:

if thing := get_a_or_b():
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

if bool(thing := get_a_or_b()):
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

if (thing := get_a_or_b()).is_ok:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'

if (thing := get_a_or_b()).ok():
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
else:
    reveal_type(thing)  # Revealed type is 'Union[main.A, main.B]'
@Akuli
Copy link
Contributor

Akuli commented Jul 29, 2020

i discovered similar := operator problems in #9229

@JukkaL
Copy link
Collaborator

JukkaL commented Jul 31, 2020

Yeah, the behavior seems inconsistent. I'm not sure about the bool() case, though.

@kprzybyla
Copy link
Contributor Author

I opened PR #9288 that at least fixes the walrus operator usage for the property case. I would be grateful for any kind of feedback :)
I will try to also work on the rest of examples but it will probably take some time since I'm not that much familiar with the code base. And the rest of cases seems like a bit of work.

The bool() case is not so obvious so this probably would need to be discussed a bit more. I will probably try to fix the other examples first and then it can be decided whether it is reasonable to handle this bool() case.

JukkaL pushed a commit that referenced this issue Aug 14, 2020
…g in if statements (#9297)

This adds support for union narrowing in if statements when condition value has 
defined literal annotations in `__bool__` method. Value is narrowed based on the 
`__bool__` method return annotation and this works even if multiple instances 
defines the same literal value for `__bool__` method return type.

This PR also works well with #9288 and 
makes below example to work as expected:

```python
class A:
    def __bool__(self) -> Literal[True]: ...

class B:
    def __bool__(self) -> Literal[False]: ...

def get_thing() -> Union[A, B]: ...

if x := get_thing():
    reveal_type(x)  # Revealed type is '__main__.A'
else:
    reveal_type(x)  # Revealed type is '__main__.B'
```

Partially fixes #9220
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
false-positive mypy gave an error on correct code feature priority-1-normal
Projects
None yet
3 participants