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

[1.14 regression] truthy check for enums #18376

Closed
delfick opened this issue Dec 30, 2024 · 7 comments · Fixed by #18379
Closed

[1.14 regression] truthy check for enums #18376

delfick opened this issue Dec 30, 2024 · 7 comments · Fixed by #18379
Labels
bug mypy got something wrong

Comments

@delfick
Copy link

delfick commented Dec 30, 2024

Bug Report

It appears the truthy check for enums is incorrect in 1.14 when warn_unreachable is set to True.

For example

import enum


class MyEnum(enum.StrEnum):
    EMPTY = ""
    NOT_EMPTY = "asdf"


def is_truthy(e: MyEnum) -> None:
    if e:
        print(f"{e} was truthy: {len(e)}")
    else:
        print(f"{e} was falsey: {len(e)}")


is_truthy(MyEnum.EMPTY)
is_truthy(MyEnum.NOT_EMPTY)

With a mypy.ini

[mypy]
warn_unreachable = True

If we run the script we get:

 was falsey: 0
asdf was truthy: 4

Which shows the behaviour of these enums is such that they can be used in an if condition

but running mypy a.py results in:

a.py:13: error: Statement is unreachable  [unreachable]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.14
  • Python version used: tested with and saw same problem on python3.12 and python3.13
@delfick delfick added the bug mypy got something wrong label Dec 30, 2024
@hauntsaninja
Copy link
Collaborator

Thanks, bisects to #17337

@hauntsaninja
Copy link
Collaborator

cc @Daverball in case you have time to take a look

hauntsaninja added a commit to hauntsaninja/mypy that referenced this issue Dec 30, 2024
hauntsaninja added a commit to hauntsaninja/mypy that referenced this issue Dec 30, 2024
hauntsaninja added a commit to hauntsaninja/mypy that referenced this issue Dec 30, 2024
@hauntsaninja
Copy link
Collaborator

PR here #18379

@delfick
Copy link
Author

delfick commented Dec 30, 2024

Thanks :)

@Daverball
Copy link
Contributor

This is specific to StrEnum and a typeshed bug. Even though str provides a custom __bool__ method at runtime the typeshed stubs don't represent that, so the only __bool__ method the trutyness check finds is the one on EnumType which is always True.

Case in point, the bug disappears if you use IntEnum, where int also provides its own __bool__.

So it's not really a true regression, the old broken behavior happened to be accidentally correct for StrEnum, because it used the enum value for determining truthyness, which is only correct for StrEnum and IntEnum.

We could probably slightly improve the behavior for StrEnum and IntEnum by special-casing them, so we statically determine that an IntEnum member with value 0 is always falsey and every other value is always truthy and the same for StrEnum with the empty string vs. any other string. But I'm not fully convinced it's worth the extra complication, since we would need to detect any custom enum subclass that inherits from either str or int without overriding __bool__ somewhere along the way.

@Daverball
Copy link
Contributor

Hmm, actually str's trutyhness relies on __len__ rather than __bool__, so there is actually no __bool__. So looks like this is actually a bug, so the fallback to the metaclass only should happen when there's no type providing either __bool__ or __len__ in the mro.

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Dec 30, 2024

Yup on __len__. I'm not sure metaclasses should be involved here. See the PR I opened with the link to Brett's description of CPython truthiness logic... I think it basically boils down to enum being final

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.

3 participants