mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-11-03 21:24:29 +00:00 
			
		
		
		
	[ty] Extend tuple __len__ and __bool__ special casing to also cover tuple subclasses (#19289)
				
					
				
			Co-authored-by: Brent Westbrook
This commit is contained in:
		
							parent
							
								
									4dec44ae49
								
							
						
					
					
						commit
						c2380fa0e2
					
				
					 6 changed files with 194 additions and 16 deletions
				
			
		| 
						 | 
					@ -72,7 +72,14 @@ reveal_type(my_bool(0))  # revealed: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Truthy values
 | 
					## Truthy values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```toml
 | 
				
			||||||
 | 
					[environment]
 | 
				
			||||||
 | 
					python-version = "3.11"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```py
 | 
					```py
 | 
				
			||||||
 | 
					from typing import Literal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
reveal_type(bool(1))  # revealed: Literal[True]
 | 
					reveal_type(bool(1))  # revealed: Literal[True]
 | 
				
			||||||
reveal_type(bool((0,)))  # revealed: Literal[True]
 | 
					reveal_type(bool((0,)))  # revealed: Literal[True]
 | 
				
			||||||
reveal_type(bool("NON EMPTY"))  # revealed: Literal[True]
 | 
					reveal_type(bool("NON EMPTY"))  # revealed: Literal[True]
 | 
				
			||||||
| 
						 | 
					@ -81,6 +88,42 @@ reveal_type(bool(True))  # revealed: Literal[True]
 | 
				
			||||||
def foo(): ...
 | 
					def foo(): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
reveal_type(bool(foo))  # revealed: Literal[True]
 | 
					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().__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().__bool__)  # revealed: () -> Literal[True]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Unknown length with an overridden `__bool__`:
 | 
				
			||||||
 | 
					class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
 | 
				
			||||||
 | 
					    def __bool__(self) -> Literal[True]:
 | 
				
			||||||
 | 
					        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[()]):
 | 
				
			||||||
 | 
					    # TODO: we should reject this override as a Liskov violation:
 | 
				
			||||||
 | 
					    def __bool__(self) -> Literal[True]:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(bool(EmptyTupleSubclassWithDunderBoolOverride(())))  # revealed: Literal[True]
 | 
				
			||||||
 | 
					reveal_type(EmptyTupleSubclassWithDunderBoolOverride.__bool__)  # revealed: def __bool__(self) -> Literal[True]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revealed: bound method EmptyTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
 | 
				
			||||||
 | 
					reveal_type(EmptyTupleSubclassWithDunderBoolOverride().__bool__)
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Falsy values
 | 
					## Falsy values
 | 
				
			||||||
| 
						 | 
					@ -92,6 +135,12 @@ reveal_type(bool(None))  # revealed: Literal[False]
 | 
				
			||||||
reveal_type(bool(""))  # revealed: Literal[False]
 | 
					reveal_type(bool(""))  # revealed: Literal[False]
 | 
				
			||||||
reveal_type(bool(False))  # revealed: Literal[False]
 | 
					reveal_type(bool(False))  # revealed: Literal[False]
 | 
				
			||||||
reveal_type(bool())  # revealed: Literal[False]
 | 
					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]
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Ambiguous values
 | 
					## Ambiguous values
 | 
				
			||||||
| 
						 | 
					@ -100,6 +149,13 @@ reveal_type(bool())  # revealed: Literal[False]
 | 
				
			||||||
reveal_type(bool([]))  # revealed: bool
 | 
					reveal_type(bool([]))  # revealed: bool
 | 
				
			||||||
reveal_type(bool({}))  # revealed: bool
 | 
					reveal_type(bool({}))  # revealed: bool
 | 
				
			||||||
reveal_type(bool(set()))  # revealed: bool
 | 
					reveal_type(bool(set()))  # revealed: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## `__bool__` returning `NoReturn`
 | 
					## `__bool__` returning `NoReturn`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,6 +65,51 @@ reveal_type(len((*[], 1, 2)))  # revealed: Literal[3]
 | 
				
			||||||
reveal_type(len((*[], *{})))  # revealed: Literal[2]
 | 
					reveal_type(len((*[], *{})))  # revealed: Literal[2]
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tuple subclasses:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py
 | 
				
			||||||
 | 
					class EmptyTupleSubclass(tuple[()]): ...
 | 
				
			||||||
 | 
					class Length1TupleSubclass(tuple[int]): ...
 | 
				
			||||||
 | 
					class Length2TupleSubclass(tuple[int, str]): ...
 | 
				
			||||||
 | 
					class UnknownLengthTupleSubclass(tuple[int, ...]): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(len(EmptyTupleSubclass()))  # revealed: Literal[0]
 | 
				
			||||||
 | 
					reveal_type(len(Length1TupleSubclass((1,))))  # revealed: Literal[1]
 | 
				
			||||||
 | 
					reveal_type(len(Length2TupleSubclass((1, "foo"))))  # revealed: Literal[2]
 | 
				
			||||||
 | 
					reveal_type(len(UnknownLengthTupleSubclass((1, 2, 3))))  # revealed: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(tuple[int, int].__len__)  # revealed: (self: tuple[int, int], /) -> Literal[2]
 | 
				
			||||||
 | 
					reveal_type(tuple[int, ...].__len__)  # revealed: (self: tuple[int, ...], /) -> int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def f(x: tuple[int, int], y: tuple[int, ...]):
 | 
				
			||||||
 | 
					    reveal_type(x.__len__)  # revealed: () -> Literal[2]
 | 
				
			||||||
 | 
					    reveal_type(y.__len__)  # revealed: () -> int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(EmptyTupleSubclass.__len__)  # revealed: (self: tuple[()], /) -> Literal[0]
 | 
				
			||||||
 | 
					reveal_type(EmptyTupleSubclass().__len__)  # revealed: () -> Literal[0]
 | 
				
			||||||
 | 
					reveal_type(UnknownLengthTupleSubclass.__len__)  # revealed: (self: tuple[int, ...], /) -> int
 | 
				
			||||||
 | 
					reveal_type(UnknownLengthTupleSubclass().__len__)  # revealed: () -> int
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If `__len__` is overridden, we use the overridden return type:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```py
 | 
				
			||||||
 | 
					from typing import Literal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnknownLengthSubclassWithDunderLenOverridden(tuple[int, ...]):
 | 
				
			||||||
 | 
					    def __len__(self) -> Literal[42]:
 | 
				
			||||||
 | 
					        return 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(len(UnknownLengthSubclassWithDunderLenOverridden()))  # revealed: Literal[42]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FixedLengthSubclassWithDunderLenOverridden(tuple[int]):
 | 
				
			||||||
 | 
					    # TODO: we should complain about this as a Liskov violation (incompatible override)
 | 
				
			||||||
 | 
					    def __len__(self) -> Literal[42]:
 | 
				
			||||||
 | 
					        return 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					reveal_type(len(FixedLengthSubclassWithDunderLenOverridden((1,))))  # revealed: Literal[42]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Lists, sets and dictionaries
 | 
					### Lists, sets and dictionaries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```py
 | 
					```py
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -551,6 +551,11 @@ static_assert(is_subtype_of(Never, AlwaysFalsy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### `AlwaysTruthy` and `AlwaysFalsy`
 | 
					### `AlwaysTruthy` and `AlwaysFalsy`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```toml
 | 
				
			||||||
 | 
					[environment]
 | 
				
			||||||
 | 
					python-version = "3.11"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```py
 | 
					```py
 | 
				
			||||||
from ty_extensions import AlwaysTruthy, AlwaysFalsy, Intersection, Not, is_subtype_of, static_assert
 | 
					from ty_extensions import AlwaysTruthy, AlwaysFalsy, Intersection, Not, is_subtype_of, static_assert
 | 
				
			||||||
from typing_extensions import Literal, LiteralString
 | 
					from typing_extensions import Literal, LiteralString
 | 
				
			||||||
| 
						 | 
					@ -588,6 +593,30 @@ static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]],
 | 
				
			||||||
static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal[""]]], Not[AlwaysFalsy]))
 | 
					static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal[""]]], Not[AlwaysFalsy]))
 | 
				
			||||||
# error: [static-assert-error]
 | 
					# error: [static-assert-error]
 | 
				
			||||||
static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy]))
 | 
					static_assert(is_subtype_of(Intersection[LiteralString, Not[Literal["", "a"]]], Not[AlwaysFalsy]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Length2TupleSubclass(tuple[int, str]): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(is_subtype_of(Length2TupleSubclass, AlwaysTruthy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmptyTupleSubclass(tuple[()]): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(is_subtype_of(EmptyTupleSubclass, AlwaysFalsy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TupleSubclassWithAtLeastLength2(tuple[int, *tuple[str, ...], bytes]): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(is_subtype_of(TupleSubclassWithAtLeastLength2, AlwaysTruthy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnknownLength(tuple[int, ...]): ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(not is_subtype_of(UnknownLength, AlwaysTruthy))
 | 
				
			||||||
 | 
					static_assert(not is_subtype_of(UnknownLength, AlwaysFalsy))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Invalid(tuple[int, str]):
 | 
				
			||||||
 | 
					    # TODO: we should emit an error here (Liskov violation)
 | 
				
			||||||
 | 
					    def __bool__(self) -> Literal[False]:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(is_subtype_of(Invalid, AlwaysFalsy))
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### `TypeGuard` and `TypeIs`
 | 
					### `TypeGuard` and `TypeIs`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ use crate::types::infer::infer_unpack_types;
 | 
				
			||||||
use crate::types::mro::{Mro, MroError, MroIterator};
 | 
					use crate::types::mro::{Mro, MroError, MroIterator};
 | 
				
			||||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
 | 
					pub(crate) use crate::types::narrow::infer_narrowing_constraint;
 | 
				
			||||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
 | 
					use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
 | 
				
			||||||
use crate::types::tuple::{TupleSpec, TupleType};
 | 
					use crate::types::tuple::TupleType;
 | 
				
			||||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
 | 
					pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
 | 
				
			||||||
use crate::{Db, FxOrderSet, Module, Program};
 | 
					use crate::{Db, FxOrderSet, Module, Program};
 | 
				
			||||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
 | 
					pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
 | 
				
			||||||
| 
						 | 
					@ -3508,14 +3508,7 @@ impl<'db> Type<'db> {
 | 
				
			||||||
            Type::BooleanLiteral(bool) => Truthiness::from(*bool),
 | 
					            Type::BooleanLiteral(bool) => Truthiness::from(*bool),
 | 
				
			||||||
            Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
 | 
					            Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
 | 
				
			||||||
            Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
 | 
					            Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
 | 
				
			||||||
            Type::Tuple(tuple) => match tuple.tuple(db).len().size_hint() {
 | 
					            Type::Tuple(tuple) => tuple.truthiness(db),
 | 
				
			||||||
                // The tuple type is AlwaysFalse if it contains only the empty tuple
 | 
					 | 
				
			||||||
                (_, Some(0)) => Truthiness::AlwaysFalse,
 | 
					 | 
				
			||||||
                // The tuple type is AlwaysTrue if its inhabitants must always have length >=1
 | 
					 | 
				
			||||||
                (minimum, _) if minimum > 0 => Truthiness::AlwaysTrue,
 | 
					 | 
				
			||||||
                // The tuple type is Ambiguous if its inhabitants could be of any length
 | 
					 | 
				
			||||||
                _ => Truthiness::Ambiguous,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(truthiness)
 | 
					        Ok(truthiness)
 | 
				
			||||||
| 
						 | 
					@ -3542,10 +3535,12 @@ impl<'db> Type<'db> {
 | 
				
			||||||
        let usize_len = match self {
 | 
					        let usize_len = match self {
 | 
				
			||||||
            Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
 | 
					            Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
 | 
				
			||||||
            Type::StringLiteral(string) => Some(string.python_len(db)),
 | 
					            Type::StringLiteral(string) => Some(string.python_len(db)),
 | 
				
			||||||
            Type::Tuple(tuple) => match tuple.tuple(db) {
 | 
					
 | 
				
			||||||
                TupleSpec::Fixed(tuple) => Some(tuple.len()),
 | 
					            // N.B. This is strictly-speaking redundant, since the `__len__` method on tuples
 | 
				
			||||||
                TupleSpec::Variable(_) => None,
 | 
					            // is special-cased in `ClassType::own_class_member`. However, it's probably more
 | 
				
			||||||
            },
 | 
					            // efficient to short-circuit here and check against the tuple spec directly,
 | 
				
			||||||
 | 
					            // rather than going through the `__len__` method.
 | 
				
			||||||
 | 
					            Type::Tuple(tuple) => tuple.tuple(db).len().into_fixed_length(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _ => None,
 | 
					            _ => None,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -574,9 +574,39 @@ impl<'db> ClassType<'db> {
 | 
				
			||||||
    /// traverse through the MRO until it finds the member.
 | 
					    /// traverse through the MRO until it finds the member.
 | 
				
			||||||
    pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
 | 
					    pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
 | 
				
			||||||
        let (class_literal, specialization) = self.class_literal(db);
 | 
					        let (class_literal, specialization) = self.class_literal(db);
 | 
				
			||||||
        class_literal
 | 
					
 | 
				
			||||||
 | 
					        let synthesize_tuple_method = |return_type| {
 | 
				
			||||||
 | 
					            let parameters =
 | 
				
			||||||
 | 
					                Parameters::new([Parameter::positional_only(Some(Name::new_static("self")))
 | 
				
			||||||
 | 
					                    .with_annotated_type(Type::instance(db, self))]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let synthesized_dunder_method =
 | 
				
			||||||
 | 
					                CallableType::function_like(db, Signature::new(parameters, Some(return_type)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Place::bound(synthesized_dunder_method).into()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match name {
 | 
				
			||||||
 | 
					            "__len__" if class_literal.is_known(db, KnownClass::Tuple) => {
 | 
				
			||||||
 | 
					                let return_type = specialization
 | 
				
			||||||
 | 
					                    .and_then(|spec| spec.tuple(db).len().into_fixed_length())
 | 
				
			||||||
 | 
					                    .and_then(|len| i64::try_from(len).ok())
 | 
				
			||||||
 | 
					                    .map(Type::IntLiteral)
 | 
				
			||||||
 | 
					                    .unwrap_or_else(|| KnownClass::Int.to_instance(db));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                synthesize_tuple_method(return_type)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            "__bool__" if class_literal.is_known(db, KnownClass::Tuple) => {
 | 
				
			||||||
 | 
					                let return_type = specialization
 | 
				
			||||||
 | 
					                    .map(|spec| spec.tuple(db).truthiness().into_type(db))
 | 
				
			||||||
 | 
					                    .unwrap_or_else(|| KnownClass::Bool.to_instance(db));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                synthesize_tuple_method(return_type)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => class_literal
 | 
				
			||||||
                .own_class_member(db, specialization, name)
 | 
					                .own_class_member(db, specialization, name)
 | 
				
			||||||
            .map_type(|ty| ty.apply_optional_specialization(db, specialization))
 | 
					                .map_type(|ty| ty.apply_optional_specialization(db, specialization)),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Look up an instance attribute (available in `__dict__`) of the given name.
 | 
					    /// Look up an instance attribute (available in `__dict__`) of the given name.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,7 @@ use std::hash::Hash;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use itertools::{Either, EitherOrBoth, Itertools};
 | 
					use itertools::{Either, EitherOrBoth, Itertools};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::types::Truthiness;
 | 
				
			||||||
use crate::types::class::{ClassType, KnownClass};
 | 
					use crate::types::class::{ClassType, KnownClass};
 | 
				
			||||||
use crate::types::{
 | 
					use crate::types::{
 | 
				
			||||||
    Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
 | 
					    Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
 | 
				
			||||||
| 
						 | 
					@ -76,6 +77,13 @@ impl TupleLength {
 | 
				
			||||||
            None => "unlimited".to_string(),
 | 
					            None => "unlimited".to_string(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn into_fixed_length(self) -> Option<usize> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            TupleLength::Fixed(len) => Some(len),
 | 
				
			||||||
 | 
					            TupleLength::Variable(_, _) => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// # Ordering
 | 
					/// # Ordering
 | 
				
			||||||
| 
						 | 
					@ -240,6 +248,10 @@ impl<'db> TupleType<'db> {
 | 
				
			||||||
    pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
 | 
					    pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
 | 
				
			||||||
        self.tuple(db).is_single_valued(db)
 | 
					        self.tuple(db).is_single_valued(db)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn truthiness(self, db: &'db dyn Db) -> Truthiness {
 | 
				
			||||||
 | 
					        self.tuple(db).truthiness()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A tuple spec describes the contents of a tuple type, which might be fixed- or variable-length.
 | 
					/// A tuple spec describes the contents of a tuple type, which might be fixed- or variable-length.
 | 
				
			||||||
| 
						 | 
					@ -967,6 +979,17 @@ impl<T> Tuple<T> {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn truthiness(&self) -> Truthiness {
 | 
				
			||||||
 | 
					        match self.len().size_hint() {
 | 
				
			||||||
 | 
					            // The tuple type is AlwaysFalse if it contains only the empty tuple
 | 
				
			||||||
 | 
					            (_, Some(0)) => Truthiness::AlwaysFalse,
 | 
				
			||||||
 | 
					            // The tuple type is AlwaysTrue if its inhabitants must always have length >=1
 | 
				
			||||||
 | 
					            (minimum, _) if minimum > 0 => Truthiness::AlwaysTrue,
 | 
				
			||||||
 | 
					            // The tuple type is Ambiguous if its inhabitants could be of any length
 | 
				
			||||||
 | 
					            _ => Truthiness::Ambiguous,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn is_empty(&self) -> bool {
 | 
					    pub(crate) fn is_empty(&self) -> bool {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Tuple::Fixed(tuple) => tuple.is_empty(),
 | 
					            Tuple::Fixed(tuple) => tuple.is_empty(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue