mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 03:26:25 +00:00
[ty] Consider __len__ when determining the truthiness of an instance of a tuple class or a @final class (#21049)
This commit is contained in:
parent
4522f35ea7
commit
e196c2ab37
6 changed files with 349 additions and 65 deletions
|
|
@ -78,7 +78,7 @@ python-version = "3.11"
|
|||
```
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
from typing import Literal, final
|
||||
|
||||
reveal_type(bool(1)) # revealed: Literal[True]
|
||||
reveal_type(bool((0,))) # revealed: Literal[True]
|
||||
|
|
@ -92,15 +92,11 @@ reveal_type(bool(foo)) # revealed: Literal[True]
|
|||
class SingleElementTupleSubclass(tuple[int]): ...
|
||||
|
||||
reveal_type(bool(SingleElementTupleSubclass((0,)))) # revealed: Literal[True]
|
||||
reveal_type(SingleElementTupleSubclass.__bool__) # revealed: (self: tuple[int], /) -> Literal[True]
|
||||
reveal_type(SingleElementTupleSubclass((1,)).__bool__) # revealed: () -> Literal[True]
|
||||
|
||||
# Unknown length, but we know the length is guaranteed to be >=2
|
||||
class MixedTupleSubclass(tuple[int, *tuple[str, ...], bytes]): ...
|
||||
|
||||
reveal_type(bool(MixedTupleSubclass((1, b"foo")))) # revealed: Literal[True]
|
||||
reveal_type(MixedTupleSubclass.__bool__) # revealed: (self: tuple[int, *tuple[str, ...], bytes], /) -> Literal[True]
|
||||
reveal_type(MixedTupleSubclass((1, b"foo")).__bool__) # revealed: () -> Literal[True]
|
||||
|
||||
# Unknown length with an overridden `__bool__`:
|
||||
class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
|
||||
|
|
@ -108,10 +104,6 @@ class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
|
|||
return True
|
||||
|
||||
reveal_type(bool(VariadicTupleSubclassWithDunderBoolOverride((1,)))) # revealed: Literal[True]
|
||||
reveal_type(VariadicTupleSubclassWithDunderBoolOverride.__bool__) # revealed: def __bool__(self) -> Literal[True]
|
||||
|
||||
# revealed: bound method VariadicTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
|
||||
reveal_type(VariadicTupleSubclassWithDunderBoolOverride().__bool__)
|
||||
|
||||
# Same again but for a subclass of a fixed-length tuple:
|
||||
class EmptyTupleSubclassWithDunderBoolOverride(tuple[()]):
|
||||
|
|
@ -124,11 +116,28 @@ reveal_type(EmptyTupleSubclassWithDunderBoolOverride.__bool__) # revealed: def
|
|||
|
||||
# revealed: bound method EmptyTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
|
||||
reveal_type(EmptyTupleSubclassWithDunderBoolOverride().__bool__)
|
||||
|
||||
@final
|
||||
class FinalClassOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[42]:
|
||||
return 42
|
||||
|
||||
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[True]
|
||||
|
||||
@final
|
||||
class FinalClassWithNoLenOrBool: ...
|
||||
|
||||
reveal_type(bool(FinalClassWithNoLenOrBool())) # revealed: Literal[True]
|
||||
|
||||
def f(x: SingleElementTupleSubclass | FinalClassOverridingLenAndNotBool | FinalClassWithNoLenOrBool):
|
||||
reveal_type(bool(x)) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Falsy values
|
||||
|
||||
```py
|
||||
from typing import final, Literal
|
||||
|
||||
reveal_type(bool(0)) # revealed: Literal[False]
|
||||
reveal_type(bool(())) # revealed: Literal[False]
|
||||
reveal_type(bool(None)) # revealed: Literal[False]
|
||||
|
|
@ -139,13 +148,23 @@ reveal_type(bool()) # revealed: Literal[False]
|
|||
class EmptyTupleSubclass(tuple[()]): ...
|
||||
|
||||
reveal_type(bool(EmptyTupleSubclass())) # revealed: Literal[False]
|
||||
reveal_type(EmptyTupleSubclass.__bool__) # revealed: (self: tuple[()], /) -> Literal[False]
|
||||
reveal_type(EmptyTupleSubclass().__bool__) # revealed: () -> Literal[False]
|
||||
|
||||
@final
|
||||
class FinalClassOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[False]
|
||||
|
||||
def f(x: EmptyTupleSubclass | FinalClassOverridingLenAndNotBool):
|
||||
reveal_type(bool(x)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## Ambiguous values
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
reveal_type(bool([])) # revealed: bool
|
||||
reveal_type(bool({})) # revealed: bool
|
||||
reveal_type(bool(set())) # revealed: bool
|
||||
|
|
@ -154,8 +173,15 @@ class VariadicTupleSubclass(tuple[int, ...]): ...
|
|||
|
||||
def f(x: tuple[int, ...], y: VariadicTupleSubclass):
|
||||
reveal_type(bool(x)) # revealed: bool
|
||||
reveal_type(x.__bool__) # revealed: () -> bool
|
||||
reveal_type(y.__bool__) # revealed: () -> bool
|
||||
|
||||
class NonFinalOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[42]:
|
||||
return 42
|
||||
|
||||
# We cannot consider `__len__` for a non-`@final` type,
|
||||
# because a subclass might override `__bool__`,
|
||||
# and `__bool__` takes precedence over `__len__`
|
||||
reveal_type(bool(NonFinalOverridingLenAndNotBool())) # revealed: bool
|
||||
```
|
||||
|
||||
## `__bool__` returning `NoReturn`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue