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

Truthyness inference incorrect for some Enum types #17333

Closed
Daverball opened this issue Jun 5, 2024 · 1 comment · Fixed by #17337
Closed

Truthyness inference incorrect for some Enum types #17333

Daverball opened this issue Jun 5, 2024 · 1 comment · Fixed by #17337
Labels
bug mypy got something wrong

Comments

@Daverball
Copy link
Contributor

Bug Report

It looks like the enum plugin incorrectly assumes that __bool__ on enum literals will always return Literal[True], while this is the default behavior, it is not correct when someone explicitly overrides __bool__. Moreover this special casing is only applied to enum literals, but not the base enum type, which should behave the same as individual members.

To Reproduce

https://mypy-play.net/?mypy=master&python=3.12&gist=dc60c5c82ba6589e5f1d03654439b05e

import enum
from typing import Any, Literal

class Foo(enum.Enum):
    X = 0

class Bar(enum.Enum):
    X = 0
    
    def __bool__(self) -> Literal[False]:
        return False

def a(x: Any | Literal[Foo.X]):
    reveal_type(x)
    if x:
        reveal_type(x)
    else:
        reveal_type(x)

def b(x: Any | Foo):
    reveal_type(x)
    if x:
        reveal_type(x)
    else:
        reveal_type(x)  # wrong, doesn't match literal behavior

def c(x: Any | Literal[Bar.X]):
    reveal_type(x)
    if x:
        reveal_type(x)  # wrong, it's backwards
    else:
        reveal_type(x)  # wrong, it's backwards

def d(x: Any | Bar):
    reveal_type(x)
    if x:
        reveal_type(x)
    else:
        reveal_type(x)

Your Environment

  • Mypy version used: Playground with master branch
  • Mypy command-line flags: Default
  • Mypy configuration options from mypy.ini (and other config files): Default
  • Python version used: 3.12
@Daverball Daverball added the bug mypy got something wrong label Jun 5, 2024
@Daverball
Copy link
Contributor Author

Looks like the culprit is this part:

mypy/mypy/types.py

Lines 2751 to 2755 in 6682563

def can_be_false_default(self) -> bool:
return not self.value
def can_be_true_default(self) -> bool:
return bool(self.value)

Since Enum literals have a string value and are disambiguated from actual string literals, based on whether or not the fallback type is an Enum, they will always implicitly be True, since the Enum members can't have an empty name.

This part should be relatively easy to fix, but it will mean that a will now have the same incorrect behavior as b. I assume this is because the metaclass's __bool__ method is not considered for truthyness:
https://mypy-play.net/?mypy=latest&python=3.12&flags=warn-unreachable&gist=ab7d1143bb3993a5b63d0ca827aea6cf

I'd consider this a separate issue, not sure if it has already been reported, but I imagine it shouldn't be that difficult to fix either.

I'll see if I can work on a PR for this.

hauntsaninja pushed a commit that referenced this issue Nov 14, 2024
Fixes: #17333 

This ensures `can_be_true` and `can_be_false` on enum literals depends
on the specific `Enum` fallback type behind the `Literal`, since
`__bool__` can be overriden like on any other type.

Additionally typeops `true_only` and `false_only` now respect the
metaclass when looking up the return values of `__bool__` and `__len__`,
which ensures that a default `Enum` that doesn't override `__bool__` is
still considered always truthy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant