Compare commits
2 Commits
charlie/di
...
jack+alex/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36a8edaf4b | ||
|
|
6453a0ba48 |
@@ -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(_)
|
||||
|
||||
@@ -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)?)),
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
)));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -151,7 +151,6 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ impl<'db> AllMembers<'db> {
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::BoundMethod(_)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user