[ty] Delegate truthiness inference of an enum Literal type to its enum-instance supertype (#21060)

This commit is contained in:
Alex Waygood 2025-10-24 14:34:16 +01:00 committed by GitHub
parent e196c2ab37
commit bf74c824eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 12 deletions

View file

@ -78,6 +78,7 @@ python-version = "3.11"
```
```py
import enum
from typing import Literal, final
reveal_type(bool(1)) # revealed: Literal[True]
@ -129,13 +130,20 @@ class FinalClassWithNoLenOrBool: ...
reveal_type(bool(FinalClassWithNoLenOrBool())) # revealed: Literal[True]
def f(x: SingleElementTupleSubclass | FinalClassOverridingLenAndNotBool | FinalClassWithNoLenOrBool):
class EnumWithMembers(enum.Enum):
A = 1
B = 2
reveal_type(bool(EnumWithMembers.A)) # revealed: Literal[True]
def f(x: SingleElementTupleSubclass | FinalClassOverridingLenAndNotBool | FinalClassWithNoLenOrBool | Literal[EnumWithMembers.A]):
reveal_type(bool(x)) # revealed: Literal[True]
```
## Falsy values
```py
import enum
from typing import final, Literal
reveal_type(bool(0)) # revealed: Literal[False]
@ -156,13 +164,23 @@ class FinalClassOverridingLenAndNotBool:
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[False]
def f(x: EmptyTupleSubclass | FinalClassOverridingLenAndNotBool):
class EnumWithMembersOverridingBool(enum.Enum):
A = 1
B = 2
def __bool__(self) -> Literal[False]:
return False
reveal_type(bool(EnumWithMembersOverridingBool.A)) # revealed: Literal[False]
def f(x: EmptyTupleSubclass | FinalClassOverridingLenAndNotBool | Literal[EnumWithMembersOverridingBool.A]):
reveal_type(bool(x)) # revealed: Literal[False]
```
## Ambiguous values
```py
import enum
from typing import Literal
reveal_type(bool([])) # revealed: bool
@ -182,6 +200,15 @@ class NonFinalOverridingLenAndNotBool:
# because a subclass might override `__bool__`,
# and `__bool__` takes precedence over `__len__`
reveal_type(bool(NonFinalOverridingLenAndNotBool())) # revealed: bool
class EnumWithMembersOverridingBool(enum.Enum):
A = 1
B = 2
def __bool__(self) -> bool:
return False
reveal_type(bool(EnumWithMembersOverridingBool.A)) # revealed: bool
```
## `__bool__` returning `NoReturn`

View file

@ -183,13 +183,11 @@ class CustomLenEnum(Enum):
def __len__(self):
return 0
# TODO: these could be `Literal[True]`
reveal_type(bool(NormalEnum.NO)) # revealed: bool
reveal_type(bool(NormalEnum.YES)) # revealed: bool
reveal_type(bool(NormalEnum.NO)) # revealed: Literal[True]
reveal_type(bool(NormalEnum.YES)) # revealed: Literal[True]
# TODO: these could be `Literal[False]`
reveal_type(bool(FalsyEnum.NO)) # revealed: bool
reveal_type(bool(FalsyEnum.YES)) # revealed: bool
reveal_type(bool(FalsyEnum.NO)) # revealed: Literal[False]
reveal_type(bool(FalsyEnum.YES)) # revealed: Literal[False]
# All of the following must be `bool`:

View file

@ -4692,10 +4692,10 @@ impl<'db> Type<'db> {
Truthiness::Ambiguous
}
Type::EnumLiteral(_) => {
// We currently make no attempt to infer the precise truthiness, but it's not impossible to do so.
// Note that custom `__bool__` or `__len__` methods on the class or superclasses affect the outcome.
Truthiness::Ambiguous
Type::EnumLiteral(enum_type) => {
enum_type
.enum_class_instance(db)
.try_bool_impl(db, allow_short_circuit, visitor)?
}
Type::IntLiteral(num) => Truthiness::from(*num != 0),