Compare commits

...

2 Commits

Author SHA1 Message Date
Jack O'Connor
36a8edaf4b lifetime insanity 2025-07-31 11:04:59 -07:00
Alex Waygood
6453a0ba48 wip 2025-07-31 16:25:05 +01:00
17 changed files with 347 additions and 415 deletions

View File

@@ -222,7 +222,6 @@ impl<'db> Completion<'db> {
// "struct" here as a more general "object." ---AG
Type::NominalInstance(_)
| Type::PropertyInstance(_)
| Type::Tuple(_)
| Type::BoundSuper(_) => CompletionKind::Struct,
Type::IntLiteral(_)
| Type::BooleanLiteral(_)

View File

@@ -592,11 +592,6 @@ pub enum Type<'db> {
LiteralString,
/// A bytes literal
BytesLiteral(BytesLiteralType<'db>),
/// An instance of the builtin `tuple` class.
/// TODO: Consider removing this in favor of `NominalInstance`. This is currently stored as a
/// separate variant partly for historical reasons, and partly to allow us to easily
/// distinguish tuples since they occur so often.
Tuple(TupleType<'db>),
/// An instance of a typevar in a generic class or function. When the generic class or function
/// is specialized, we will replace this typevar with its specialization.
TypeVar(TypeVarInstance<'db>),
@@ -681,6 +676,30 @@ impl<'db> Type<'db> {
self.materialize(db, TypeVarVariance::Contravariant)
}
/// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
///
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
/// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
fn tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
self.into_nominal_instance()
.and_then(|instance| instance.class.tuple_spec(db))
}
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
/// tuple spec.
///
/// You usually don't want to use this method, as you usually want to consider a subclass
/// of a tuple type in the same way as the `tuple` type itself. Only use this method if you
/// are certain that a *literal tuple* is required, and that a subclass of tuple will not
/// do.
///
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
/// But for a subclass of `tuple[int, str]`, it will return `None`.
fn exact_tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
self.into_nominal_instance()
.and_then(|instance| instance.class.own_tuple_spec(db))
}
/// Returns the materialization of this type depending on the given `variance`.
///
/// More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of
@@ -774,7 +793,6 @@ impl<'db> Type<'db> {
.map(|ty| ty.materialize(db, variance.flip())),
)
.build(),
Type::Tuple(tuple_type) => Type::tuple(tuple_type.materialize(db, variance)),
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
Type::TypeIs(type_is) => {
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
@@ -1023,9 +1041,6 @@ impl<'db> Type<'db> {
Type::Intersection(intersection) => visitor.visit(self, |v| {
Type::Intersection(intersection.normalized_impl(db, v))
}),
Type::Tuple(tuple) => {
visitor.visit(self, |v| Type::tuple(tuple.normalized_impl(db, v)))
}
Type::Callable(callable) => {
visitor.visit(self, |v| Type::Callable(callable.normalized_impl(db, v)))
}
@@ -1129,7 +1144,6 @@ impl<'db> Type<'db> {
| Type::Union(_)
| Type::Intersection(_)
| Type::Callable(_)
| Type::Tuple(_)
| Type::TypeVar(_)
| Type::BoundSuper(_)
| Type::TypeIs(_) => false,
@@ -1194,7 +1208,6 @@ impl<'db> Type<'db> {
| Type::StringLiteral(_)
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::Tuple(_)
| Type::TypeIs(_) => None,
// TODO
@@ -1528,24 +1541,6 @@ impl<'db> Type<'db> {
(Type::Callable(_), _) => false,
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
self_tuple.has_relation_to(db, target_tuple, relation)
}
(Type::Tuple(self_tuple), Type::NominalInstance(target_instance)) => {
self_tuple.to_class_type(db).is_some_and(|self_class| {
self_class.has_relation_to(db, target_instance.class, relation)
})
}
(Type::NominalInstance(self_instance), Type::Tuple(target_tuple)) => {
target_tuple.to_class_type(db).is_some_and(|target_class| {
self_instance
.class
.has_relation_to(db, target_class, relation)
})
}
(Type::Tuple(_), _) => false,
(Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target),
(Type::BoundSuper(_), _) => KnownClass::Super
.to_instance(db)
@@ -1675,8 +1670,6 @@ impl<'db> Type<'db> {
first.is_equivalent_to(db, second)
}
(Type::Tuple(first), Type::Tuple(second)) => first.is_equivalent_to(db, second),
(Type::Union(first), Type::Union(second)) => first.is_equivalent_to(db, second),
(Type::Intersection(first), Type::Intersection(second)) => {
@@ -1852,45 +1845,6 @@ impl<'db> Type<'db> {
| Type::KnownInstance(..)),
) => left != right,
// One tuple type can be a subtype of another tuple type,
// but we know for sure that any given tuple type is disjoint from all single-valued types
(
Type::Tuple(..),
Type::ClassLiteral(..)
| Type::GenericAlias(..)
| Type::ModuleLiteral(..)
| Type::BooleanLiteral(..)
| Type::BytesLiteral(..)
| Type::FunctionLiteral(..)
| Type::BoundMethod(..)
| Type::MethodWrapper(..)
| Type::WrapperDescriptor(..)
| Type::DataclassDecorator(..)
| Type::DataclassTransformer(..)
| Type::IntLiteral(..)
| Type::EnumLiteral(..)
| Type::StringLiteral(..)
| Type::LiteralString,
)
| (
Type::ClassLiteral(..)
| Type::GenericAlias(..)
| Type::ModuleLiteral(..)
| Type::BooleanLiteral(..)
| Type::BytesLiteral(..)
| Type::FunctionLiteral(..)
| Type::BoundMethod(..)
| Type::MethodWrapper(..)
| Type::WrapperDescriptor(..)
| Type::DataclassDecorator(..)
| Type::DataclassTransformer(..)
| Type::IntLiteral(..)
| Type::EnumLiteral(..)
| Type::StringLiteral(..)
| Type::LiteralString,
Type::Tuple(..),
) => true,
(
Type::SubclassOf(_),
Type::BooleanLiteral(..)
@@ -2061,16 +2015,6 @@ impl<'db> Type<'db> {
!known_instance.is_instance_of(db, instance.class)
}
(Type::SpecialForm(special_form), Type::Tuple(tuple))
| (Type::Tuple(tuple), Type::SpecialForm(special_form)) => tuple
.to_class_type(db)
.is_some_and(|tuple_class| !special_form.is_instance_of(db, tuple_class)),
(Type::KnownInstance(known_instance), Type::Tuple(tuple))
| (Type::Tuple(tuple), Type::KnownInstance(known_instance)) => tuple
.to_class_type(db)
.is_some_and(|tuple_class| !known_instance.is_instance_of(db, tuple_class)),
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
@@ -2219,17 +2163,6 @@ impl<'db> Type<'db> {
left.is_disjoint_from_impl(db, right)
}
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
tuple.is_disjoint_from_impl(db, other_tuple, visitor)
}
(Type::Tuple(tuple), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::Tuple(tuple)) => {
tuple.to_class_type(db).is_some_and(|tuple_class| {
!instance.class.could_coexist_in_mro_with(db, tuple_class)
})
}
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
KnownClass::Property
.to_instance(db)
@@ -2340,14 +2273,6 @@ impl<'db> Type<'db> {
Type::DataclassDecorator(_) | Type::DataclassTransformer(_) => false,
Type::NominalInstance(instance) => instance.is_singleton(db),
Type::PropertyInstance(_) => false,
Type::Tuple(..) => {
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
// should not be relied on for type narrowing, so we do not treat it as one.
// See:
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
false
}
Type::Union(..) => {
// A single-element union, where the sole element was a singleton, would itself
// be a singleton type. However, unions with length < 2 should never appear in
@@ -2434,7 +2359,6 @@ impl<'db> Type<'db> {
false
}
Type::Tuple(tuple) => tuple.is_single_valued(db),
Type::NominalInstance(instance) => instance.is_single_valued(db),
Type::BoundSuper(_) => {
@@ -2569,7 +2493,6 @@ impl<'db> Type<'db> {
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::EnumLiteral(_)
| Type::Tuple(_)
| Type::TypeVar(_)
| Type::NominalInstance(_)
| Type::ProtocolInstance(_)
@@ -2701,10 +2624,6 @@ impl<'db> Type<'db> {
Type::EnumLiteral(enum_literal) => enum_literal
.enum_class_instance(db)
.instance_member(db, name),
Type::Tuple(tuple) => tuple
.to_class_type(db)
.map(|class| class.instance_member(db, name))
.unwrap_or(Place::Unbound.into()),
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
Type::ModuleLiteral(_) => KnownClass::ModuleType
@@ -3212,7 +3131,6 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::LiteralString
| Type::Tuple(..)
| Type::TypeVar(..)
| Type::SpecialForm(..)
| Type::KnownInstance(..)
@@ -3569,7 +3487,6 @@ 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) => tuple.truthiness(db),
};
Ok(truthiness)
@@ -3596,13 +3513,6 @@ 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)),
// 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,
};
@@ -4541,7 +4451,6 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(_)
| Type::BooleanLiteral(_)
| Type::LiteralString
| Type::Tuple(_)
| Type::BoundSuper(_)
| Type::ModuleLiteral(_)
| Type::TypeIs(_) => CallableBinding::not_callable(self).into(),
@@ -4698,7 +4607,11 @@ impl<'db> Type<'db> {
}
match self {
Type::Tuple(tuple_type) => return Ok(Cow::Borrowed(tuple_type.tuple(db))),
Type::NominalInstance(NominalInstanceType { class, .. }) => {
if let Some(spec) = class.tuple_spec(db) {
return Ok(spec);
}
}
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
return Ok(Cow::Owned(TupleSpec::homogeneous(todo_type!(
"*tuple[] annotations"
@@ -5153,7 +5066,6 @@ impl<'db> Type<'db> {
| Type::ModuleLiteral(_)
| Type::IntLiteral(_)
| Type::StringLiteral(_)
| Type::Tuple(_)
| Type::LiteralString
| Type::BoundSuper(_)
| Type::AlwaysTruthy
@@ -5213,7 +5125,6 @@ impl<'db> Type<'db> {
| Type::LiteralString
| Type::ModuleLiteral(_)
| Type::StringLiteral(_)
| Type::Tuple(_)
| Type::TypeVar(_)
| Type::Callable(_)
| Type::BoundMethod(_)
@@ -5432,40 +5343,6 @@ impl<'db> Type<'db> {
KnownClass::NoneType.to_instance(db)
}
/// Return the type of `tuple(sys.version_info)`.
///
/// This is not exactly the type that `sys.version_info` has at runtime,
/// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons.
fn version_info_tuple(db: &'db dyn Db) -> Self {
let python_version = Program::get(db).python_version(db);
let int_instance_ty = KnownClass::Int.to_instance(db);
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
let release_level_ty = {
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
.iter()
.map(|level| Type::string_literal(db, level))
.collect();
// For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
// those techniques ensure that union elements are deduplicated and unions are eagerly simplified
// into other types where necessary. Here, however, we know that there are no duplicates
// in this union, so it's probably more efficient to use `UnionType::new()` directly.
Type::Union(UnionType::new(db, elements))
};
TupleType::from_elements(
db,
[
Type::IntLiteral(python_version.major.into()),
Type::IntLiteral(python_version.minor.into()),
int_instance_ty,
release_level_ty,
int_instance_ty,
],
)
}
/// Given a type that is assumed to represent an instance of a class,
/// return a type that represents that class itself.
#[must_use]
@@ -5491,10 +5368,6 @@ impl<'db> Type<'db> {
}
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
Type::Tuple(tuple) => tuple
.to_class_type(db)
.map(Type::from)
.unwrap_or_else(Type::unknown),
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
None => KnownClass::Type.to_instance(db),
@@ -5648,7 +5521,6 @@ impl<'db> Type<'db> {
}
builder.build()
}
Type::Tuple(tuple) => Type::tuple(tuple.apply_type_mapping(db, type_mapping)),
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),
@@ -5741,10 +5613,6 @@ impl<'db> Type<'db> {
}
}
Type::Tuple(tuple) => {
tuple.find_legacy_typevars(db, typevars);
}
Type::GenericAlias(alias) => {
alias.find_legacy_typevars(db, typevars);
}
@@ -5888,8 +5756,7 @@ impl<'db> Type<'db> {
| Self::DataclassDecorator(_)
| Self::DataclassTransformer(_)
| Self::PropertyInstance(_)
| Self::BoundSuper(_)
| Self::Tuple(_) => self.to_meta_type(db).definition(db),
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
Self::TypeVar(var) => Some(TypeDefinition::TypeVar(var.definition(db)?)),

View File

@@ -6,7 +6,7 @@ use ruff_python_ast as ast;
use crate::Db;
use crate::types::KnownClass;
use crate::types::enums::enum_member_literals;
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
use crate::types::tuple::{Tuple, TupleLength, TupleType};
use super::Type;
@@ -221,6 +221,34 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
]);
}
// If the class is a fixed-length tuple subtype, we expand it to its elements.
if let Some(spec) = instance.class.tuple_spec(db) {
return match &*spec {
Tuple::Fixed(fixed_length_tuple) => {
let expanded = fixed_length_tuple
.all_elements()
.map(|element| {
if let Some(expanded) = expand_type(db, *element) {
Either::Left(expanded.into_iter())
} else {
Either::Right(std::iter::once(*element))
}
})
.multi_cartesian_product()
.map(|types| TupleType::from_elements(db, types))
.collect::<Vec<_>>();
if expanded.len() == 1 {
// There are no elements in the tuple type that can be expanded.
None
} else {
Some(expanded)
}
}
Tuple::Variable(_) => None,
};
}
let class_literal = instance.class.class_literal(db).0;
if let Some(enum_members) = enum_member_literals(db, class_literal, None) {
@@ -229,32 +257,6 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
None
}
Type::Tuple(tuple_type) => {
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
// should not be expanded here.
let tuple = tuple_type.tuple(db);
if !matches!(tuple, TupleSpec::Fixed(_)) {
return None;
}
let expanded = tuple
.all_elements()
.map(|element| {
if let Some(expanded) = expand_type(db, *element) {
Either::Left(expanded.into_iter())
} else {
Either::Right(std::iter::once(*element))
}
})
.multi_cartesian_product()
.map(|types| TupleType::from_elements(db, types))
.collect::<Vec<_>>();
if expanded.len() == 1 {
// There are no elements in the tuple type that can be expanded.
None
} else {
Some(expanded)
}
}
Type::Union(union) => Some(union.iter(db).copied().collect()),
// We don't handle `type[A | B]` here because it's already stored in the expanded form
// i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.

View File

@@ -1034,7 +1034,7 @@ impl<'db> Bindings<'db> {
);
continue;
};
overload.set_return_type(Type::tuple(TupleType::new(
overload.set_return_type(Type::tuple(db, TupleType::new(
db,
tuple_spec.as_ref(),
)));

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::hash::BuildHasherDefault;
use std::sync::{LazyLock, Mutex};
@@ -22,9 +23,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, declaration_type,
infer_definition_types,
DeprecatedInstance, DynamicType, KnownInstanceType, NominalInstanceType, TypeAliasType,
TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarKind, declaration_type, infer_definition_types,
};
use crate::{
Db, FxIndexMap, FxOrderSet, KnownModule, Program,
@@ -1013,6 +1014,59 @@ impl<'db> ClassType<'db> {
}
}
}
/// If this class is `tuple`, a specialization of `tuple` (`tuple[int, str]`, etc.),
/// *or a subclass of `tuple`, or a subclass of a specialization of `tuple`*,
/// return its tuple specification.
pub(super) fn tuple_spec(self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
self.iter_mro(db)
.filter_map(ClassBase::into_class)
.find_map(|class| class.own_tuple_spec(db))
}
/// If this class is `tuple` or a specialization of `tuple` (`tuple[int, str]`, etc.),
/// return its tuple specification.
///
/// You usually don't want to use this method directly, because you usually want to consider
/// any subclass of a certain tuple type in the same way as that tuple type itself.
/// Use [`ClassType::tuple_spec`] instead.
pub(super) fn own_tuple_spec(self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
let (class_literal, specialization) = self.class_literal(db);
match class_literal.known(db)? {
KnownClass::Tuple => Some(
specialization
.map(|spec| Cow::Borrowed(spec.tuple(db)))
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
),
KnownClass::VersionInfo => {
let python_version = Program::get(db).python_version(db);
let int_instance_ty = KnownClass::Int.to_instance(db);
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
let release_level_ty = {
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
.iter()
.map(|level| Type::string_literal(db, level))
.collect();
// For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
// those techniques ensure that union elements are deduplicated and unions are eagerly simplified
// into other types where necessary. Here, however, we know that there are no duplicates
// in this union, so it's probably more efficient to use `UnionType::new()` directly.
Type::Union(UnionType::new(db, elements))
};
Some(Cow::Owned(TupleSpec::from_elements([
Type::IntLiteral(python_version.major.into()),
Type::IntLiteral(python_version.minor.into()),
int_instance_ty,
release_level_ty,
int_instance_ty,
])))
}
_ => None,
}
}
}
impl<'db> From<GenericAlias<'db>> for ClassType<'db> {
@@ -4313,16 +4367,14 @@ impl SlotsKind {
match slots_ty {
// __slots__ = ("a", "b")
Type::Tuple(tuple) => {
let tuple = tuple.tuple(db);
if tuple.is_variadic() {
Self::Dynamic
} else if tuple.is_empty() {
Self::Empty
} else {
Self::NotEmpty
}
}
Type::NominalInstance(NominalInstanceType { class, .. }) => match class
.tuple_spec(db)
.and_then(|spec| spec.len().into_fixed_length())
{
Some(0) => Self::Empty,
Some(_) => Self::NotEmpty,
None => Self::Dynamic,
},
// __slots__ = "abc" # Same as `("abc",)`
Type::StringLiteral(_) => Self::NotEmpty,

View File

@@ -151,7 +151,6 @@ impl<'db> ClassBase<'db> {
| Type::EnumLiteral(_)
| Type::StringLiteral(_)
| Type::LiteralString
| Type::Tuple(_)
| Type::ModuleLiteral(_)
| Type::TypeVar(_)
| Type::BoundSuper(_)

View File

@@ -76,6 +76,11 @@ impl Display for DisplayRepresentation<'_> {
match (instance.class, instance.class.known(self.db)) {
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
.specialization(self.db)
.tuple(self.db)
.display(self.db)
.fmt(f),
(ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)),
(ClassType::Generic(alias), _) => alias.display(self.db).fmt(f),
}
@@ -201,7 +206,6 @@ impl Display for DisplayRepresentation<'_> {
name = enum_literal.name(self.db),
)
}
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)),
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),

View File

@@ -944,8 +944,6 @@ fn is_instance_truthiness<'db>(
.is_some_and(is_instance),
),
Type::Tuple(..) => always_true_if(class.is_known(db, KnownClass::Tuple)),
Type::FunctionLiteral(..) => {
always_true_if(is_instance(&KnownClass::FunctionType.to_instance(db)))
}

View File

@@ -758,22 +758,25 @@ impl<'db> SpecializationBuilder<'db> {
}
}
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
let formal_tuple = formal_tuple.tuple(self.db);
let actual_tuple = actual_tuple.tuple(self.db);
let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else {
return Ok(());
};
let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else {
return Ok(());
};
let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else {
return Ok(());
};
for (formal_element, actual_element) in
formal_tuple.all_elements().zip(actual_tuple.all_elements())
{
self.infer(*formal_element, *actual_element)?;
(
Type::NominalInstance(NominalInstanceType { class: class1, .. }),
Type::NominalInstance(NominalInstanceType { class: class2, .. })
) if class1.tuple_spec(self.db).is_some() => {
if let (Some(formal_tuple), Some(actual_tuple)) = (class1.tuple_spec(self.db), class2.tuple_spec(self.db)) {
let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else {
return Ok(());
};
let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else {
return Ok(());
};
let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else {
return Ok(());
};
for (formal_element, actual_element) in
formal_tuple.all_elements().zip(actual_tuple.all_elements())
{
self.infer(*formal_element, *actual_element)?;
}
}
}

View File

@@ -125,7 +125,6 @@ impl<'db> AllMembers<'db> {
| Type::BytesLiteral(_)
| Type::EnumLiteral(_)
| Type::LiteralString
| Type::Tuple(_)
| Type::PropertyInstance(_)
| Type::FunctionLiteral(_)
| Type::BoundMethod(_)

View File

@@ -3240,9 +3240,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// If it's an `except*` handler, this won't actually be the type of the bound symbol;
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
let symbol_ty = if let Some(tuple_spec) = node_ty.tuple_instance_spec(self.db()) {
let mut builder = UnionBuilder::new(self.db());
for element in tuple.tuple(self.db()).all_elements().copied() {
for element in tuple_spec.all_elements().copied() {
builder = builder.add(
if element.is_assignable_to(self.db(), type_base_exception) {
element.to_instance(self.db()).expect(
@@ -3748,7 +3748,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BytesLiteral(..)
| Type::EnumLiteral(..)
| Type::LiteralString
| Type::Tuple(..)
| Type::SpecialForm(..)
| Type::KnownInstance(..)
| Type::PropertyInstance(..)
@@ -4158,15 +4157,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ast::Expr::Name(name) => self.infer_definition(name),
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
let mut assigned_tys = match assigned_ty {
Some(Type::Tuple(tuple)) => {
Either::Left(tuple.tuple(self.db()).all_elements().copied())
if let Some(tuple_spec) =
assigned_ty.and_then(|ty| ty.tuple_instance_spec(self.db()))
{
let mut assigned_tys = tuple_spec.all_elements();
for element in elts {
self.infer_target_impl(element, assigned_tys.next().copied());
}
} else {
for element in elts {
self.infer_target_impl(element, None);
}
Some(_) | None => Either::Right(std::iter::empty()),
};
for element in elts {
self.infer_target_impl(element, assigned_tys.next());
}
}
ast::Expr::Attribute(
@@ -6942,7 +6943,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::EnumLiteral(_)
| Type::Tuple(_)
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_),
@@ -7267,7 +7267,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::EnumLiteral(_)
| Type::Tuple(_)
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_),
@@ -7296,7 +7295,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::EnumLiteral(_)
| Type::Tuple(_)
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_),
@@ -7904,96 +7902,77 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Ok(Type::BooleanLiteral(literal_1 != literal_2))
}
(Type::Tuple(_), Type::NominalInstance(instance))
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
left,
op,
Type::version_info_tuple(self.db()),
range,
)
}
(Type::NominalInstance(instance), Type::Tuple(_))
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
Type::version_info_tuple(self.db()),
op,
right,
range,
)
}
(Type::Tuple(lhs), Type::Tuple(rhs)) => {
let lhs_tuple = lhs.tuple(self.db());
let rhs_tuple = rhs.tuple(self.db());
_ => {
// Special-casing for comparisons between tuple instance types
if let (Some(lhs_tuple), Some(rhs_tuple)) = (
left.tuple_instance_spec(self.db()),
right.tuple_instance_spec(self.db()),
) {
let mut tuple_rich_comparison =
|op| self.infer_tuple_rich_comparison(&lhs_tuple, op, &rhs_tuple, range);
let mut tuple_rich_comparison =
|op| self.infer_tuple_rich_comparison(lhs_tuple, op, rhs_tuple, range);
return match op {
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
ast::CmpOp::NotEq => tuple_rich_comparison(RichCompareOperator::Ne),
ast::CmpOp::Lt => tuple_rich_comparison(RichCompareOperator::Lt),
ast::CmpOp::LtE => tuple_rich_comparison(RichCompareOperator::Le),
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
ast::CmpOp::In | ast::CmpOp::NotIn => {
let mut any_eq = false;
let mut any_ambiguous = false;
match op {
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
ast::CmpOp::NotEq => tuple_rich_comparison(RichCompareOperator::Ne),
ast::CmpOp::Lt => tuple_rich_comparison(RichCompareOperator::Lt),
ast::CmpOp::LtE => tuple_rich_comparison(RichCompareOperator::Le),
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
ast::CmpOp::In | ast::CmpOp::NotIn => {
let mut any_eq = false;
let mut any_ambiguous = false;
for ty in rhs_tuple.all_elements().copied() {
let eq_result = self.infer_binary_type_comparison(
Type::Tuple(lhs),
for ty in rhs_tuple.all_elements().copied() {
let eq_result = self.infer_binary_type_comparison(
left,
ast::CmpOp::Eq,
ty,
range,
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
match eq_result {
todo @ Type::Dynamic(DynamicType::Todo(_)) => return Ok(todo),
// It's okay to ignore errors here because Python doesn't call `__bool__`
// for different union variants. Instead, this is just for us to
// evaluate a possibly truthy value to `false` or `true`.
ty => match ty.bool(self.db()) {
Truthiness::AlwaysTrue => any_eq = true,
Truthiness::AlwaysFalse => (),
Truthiness::Ambiguous => any_ambiguous = true,
},
match eq_result {
todo @ Type::Dynamic(DynamicType::Todo(_)) => return Ok(todo),
// It's okay to ignore errors here because Python doesn't call `__bool__`
// for different union variants. Instead, this is just for us to
// evaluate a possibly truthy value to `false` or `true`.
ty => match ty.bool(self.db()) {
Truthiness::AlwaysTrue => any_eq = true,
Truthiness::AlwaysFalse => (),
Truthiness::Ambiguous => any_ambiguous = true,
},
}
}
if any_eq {
Ok(Type::BooleanLiteral(op.is_in()))
} else if !any_ambiguous {
Ok(Type::BooleanLiteral(op.is_not_in()))
} else {
Ok(KnownClass::Bool.to_instance(self.db()))
}
}
if any_eq {
Ok(Type::BooleanLiteral(op.is_in()))
} else if !any_ambiguous {
Ok(Type::BooleanLiteral(op.is_not_in()))
} else {
Ok(KnownClass::Bool.to_instance(self.db()))
}
}
ast::CmpOp::Is | ast::CmpOp::IsNot => {
// - `[ast::CmpOp::Is]`: returns `false` if the elements are definitely unequal, otherwise `bool`
// - `[ast::CmpOp::IsNot]`: returns `true` if the elements are definitely unequal, otherwise `bool`
let eq_result = tuple_rich_comparison(RichCompareOperator::Eq).expect(
ast::CmpOp::Is | ast::CmpOp::IsNot => {
// - `[ast::CmpOp::Is]`: returns `false` if the elements are definitely unequal, otherwise `bool`
// - `[ast::CmpOp::IsNot]`: returns `true` if the elements are definitely unequal, otherwise `bool`
let eq_result = tuple_rich_comparison(RichCompareOperator::Eq).expect(
"infer_binary_type_comparison should never return None for `CmpOp::Eq`",
);
Ok(match eq_result {
todo @ Type::Dynamic(DynamicType::Todo(_)) => todo,
// It's okay to ignore errors here because Python doesn't call `__bool__`
// for `is` and `is not` comparisons. This is an implementation detail
// for how we determine the truthiness of a type.
ty => match ty.bool(self.db()) {
Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()),
_ => KnownClass::Bool.to_instance(self.db()),
},
})
}
Ok(match eq_result {
todo @ Type::Dynamic(DynamicType::Todo(_)) => todo,
// It's okay to ignore errors here because Python doesn't call `__bool__`
// for `is` and `is not` comparisons. This is an implementation detail
// for how we determine the truthiness of a type.
ty => match ty.bool(self.db()) {
Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()),
_ => KnownClass::Bool.to_instance(self.db()),
},
})
}
};
}
}
// Lookup the rich comparison `__dunder__` methods
_ => {
// Final generalized fallback: lookup the rich comparison `__dunder__` methods
let rich_comparison = |op| self.infer_rich_comparison(left, right, op);
let membership_test_comparison = |op, range: TextRange| {
self.infer_membership_test_comparison(left, right, op, range)
@@ -8369,17 +8348,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let context = &self.context;
let inferred = match (value_ty, slice_ty) {
(Type::NominalInstance(instance), _)
if instance.class.is_known(db, KnownClass::VersionInfo) =>
{
Some(self.infer_subscript_expression_types(
value_node,
Type::version_info_tuple(db),
slice_ty,
expr_context,
))
}
(Type::Union(union), _) => Some(union.map(db, |element| {
self.infer_subscript_expression_types(value_node, *element, slice_ty, expr_context)
})),
@@ -8393,9 +8361,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
(Type::Tuple(tuple_ty), Type::IntLiteral(i64_int)) => {
i32::try_from(i64_int).ok().map(|i32_int| {
let tuple = tuple_ty.tuple(db);
(
Type::NominalInstance(NominalInstanceType { class, .. }),
Type::IntLiteral(i64_int),
) => class
.tuple_spec(db)
.and_then(|tuple| Some((tuple, i32::try_from(i64_int).ok()?)))
.map(|(tuple, i32_int)| {
tuple.py_index(db, i32_int).unwrap_or_else(|_| {
report_index_out_of_bounds(
context,
@@ -8407,26 +8379,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
Type::unknown()
})
})
}
}),
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
(Type::Tuple(tuple_ty), Type::NominalInstance(NominalInstanceType { class, .. })) => {
class
.slice_literal(db)
.map(|SliceLiteral { start, stop, step }| {
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(db) else {
return todo_type!("slice into variable-length tuple");
};
(
Type::NominalInstance(NominalInstanceType {
class: maybe_tuple, ..
}),
Type::NominalInstance(NominalInstanceType {
class: maybe_slice, ..
}),
) => maybe_tuple
.tuple_spec(db)
.as_deref()
.and_then(|tuple_spec| Some((tuple_spec, maybe_slice.slice_literal(db)?)))
.map(|(tuple, SliceLiteral { start, stop, step })| match tuple {
TupleSpec::Fixed(tuple) => {
if let Ok(new_elements) = tuple.py_slice(db, start, stop, step) {
TupleType::from_elements(db, new_elements.copied())
} else {
report_slice_step_size_zero(context, value_node.into());
Type::unknown()
}
})
}
}
TupleSpec::Variable(_) => {
todo_type!("slice into variable-length tuple")
}
}),
// Ex) Given `"value"[1]`, return `"a"`
(Type::StringLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
@@ -8508,18 +8487,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}),
// Ex) Given `"value"[True]`, return `"a"`
(Type::StringLiteral(_) | Type::BytesLiteral(_), Type::BooleanLiteral(bool)) => {
Some(self.infer_subscript_expression_types(
value_node,
value_ty,
Type::IntLiteral(i64::from(bool)),
expr_context,
))
}
(
Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_),
Type::NominalInstance(NominalInstanceType { class, .. }),
Type::BooleanLiteral(bool),
) => Some(self.infer_subscript_expression_types(
) if class.tuple_spec(db).is_some() => Some(self.infer_subscript_expression_types(
value_node,
value_ty,
Type::IntLiteral(i64::from(bool)),
expr_context,
)),
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars)) => {
Some(match typevars.tuple(db) {
(
Type::SpecialForm(SpecialFormType::Protocol),
Type::NominalInstance(NominalInstanceType { class, .. }),
) => {
class.tuple_spec(db).map(|typevars| match &*typevars {
TupleSpec::Fixed(typevars) => self
.legacy_generic_class_context(
value_node,
@@ -8550,8 +8541,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(todo_type!("doubly-specialized typing.Protocol"))
}
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars)) => {
Some(match typevars.tuple(db) {
(
Type::SpecialForm(SpecialFormType::Generic),
Type::NominalInstance(NominalInstanceType { class, .. }),
) => {
class.tuple_spec(db).map(|typevars| match &*typevars {
TupleSpec::Fixed(typevars) => self
.legacy_generic_class_context(
value_node,
@@ -9654,7 +9648,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
} = starred;
let starred_type = self.infer_type_expression(value);
if let Type::Tuple(_) = starred_type {
if starred_type.exact_tuple_instance_spec(self.db()).is_some() {
starred_type
} else {
todo_type!("PEP 646")
@@ -9712,7 +9706,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
match element {
ast::Expr::Starred(_) => !matches!(element_ty, Type::Tuple(_)),
ast::Expr::Starred(_) => !element_ty.exact_tuple_instance_spec(builder.db()).is_some(),
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
let value_ty = if builder.deferred_state.in_string_annotation() {
// Using `.expression_type` does not work in string annotations, because
@@ -9749,10 +9743,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let element_ty = self.infer_type_expression(element);
return_todo |=
element_could_alter_type_of_whole_tuple(element, element_ty, self);
if let ast::Expr::Starred(_) = element {
if let Type::Tuple(inner_tuple) = element_ty {
element_types =
element_types.concat(self.db(), inner_tuple.tuple(self.db()));
if let Some(inner_tuple) = element_ty.exact_tuple_instance_spec(self.db()) {
element_types = element_types.concat(self.db(), &inner_tuple);
} else {
// TODO: emit a diagnostic
}
@@ -9764,7 +9758,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let ty = if return_todo {
todo_type!("PEP 646")
} else {
Type::tuple(TupleType::new(self.db(), element_types))
Type::tuple(self.db(), TupleType::new(self.db(), element_types))
};
// Here, we store the type for the inner `int, str` tuple-expression,

View File

@@ -7,9 +7,12 @@ use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
use crate::place::PlaceAndQualifiers;
use crate::types::cyclic::PairVisitor;
use crate::types::enums::is_single_member_enum;
use crate::types::generics::{GenericContext, Specialization};
use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::TupleType;
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance};
use crate::types::{
DynamicType, GenericAlias, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance,
};
use crate::{Db, FxOrderSet};
pub(super) use synthesized_protocol::SynthesizedProtocolType;
@@ -18,12 +21,6 @@ impl<'db> Type<'db> {
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
match (class, class.known(db)) {
(_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any),
(ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => {
TupleType::homogeneous(db, Type::unknown())
}
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db)))
}
_ if class.class_literal(db).0.is_protocol(db) => {
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
}
@@ -31,6 +28,26 @@ impl<'db> Type<'db> {
}
}
pub(crate) fn tuple(db: &'db dyn Db, tuple: Option<TupleType<'db>>) -> Self {
let Some(tuple) = tuple else {
return Type::Never;
};
let Some(tuple_class) = KnownClass::Tuple.try_to_class_literal(db) else {
return Type::unknown();
};
let specialization = Specialization::new(
db,
GenericContext::new(db, FxOrderSet::default()),
Box::default(),
Some(tuple),
);
let alias = GenericAlias::new(db, tuple_class, specialization);
Type::NominalInstance(NominalInstanceType {
class: ClassType::Generic(alias),
_phantom: PhantomData,
})
}
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
match self {
Type::NominalInstance(instance_type) => Some(instance_type),

View File

@@ -11,8 +11,9 @@ use crate::types::enums::{enum_member_literals, enum_metadata};
use crate::types::function::KnownFunction;
use crate::types::infer::infer_same_file_expression_type;
use crate::types::{
ClassLiteral, ClassType, IntersectionBuilder, KnownClass, SubclassOfInner, SubclassOfType,
Truthiness, Type, TypeVarBoundOrConstraints, UnionBuilder, infer_expression_types,
ClassLiteral, ClassType, IntersectionBuilder, KnownClass, NominalInstanceType, SubclassOfInner,
SubclassOfType, Truthiness, Type, TypeVarBoundOrConstraints, UnionBuilder,
infer_expression_types,
};
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
@@ -182,14 +183,6 @@ impl ClassInfoConstraintFunction {
};
match classinfo {
Type::Tuple(tuple) => UnionType::try_from_elements(
db,
tuple
.tuple(db)
.all_elements()
.copied()
.map(|element| self.generate_constraint(db, element)),
),
Type::ClassLiteral(class_literal) => {
// At runtime (on Python 3.11+), this will return `True` for classes that actually
// do inherit `typing.Any` and `False` otherwise. We could accurately model that?
@@ -233,6 +226,18 @@ impl ClassInfoConstraintFunction {
// e.g. `isinstance(x, list[int])` fails at runtime.
Type::GenericAlias(_) => None,
Type::NominalInstance(NominalInstanceType { class, .. }) => {
class.tuple_spec(db).and_then(|tuple| {
UnionType::try_from_elements(
db,
tuple
.all_elements()
.copied()
.map(|element| self.generate_constraint(db, element)),
)
})
}
Type::AlwaysFalsy
| Type::AlwaysTruthy
| Type::BooleanLiteral(_)
@@ -249,7 +254,6 @@ impl ClassInfoConstraintFunction {
| Type::ProtocolInstance(_)
| Type::PropertyInstance(_)
| Type::SpecialForm(_)
| Type::NominalInstance(_)
| Type::LiteralString
| Type::StringLiteral(_)
| Type::IntLiteral(_)
@@ -616,20 +620,21 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
fn evaluate_expr_in(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
if lhs_ty.is_single_valued(self.db) || lhs_ty.is_union_of_single_valued(self.db) {
match rhs_ty {
Type::Tuple(rhs_tuple) => Some(UnionType::from_elements(
self.db,
rhs_tuple.tuple(self.db).all_elements(),
)),
Type::StringLiteral(string_literal) => Some(UnionType::from_elements(
if let Type::StringLiteral(string_literal) = rhs_ty {
Some(UnionType::from_elements(
self.db,
string_literal
.iter_each_char(self.db)
.map(Type::StringLiteral),
)),
_ => None,
))
} else if let Some(tuple_spec) = rhs_ty.tuple_instance_spec(self.db) {
// N.B. Strictly speaking this is unsound, since a tuple subclass might override `__contains__`
// but we'd still apply the narrowing here. This seems unlikely, however, and narrowing is
// generally unsound in numerous ways anyway (attribute narrowing, subscript, narrowing,
// narrowing of globals, etc.). So this doesn't seem worth worrying about too much.
Some(UnionType::from_elements(self.db, tuple_spec.all_elements()))
} else {
None
}
} else {
None

View File

@@ -144,15 +144,6 @@ pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>
// The Salsa heap is tracked separately.
impl get_size2::GetSize for TupleType<'_> {}
impl<'db> Type<'db> {
pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self {
let Some(tuple) = tuple else {
return Type::Never;
};
Self::Tuple(tuple)
}
}
impl<'db> TupleType<'db> {
pub(crate) fn new<T>(db: &'db dyn Db, tuple_key: T) -> Option<Self>
where
@@ -181,17 +172,17 @@ impl<'db> TupleType<'db> {
}
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
Type::tuple(TupleType::new(
Type::tuple(
db,
TupleSpec::from(FixedLengthTuple::empty()),
))
TupleType::new(db, TupleSpec::from(FixedLengthTuple::empty())),
)
}
pub(crate) fn from_elements(
db: &'db dyn Db,
types: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
Type::tuple(TupleType::new(db, TupleSpec::from_elements(types)))
Type::tuple(db, TupleType::new(db, TupleSpec::from_elements(types)))
}
#[cfg(test)]
@@ -201,14 +192,14 @@ impl<'db> TupleType<'db> {
variable: Type<'db>,
suffix: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
Type::tuple(TupleType::new(
Type::tuple(
db,
VariableLengthTuple::mixed(prefix, variable, suffix),
))
TupleType::new(db, VariableLengthTuple::mixed(prefix, variable, suffix)),
)
}
pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Type<'db> {
Type::tuple(TupleType::new(db, TupleSpec::homogeneous(element)))
Type::tuple(db, TupleType::new(db, TupleSpec::homogeneous(element)))
}
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
@@ -499,13 +490,16 @@ impl<'db> PyIndex<'db> for &FixedLengthTuple<Type<'db>> {
impl<'db> PySlice<'db> for FixedLengthTuple<Type<'db>> {
type Item = Type<'db>;
fn py_slice(
&'db self,
fn py_slice<'self_>(
&'self_ self,
db: &'db dyn Db,
start: Option<i32>,
stop: Option<i32>,
step: Option<i32>,
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
) -> Result<impl Iterator<Item = &'self_ Self::Item>, StepSizeZeroError>
where
'db: 'self_,
{
self.0.py_slice(db, start, stop, step)
}
}
@@ -970,10 +964,9 @@ impl<T> Tuple<T> {
VariableLengthTuple::homogeneous(element)
}
pub(crate) fn from_elements(elements: impl IntoIterator<Item = T>) -> Self {
pub(crate) fn from_elements(elements: impl IntoIterator<Item = T>) -> Tuple<T> {
FixedLengthTuple::from_elements(elements).into()
}
pub(crate) fn with_capacity(capacity: usize) -> Self {
Tuple::Fixed(FixedLengthTuple::with_capacity(capacity))
}

View File

@@ -100,10 +100,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::Callable(_), _) => Ordering::Less,
(_, Type::Callable(_)) => Ordering::Greater,
(Type::Tuple(left), Type::Tuple(right)) => left.cmp(right),
(Type::Tuple(_), _) => Ordering::Less,
(_, Type::Tuple(_)) => Ordering::Greater,
(Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => left.cmp(right),
(Type::ModuleLiteral(_), _) => Ordering::Less,
(_, Type::ModuleLiteral(_)) => Ordering::Greater,

View File

@@ -163,7 +163,6 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
TypeKind::NonAtomic(NonAtomicType::Intersection(intersection))
}
Type::Union(union) => TypeKind::NonAtomic(NonAtomicType::Union(union)),
Type::Tuple(tuple) => TypeKind::NonAtomic(NonAtomicType::Tuple(tuple)),
Type::BoundMethod(method) => TypeKind::NonAtomic(NonAtomicType::BoundMethod(method)),
Type::BoundSuper(bound_super) => {
TypeKind::NonAtomic(NonAtomicType::BoundSuper(bound_super))

View File

@@ -110,25 +110,30 @@ pub(crate) struct StepSizeZeroError;
pub(crate) trait PySlice<'db> {
type Item: 'db;
fn py_slice(
&'db self,
fn py_slice<'self_>(
&'self_ self,
db: &'db dyn Db,
start: Option<i32>,
stop: Option<i32>,
step: Option<i32>,
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError>;
) -> Result<impl Iterator<Item = &'self_ Self::Item>, StepSizeZeroError>
where
'db: 'self_;
}
impl<'db, T: 'db> PySlice<'db> for [T] {
type Item = T;
fn py_slice(
&'db self,
fn py_slice<'self_>(
&'self_ self,
_db: &'db dyn Db,
start: Option<i32>,
stop: Option<i32>,
step_int: Option<i32>,
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
) -> Result<impl Iterator<Item = &'self_ Self::Item>, StepSizeZeroError>
where
'db: 'self_,
{
let step_int = step_int.unwrap_or(1);
if step_int == 0 {
return Err(StepSizeZeroError);