Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use infer::nearest_enclosing_class;
use itertools::{Either, Itertools};
use ruff_db::parsed::parsed_module;

use std::borrow::Cow;
use std::slice::Iter;

use bitflags::bitflags;
Expand Down Expand Up @@ -689,7 +688,7 @@ impl<'db> Type<'db> {
///
/// 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>>> {
fn tuple_instance_spec(&self, db: &'db dyn Db) -> Option<TupleSpec<'db>> {
self.into_nominal_instance()
.and_then(|instance| instance.tuple_spec(db))
}
Expand All @@ -704,9 +703,9 @@ impl<'db> Type<'db> {
///
/// 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>>> {
fn exact_tuple_instance_spec(&self) -> Option<TupleSpec<'db>> {
self.into_nominal_instance()
.and_then(|instance| instance.own_tuple_spec(db))
.and_then(|instance| instance.own_tuple_spec())
}

/// Returns the materialization of this type depending on the given `variance`.
Expand Down Expand Up @@ -4737,9 +4736,9 @@ impl<'db> Type<'db> {
///
/// This method should only be used outside of type checking because it omits any errors.
/// For type checking, use [`try_iterate`](Self::try_iterate) instead.
fn iterate(self, db: &'db dyn Db) -> Cow<'db, TupleSpec<'db>> {
fn iterate(self, db: &'db dyn Db) -> TupleSpec<'db> {
self.try_iterate(db)
.unwrap_or_else(|err| Cow::Owned(TupleSpec::homogeneous(err.fallback_element_type(db))))
.unwrap_or_else(|err| TupleSpec::homogeneous(db, err.fallback_element_type(db)))
}

/// Given the type of an object that is iterated over in some way,
Expand All @@ -4750,15 +4749,15 @@ impl<'db> Type<'db> {
/// ```python
/// y(*x)
/// ```
fn try_iterate(self, db: &'db dyn Db) -> Result<Cow<'db, TupleSpec<'db>>, IterationError<'db>> {
fn try_iterate(self, db: &'db dyn Db) -> Result<TupleSpec<'db>, IterationError<'db>> {
self.try_iterate_with_mode(db, EvaluationMode::Sync)
}

fn try_iterate_with_mode(
self,
db: &'db dyn Db,
mode: EvaluationMode,
) -> Result<Cow<'db, TupleSpec<'db>>, IterationError<'db>> {
) -> Result<TupleSpec<'db>, IterationError<'db>> {
if mode.is_async() {
let try_call_dunder_anext_on_iterator = |iterator: Type<'db>| {
iterator
Expand All @@ -4772,7 +4771,7 @@ impl<'db> Type<'db> {
Ok(dunder_aiter_bindings) => {
let iterator = dunder_aiter_bindings.return_type(db);
match try_call_dunder_anext_on_iterator(iterator) {
Ok(result) => Ok(Cow::Owned(TupleSpec::homogeneous(result))),
Ok(result) => Ok(TupleSpec::homogeneous(db, result)),
Err(dunder_anext_error) => {
Err(IterationError::IterReturnsInvalidIterator {
iterator,
Expand Down Expand Up @@ -4817,27 +4816,28 @@ impl<'db> Type<'db> {
}
}
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
return Ok(Cow::Owned(TupleSpec::homogeneous(todo_type!(
"*tuple[] annotations"
))));
return Ok(TupleSpec::homogeneous(
db,
todo_type!("*tuple[] annotations"),
));
}
Type::StringLiteral(string_literal_ty) => {
// We could go further and deconstruct to an array of `StringLiteral`
// with each individual character, instead of just an array of
// `LiteralString`, but there would be a cost and it's not clear that
// it's worth it.
return Ok(Cow::Owned(TupleSpec::from_elements(std::iter::repeat_n(
Type::LiteralString,
string_literal_ty.python_len(db),
))));
return Ok(TupleSpec::heterogeneous(
db,
std::iter::repeat_n(Type::LiteralString, string_literal_ty.python_len(db)),
));
}
Type::Never => {
// The dunder logic below would have us return `tuple[Never, ...]`, which eagerly
// simplifies to `tuple[()]`. That will will cause us to emit false positives if we
// index into the tuple. Using `tuple[Unknown, ...]` avoids these false positives.
// TODO: Consider removing this special case, and instead hide the indexing
// diagnostic in unreachable code.
return Ok(Cow::Owned(TupleSpec::homogeneous(Type::unknown())));
return Ok(TupleSpec::homogeneous(db, Type::unknown()));
}
_ => {}
}
Expand Down Expand Up @@ -4866,7 +4866,7 @@ impl<'db> Type<'db> {
// `__iter__` is definitely bound and calling it succeeds.
// See what calling `__next__` on the object returned by `__iter__` gives us...
try_call_dunder_next_on_iterator(iterator)
.map(|ty| Cow::Owned(TupleSpec::homogeneous(ty)))
.map(|ty| TupleSpec::homogeneous(db, ty))
.map_err(
|dunder_next_error| IterationError::IterReturnsInvalidIterator {
iterator,
Expand All @@ -4891,10 +4891,13 @@ impl<'db> Type<'db> {
// and the type returned by the `__getitem__` method.
//
// No diagnostic is emitted; iteration will always succeed!
Cow::Owned(TupleSpec::homogeneous(UnionType::from_elements(
TupleSpec::homogeneous(
db,
[dunder_next_return, dunder_getitem_return_type],
)))
UnionType::from_elements(
db,
[dunder_next_return, dunder_getitem_return_type],
),
)
})
.map_err(|dunder_getitem_error| {
IterationError::PossiblyUnboundIterAndGetitemError {
Expand All @@ -4921,7 +4924,7 @@ impl<'db> Type<'db> {

// There's no `__iter__` method. Try `__getitem__` instead...
Err(CallDunderError::MethodNotAvailable) => try_call_dunder_getitem()
.map(|ty| Cow::Owned(TupleSpec::homogeneous(ty)))
.map(|ty| TupleSpec::homogeneous(db, ty))
.map_err(
|dunder_getitem_error| IterationError::UnboundIterAndGetitemError {
dunder_getitem_error,
Expand Down
6 changes: 3 additions & 3 deletions crates/ty_python_semantic/src/types/call/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl<'a, 'db> CallArguments<'a, 'db> {
let ty = infer_argument_type(arg, value);
let length = ty
.try_iterate(db)
.map(|tuple| tuple.len())
.map(|tuple| tuple.len(db))
.unwrap_or(TupleLength::unknown());
(Argument::Variadic(length), Some(ty))
}
Expand Down Expand Up @@ -225,7 +225,7 @@ 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.tuple_spec(db) {
return match &*spec {
return match spec.inner(db) {
Tuple::Fixed(fixed_length_tuple) => {
let expanded = fixed_length_tuple
.all_elements()
Expand All @@ -237,7 +237,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
}
})
.multi_cartesian_product()
.map(|types| Type::tuple(TupleType::from_elements(db, types)))
.map(|types| Type::tuple(TupleType::heterogeneous(db, types)))
.collect::<Vec<_>>();

if expanded.len() == 1 {
Expand Down
15 changes: 6 additions & 9 deletions crates/ty_python_semantic/src/types/call/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a
//! union of types, each of which might contain multiple overloads.

use std::borrow::Cow;
use std::collections::HashSet;
use std::fmt;

Expand Down Expand Up @@ -1034,10 +1033,8 @@ impl<'db> Bindings<'db> {
);
continue;
};
overload.set_return_type(Type::tuple(TupleType::new(
db,
tuple_spec.as_ref(),
)));
overload
.set_return_type(Type::tuple(TupleType::new(db, tuple_spec)));
}
}

Expand Down Expand Up @@ -2151,9 +2148,9 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
// arbitrary number of `Unknown`s at the end, so that if the expanded value has a
// smaller arity than the unexpanded value, we still have enough values to assign to
// the already matched parameters.
let argument_types = match argument_types.as_ref() {
let argument_types = match argument_types.inner(self.db) {
Tuple::Fixed(_) => {
Cow::Owned(argument_types.concat(self.db, &Tuple::homogeneous(Type::unknown())))
argument_types.concat(self.db, &Tuple::homogeneous(Type::unknown()))
}
Tuple::Variable(_) => argument_types,
};
Expand All @@ -2173,13 +2170,13 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {

// Check the types by zipping through the splatted argument types and their matched
// parameters.
for (argument_type, parameter_index) in (argument_types.all_elements())
for (argument_type, parameter_index) in (argument_types.all_elements(self.db))
.zip(&self.argument_matches[argument_index].parameters)
{
self.check_argument_type(
adjusted_argument_index,
argument,
*argument_type,
argument_type,
*parameter_index,
);
}
Expand Down
18 changes: 11 additions & 7 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
use crate::types::infer::nearest_enclosing_class;
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::TupleSpec;
use crate::types::tuple::Tuple;
use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
DeprecatedInstance, KnownInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
Expand Down Expand Up @@ -668,7 +668,7 @@ impl<'db> ClassType<'db> {
"__len__" if class_literal.is_tuple(db) => {
let return_type = specialization
.and_then(|spec| spec.tuple(db))
.and_then(|tuple| tuple.len().into_fixed_length())
.and_then(|tuple| tuple.len(db).into_fixed_length())
.and_then(|len| i64::try_from(len).ok())
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(db));
Expand All @@ -679,7 +679,7 @@ impl<'db> ClassType<'db> {
"__bool__" if class_literal.is_tuple(db) => {
let return_type = specialization
.and_then(|spec| spec.tuple(db))
.map(|tuple| tuple.truthiness().into_type(db))
.map(|tuple| tuple.truthiness(db).into_type(db))
.unwrap_or_else(|| KnownClass::Bool.to_instance(db));

synthesize_simple_tuple_method(return_type)
Expand All @@ -692,13 +692,15 @@ impl<'db> ClassType<'db> {
let mut element_type_to_indices: FxIndexMap<Type<'db>, Vec<i64>> =
FxIndexMap::default();

let tuple = tuple.inner(db);

match tuple {
// E.g. for `tuple[int, str]`, we will generate the following overloads:
//
// __getitem__(self, index: Literal[0, -2], /) -> int
// __getitem__(self, index: Literal[1, -1], /) -> str
//
TupleSpec::Fixed(fixed_length_tuple) => {
Tuple::Fixed(fixed_length_tuple) => {
let tuple_length = fixed_length_tuple.len();

for (index, ty) in fixed_length_tuple.elements().enumerate() {
Expand All @@ -721,7 +723,7 @@ impl<'db> ClassType<'db> {
// __getitem__(self, index: Literal[-2], /) -> bytes
// __getitem__(self, index: Literal[-3], /) -> float | str
//
TupleSpec::Variable(variable_length_tuple) => {
Tuple::Variable(variable_length_tuple) => {
for (index, ty) in variable_length_tuple.prefix.iter().enumerate() {
if let Ok(index) = i64::try_from(index) {
element_type_to_indices.entry(*ty).or_default().push(index);
Expand Down Expand Up @@ -851,7 +853,9 @@ impl<'db> ClassType<'db> {
let mut iterable_parameter =
Parameter::positional_only(Some(Name::new_static("iterable")));

let tuple = specialization.and_then(|spec| spec.tuple(db));
let tuple = specialization
.and_then(|spec| spec.tuple(db))
.map(|spec| spec.inner(db));

match tuple {
Some(tuple) => {
Expand Down Expand Up @@ -4720,7 +4724,7 @@ impl SlotsKind {
// __slots__ = ("a", "b")
Type::NominalInstance(nominal) => match nominal
.tuple_spec(db)
.and_then(|spec| spec.len().into_fixed_length())
.and_then(|spec| spec.len(db).into_fixed_length())
{
Some(0) => Self::Empty,
Some(_) => Self::NotEmpty,
Expand Down
8 changes: 4 additions & 4 deletions crates/ty_python_semantic/src/types/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
use crate::types::function::{FunctionType, OverloadLiteral};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::TupleSpec;
use crate::types::tuple::{Tuple, TupleSpec};
use crate::types::{
BoundTypeVarInstance, CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol,
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
Expand Down Expand Up @@ -262,8 +262,8 @@ pub(crate) struct DisplayTuple<'db> {
impl Display for DisplayTuple<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("tuple[")?;
match self.tuple {
TupleSpec::Fixed(tuple) => {
match self.tuple.inner(self.db) {
Tuple::Fixed(tuple) => {
let elements = tuple.elements_slice();
if elements.is_empty() {
f.write_str("()")?;
Expand All @@ -286,7 +286,7 @@ impl Display for DisplayTuple<'_> {
// above only an S is included only if there's a suffix; anything about both a P and an
// S is included if there is either a prefix or a suffix. The initial `tuple[` and
// trailing `]` are printed elsewhere. The `yyy, ...` is printed no matter what.)
TupleSpec::Variable(tuple) => {
Tuple::Variable(tuple) => {
if !tuple.prefix.is_empty() {
tuple.prefix.display(self.db).fmt(f)?;
f.write_str(", ")?;
Expand Down
8 changes: 5 additions & 3 deletions crates/ty_python_semantic/src/types/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ impl<'db> GenericContext<'db> {
db,
self,
partial.types(db),
TupleType::homogeneous(db, Type::unknown()),
Some(TupleType::homogeneous(db, Type::unknown())),
)
} else {
partial
Expand Down Expand Up @@ -424,8 +424,8 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si

impl<'db> Specialization<'db> {
/// Returns the tuple spec for a specialization of the `tuple` class.
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db))
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<TupleSpec<'db>> {
self.tuple_inner(db).map(|tuple_type| tuple_type.spec)
}

/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
Expand Down Expand Up @@ -867,6 +867,8 @@ impl<'db> SpecializationBuilder<'db> {
formal.tuple_instance_spec(self.db),
actual_nominal.tuple_spec(self.db),
) {
let formal_tuple = formal_tuple.inner(self.db);
let actual_tuple = actual_tuple.inner(self.db);
let Some(most_precise_length) =
formal_tuple.len().most_precise(actual_tuple.len())
else {
Expand Down
Loading
Loading