[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 ```py
import enum
from typing import Literal, final from typing import Literal, final
reveal_type(bool(1)) # revealed: Literal[True] reveal_type(bool(1)) # revealed: Literal[True]
@ -129,13 +130,20 @@ class FinalClassWithNoLenOrBool: ...
reveal_type(bool(FinalClassWithNoLenOrBool())) # revealed: Literal[True] 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] reveal_type(bool(x)) # revealed: Literal[True]
``` ```
## Falsy values ## Falsy values
```py ```py
import enum
from typing import final, Literal from typing import final, Literal
reveal_type(bool(0)) # revealed: Literal[False] reveal_type(bool(0)) # revealed: Literal[False]
@ -156,13 +164,23 @@ class FinalClassOverridingLenAndNotBool:
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[False] 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] reveal_type(bool(x)) # revealed: Literal[False]
``` ```
## Ambiguous values ## Ambiguous values
```py ```py
import enum
from typing import Literal from typing import Literal
reveal_type(bool([])) # revealed: bool reveal_type(bool([])) # revealed: bool
@ -182,6 +200,15 @@ class NonFinalOverridingLenAndNotBool:
# because a subclass might override `__bool__`, # because a subclass might override `__bool__`,
# and `__bool__` takes precedence over `__len__` # and `__bool__` takes precedence over `__len__`
reveal_type(bool(NonFinalOverridingLenAndNotBool())) # revealed: bool 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` ## `__bool__` returning `NoReturn`

View File

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

View File

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