Skip to content

Commit 6453a0b

Browse files
committed
wip
1 parent a3f28ba commit 6453a0b

File tree

16 files changed

+331
-407
lines changed

16 files changed

+331
-407
lines changed

crates/ty_python_semantic/src/semantic_model.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ impl<'db> Completion<'db> {
222222
// "struct" here as a more general "object." ---AG
223223
Type::NominalInstance(_)
224224
| Type::PropertyInstance(_)
225-
| Type::Tuple(_)
226225
| Type::BoundSuper(_) => CompletionKind::Struct,
227226
Type::IntLiteral(_)
228227
| Type::BooleanLiteral(_)

crates/ty_python_semantic/src/types.rs

Lines changed: 30 additions & 163 deletions
Large diffs are not rendered by default.

crates/ty_python_semantic/src/types/call/arguments.rs

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ruff_python_ast as ast;
66
use crate::Db;
77
use crate::types::KnownClass;
88
use crate::types::enums::enum_member_literals;
9-
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
9+
use crate::types::tuple::{Tuple, TupleLength, TupleType};
1010

1111
use super::Type;
1212

@@ -221,6 +221,34 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
221221
]);
222222
}
223223

224+
// If the class is a fixed-length tuple subtype, we expand it to its elements.
225+
if let Some(spec) = instance.class.tuple_spec(db) {
226+
return match &*spec {
227+
Tuple::Fixed(fixed_length_tuple) => {
228+
let expanded = fixed_length_tuple
229+
.all_elements()
230+
.map(|element| {
231+
if let Some(expanded) = expand_type(db, *element) {
232+
Either::Left(expanded.into_iter())
233+
} else {
234+
Either::Right(std::iter::once(*element))
235+
}
236+
})
237+
.multi_cartesian_product()
238+
.map(|types| TupleType::from_elements(db, types))
239+
.collect::<Vec<_>>();
240+
241+
if expanded.len() == 1 {
242+
// There are no elements in the tuple type that can be expanded.
243+
None
244+
} else {
245+
Some(expanded)
246+
}
247+
}
248+
Tuple::Variable(_) => None,
249+
};
250+
}
251+
224252
let class_literal = instance.class.class_literal(db).0;
225253

226254
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>>> {
229257

230258
None
231259
}
232-
Type::Tuple(tuple_type) => {
233-
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
234-
// should not be expanded here.
235-
let tuple = tuple_type.tuple(db);
236-
if !matches!(tuple, TupleSpec::Fixed(_)) {
237-
return None;
238-
}
239-
let expanded = tuple
240-
.all_elements()
241-
.map(|element| {
242-
if let Some(expanded) = expand_type(db, *element) {
243-
Either::Left(expanded.into_iter())
244-
} else {
245-
Either::Right(std::iter::once(*element))
246-
}
247-
})
248-
.multi_cartesian_product()
249-
.map(|types| TupleType::from_elements(db, types))
250-
.collect::<Vec<_>>();
251-
if expanded.len() == 1 {
252-
// There are no elements in the tuple type that can be expanded.
253-
None
254-
} else {
255-
Some(expanded)
256-
}
257-
}
258260
Type::Union(union) => Some(union.iter(db).copied().collect()),
259261
// We don't handle `type[A | B]` here because it's already stored in the expanded form
260262
// i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ impl<'db> Bindings<'db> {
10341034
);
10351035
continue;
10361036
};
1037-
overload.set_return_type(Type::tuple(TupleType::new(
1037+
overload.set_return_type(Type::tuple(db, TupleType::new(
10381038
db,
10391039
tuple_spec.as_ref(),
10401040
)));

crates/ty_python_semantic/src/types/class.rs

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::hash::BuildHasherDefault;
23
use std::sync::{LazyLock, Mutex};
34

@@ -22,9 +23,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
2223
use crate::types::tuple::{TupleSpec, TupleType};
2324
use crate::types::{
2425
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
25-
DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
26-
TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, declaration_type,
27-
infer_definition_types,
26+
DeprecatedInstance, DynamicType, KnownInstanceType, NominalInstanceType, TypeAliasType,
27+
TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance,
28+
TypeVarKind, declaration_type, infer_definition_types,
2829
};
2930
use crate::{
3031
Db, FxIndexMap, FxOrderSet, KnownModule, Program,
@@ -1013,6 +1014,59 @@ impl<'db> ClassType<'db> {
10131014
}
10141015
}
10151016
}
1017+
1018+
/// If this class is `tuple`, a specialization of `tuple` (`tuple[int, str]`, etc.),
1019+
/// *or a subclass of `tuple`, or a subclass of a specialization of `tuple`*,
1020+
/// return its tuple specification.
1021+
pub(super) fn tuple_spec(self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
1022+
self.iter_mro(db)
1023+
.filter_map(ClassBase::into_class)
1024+
.find_map(|class| class.own_tuple_spec(db))
1025+
}
1026+
1027+
/// If this class is `tuple` or a specialization of `tuple` (`tuple[int, str]`, etc.),
1028+
/// return its tuple specification.
1029+
///
1030+
/// You usually don't want to use this method directly, because you usually want to consider
1031+
/// any subclass of a certain tuple type in the same way as that tuple type itself.
1032+
/// Use [`ClassType::tuple_spec`] instead.
1033+
pub(super) fn own_tuple_spec(self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
1034+
let (class_literal, specialization) = self.class_literal(db);
1035+
match class_literal.known(db)? {
1036+
KnownClass::Tuple => Some(
1037+
specialization
1038+
.map(|spec| Cow::Borrowed(spec.tuple(db)))
1039+
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
1040+
),
1041+
KnownClass::VersionInfo => {
1042+
let python_version = Program::get(db).python_version(db);
1043+
let int_instance_ty = KnownClass::Int.to_instance(db);
1044+
1045+
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
1046+
let release_level_ty = {
1047+
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
1048+
.iter()
1049+
.map(|level| Type::string_literal(db, level))
1050+
.collect();
1051+
1052+
// For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
1053+
// those techniques ensure that union elements are deduplicated and unions are eagerly simplified
1054+
// into other types where necessary. Here, however, we know that there are no duplicates
1055+
// in this union, so it's probably more efficient to use `UnionType::new()` directly.
1056+
Type::Union(UnionType::new(db, elements))
1057+
};
1058+
1059+
Some(Cow::Owned(TupleSpec::from_elements([
1060+
Type::IntLiteral(python_version.major.into()),
1061+
Type::IntLiteral(python_version.minor.into()),
1062+
int_instance_ty,
1063+
release_level_ty,
1064+
int_instance_ty,
1065+
])))
1066+
}
1067+
_ => None,
1068+
}
1069+
}
10161070
}
10171071

10181072
impl<'db> From<GenericAlias<'db>> for ClassType<'db> {
@@ -4313,16 +4367,14 @@ impl SlotsKind {
43134367

43144368
match slots_ty {
43154369
// __slots__ = ("a", "b")
4316-
Type::Tuple(tuple) => {
4317-
let tuple = tuple.tuple(db);
4318-
if tuple.is_variadic() {
4319-
Self::Dynamic
4320-
} else if tuple.is_empty() {
4321-
Self::Empty
4322-
} else {
4323-
Self::NotEmpty
4324-
}
4325-
}
4370+
Type::NominalInstance(NominalInstanceType { class, .. }) => match class
4371+
.tuple_spec(db)
4372+
.and_then(|spec| spec.len().into_fixed_length())
4373+
{
4374+
Some(0) => Self::Empty,
4375+
Some(_) => Self::NotEmpty,
4376+
None => Self::Dynamic,
4377+
},
43264378

43274379
// __slots__ = "abc" # Same as `("abc",)`
43284380
Type::StringLiteral(_) => Self::NotEmpty,

crates/ty_python_semantic/src/types/class_base.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ impl<'db> ClassBase<'db> {
151151
| Type::EnumLiteral(_)
152152
| Type::StringLiteral(_)
153153
| Type::LiteralString
154-
| Type::Tuple(_)
155154
| Type::ModuleLiteral(_)
156155
| Type::TypeVar(_)
157156
| Type::BoundSuper(_)

crates/ty_python_semantic/src/types/display.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ impl Display for DisplayRepresentation<'_> {
7676
match (instance.class, instance.class.known(self.db)) {
7777
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
7878
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
79+
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
80+
.specialization(self.db)
81+
.tuple(self.db)
82+
.display(self.db)
83+
.fmt(f),
7984
(ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)),
8085
(ClassType::Generic(alias), _) => alias.display(self.db).fmt(f),
8186
}
@@ -201,7 +206,6 @@ impl Display for DisplayRepresentation<'_> {
201206
name = enum_literal.name(self.db),
202207
)
203208
}
204-
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
205209
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)),
206210
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
207211
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),

crates/ty_python_semantic/src/types/function.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,6 @@ fn is_instance_truthiness<'db>(
944944
.is_some_and(is_instance),
945945
),
946946

947-
Type::Tuple(..) => always_true_if(class.is_known(db, KnownClass::Tuple)),
948-
949947
Type::FunctionLiteral(..) => {
950948
always_true_if(is_instance(&KnownClass::FunctionType.to_instance(db)))
951949
}

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -758,22 +758,25 @@ impl<'db> SpecializationBuilder<'db> {
758758
}
759759
}
760760

761-
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
762-
let formal_tuple = formal_tuple.tuple(self.db);
763-
let actual_tuple = actual_tuple.tuple(self.db);
764-
let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else {
765-
return Ok(());
766-
};
767-
let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else {
768-
return Ok(());
769-
};
770-
let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else {
771-
return Ok(());
772-
};
773-
for (formal_element, actual_element) in
774-
formal_tuple.all_elements().zip(actual_tuple.all_elements())
775-
{
776-
self.infer(*formal_element, *actual_element)?;
761+
(
762+
Type::NominalInstance(NominalInstanceType { class: class1, .. }),
763+
Type::NominalInstance(NominalInstanceType { class: class2, .. })
764+
) if class1.tuple_spec(self.db).is_some() => {
765+
if let (Some(formal_tuple), Some(actual_tuple)) = (class1.tuple_spec(self.db), class2.tuple_spec(self.db)) {
766+
let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else {
767+
return Ok(());
768+
};
769+
let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else {
770+
return Ok(());
771+
};
772+
let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else {
773+
return Ok(());
774+
};
775+
for (formal_element, actual_element) in
776+
formal_tuple.all_elements().zip(actual_tuple.all_elements())
777+
{
778+
self.infer(*formal_element, *actual_element)?;
779+
}
777780
}
778781
}
779782

crates/ty_python_semantic/src/types/ide_support.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ impl<'db> AllMembers<'db> {
125125
| Type::BytesLiteral(_)
126126
| Type::EnumLiteral(_)
127127
| Type::LiteralString
128-
| Type::Tuple(_)
129128
| Type::PropertyInstance(_)
130129
| Type::FunctionLiteral(_)
131130
| Type::BoundMethod(_)

0 commit comments

Comments
 (0)