mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-26 18:06:43 +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
|
|
@ -56,7 +56,7 @@ use crate::types::infer::infer_unpack_types;
|
|||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
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;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||
|
|
@ -3508,14 +3508,7 @@ impl<'db> Type<'db> {
|
|||
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||
Type::Tuple(tuple) => match tuple.tuple(db).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,
|
||||
},
|
||||
Type::Tuple(tuple) => tuple.truthiness(db),
|
||||
};
|
||||
|
||||
Ok(truthiness)
|
||||
|
|
@ -3542,10 +3535,12 @@ impl<'db> Type<'db> {
|
|||
let usize_len = match self {
|
||||
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
|
||||
Type::StringLiteral(string) => Some(string.python_len(db)),
|
||||
Type::Tuple(tuple) => match tuple.tuple(db) {
|
||||
TupleSpec::Fixed(tuple) => Some(tuple.len()),
|
||||
TupleSpec::Variable(_) => None,
|
||||
},
|
||||
|
||||
// N.B. This is strictly-speaking redundant, since the `__len__` method on tuples
|
||||
// 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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -574,9 +574,39 @@ impl<'db> ClassType<'db> {
|
|||
/// traverse through the MRO until it finds the member.
|
||||
pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
let (class_literal, specialization) = self.class_literal(db);
|
||||
class_literal
|
||||
.own_class_member(db, specialization, name)
|
||||
.map_type(|ty| ty.apply_optional_specialization(db, specialization))
|
||||
|
||||
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)
|
||||
.map_type(|ty| ty.apply_optional_specialization(db, specialization)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 crate::types::Truthiness;
|
||||
use crate::types::class::{ClassType, KnownClass};
|
||||
use crate::types::{
|
||||
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
|
||||
|
|
@ -76,6 +77,13 @@ impl TupleLength {
|
|||
None => "unlimited".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_fixed_length(self) -> Option<usize> {
|
||||
match self {
|
||||
TupleLength::Fixed(len) => Some(len),
|
||||
TupleLength::Variable(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Ordering
|
||||
|
|
@ -240,6 +248,10 @@ impl<'db> TupleType<'db> {
|
|||
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
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.
|
||||
|
|
@ -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 {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => tuple.is_empty(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue