Remove Type::tuple in favor of TupleType::from_elements (#15218)

## Summary

Remove `Type::tuple` in favor of `TupleType::from_elements`, avoid a few
intermediate `Vec`tors. Resolves an old [review
comment](https://github.com/astral-sh/ruff/pull/14744#discussion_r1867493706).

## Test Plan

New regression test for something I ran into while implementing this.
This commit is contained in:
David Peter 2025-01-02 17:22:32 +01:00 committed by GitHub
parent 11e873eb45
commit 7671a3bbc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 27 deletions

View file

@ -705,13 +705,6 @@ impl<'db> Type<'db> {
Self::BytesLiteral(BytesLiteralType::new(db, bytes)) Self::BytesLiteral(BytesLiteralType::new(db, bytes))
} }
pub fn tuple<T: Into<Type<'db>>>(
db: &'db dyn Db,
elements: impl IntoIterator<Item = T>,
) -> Self {
TupleType::from_elements(db, elements)
}
#[must_use] #[must_use]
pub fn negate(&self, db: &'db dyn Db) -> Type<'db> { pub fn negate(&self, db: &'db dyn Db) -> Type<'db> {
IntersectionBuilder::new(db).add_negative(*self).build() IntersectionBuilder::new(db).add_negative(*self).build()
@ -2118,15 +2111,16 @@ impl<'db> Type<'db> {
Type::Union(UnionType::new(db, elements)) Type::Union(UnionType::new(db, elements))
}; };
let version_info_elements = &[ TupleType::from_elements(
Type::IntLiteral(python_version.major.into()), db,
Type::IntLiteral(python_version.minor.into()), [
int_instance_ty, Type::IntLiteral(python_version.major.into()),
release_level_ty, Type::IntLiteral(python_version.minor.into()),
int_instance_ty, int_instance_ty,
]; release_level_ty,
int_instance_ty,
Self::tuple(db, version_info_elements) ],
)
} }
/// Given a type that is assumed to represent an instance of a class, /// Given a type that is assumed to represent an instance of a class,
@ -3435,8 +3429,8 @@ impl<'db> Class<'db> {
/// The member resolves to a member on the class itself or any of its proper superclasses. /// The member resolves to a member on the class itself or any of its proper superclasses.
pub(crate) fn class_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> { pub(crate) fn class_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
if name == "__mro__" { if name == "__mro__" {
let tuple_elements: Vec<Type<'db>> = self.iter_mro(db).map(Type::from).collect(); let tuple_elements = self.iter_mro(db).map(Type::from);
return Type::tuple(db, &tuple_elements).into(); return TupleType::from_elements(db, tuple_elements).into();
} }
for superclass in self.iter_mro(db) { for superclass in self.iter_mro(db) {
@ -3846,7 +3840,7 @@ pub(crate) mod tests {
} }
Ty::Tuple(tys) => { Ty::Tuple(tys) => {
let elements = tys.into_iter().map(|ty| ty.into_type(db)); let elements = tys.into_iter().map(|ty| ty.into_type(db));
Type::tuple(db, elements) TupleType::from_elements(db, elements)
} }
Ty::SubclassOfAny => Type::subclass_of_base(ClassBase::Any), Ty::SubclassOfAny => Type::subclass_of_base(ClassBase::Any),
Ty::SubclassOfUnknown => Type::subclass_of_base(ClassBase::Unknown), Ty::SubclassOfUnknown => Type::subclass_of_base(ClassBase::Unknown),

View file

@ -2672,10 +2672,12 @@ impl<'db> TypeInferenceBuilder<'db> {
parenthesized: _, parenthesized: _,
} = tuple; } = tuple;
let element_types: Vec<Type<'db>> = // Collecting all elements is necessary to infer all sub-expressions even if some
elts.iter().map(|elt| self.infer_expression(elt)).collect(); // element types are `Never` (which leads `from_elements` to return early without
// consuming the whole iterator).
let element_types: Vec<_> = elts.iter().map(|elt| self.infer_expression(elt)).collect();
Type::tuple(self.db(), &element_types) TupleType::from_elements(self.db(), element_types)
} }
fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> { fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> {
@ -4239,8 +4241,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let (start, stop, step) = slice_ty.as_tuple(self.db()); let (start, stop, step) = slice_ty.as_tuple(self.db());
if let Ok(new_elements) = elements.py_slice(start, stop, step) { if let Ok(new_elements) = elements.py_slice(start, stop, step) {
let new_elements: Vec<_> = new_elements.copied().collect(); TupleType::from_elements(self.db(), new_elements)
Type::tuple(self.db(), &new_elements)
} else { } else {
report_slice_step_size_zero(&self.context, value_node.into()); report_slice_step_size_zero(&self.context, value_node.into());
Type::Unknown Type::Unknown
@ -4842,7 +4843,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let ty = if return_todo { let ty = if return_todo {
todo_type!("full tuple[...] support") todo_type!("full tuple[...] support")
} else { } else {
Type::tuple(self.db(), &element_types) TupleType::from_elements(self.db(), element_types)
}; };
// Here, we store the type for the inner `int, str` tuple-expression, // Here, we store the type for the inner `int, str` tuple-expression,
@ -4857,7 +4858,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) { if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) {
todo_type!("full tuple[...] support") todo_type!("full tuple[...] support")
} else { } else {
Type::tuple(self.db(), [single_element_ty]) TupleType::from_elements(self.db(), std::iter::once(single_element_ty))
} }
} }
} }

View file

@ -96,7 +96,7 @@ impl<'db> Unpacker<'db> {
// with each individual character, instead of just an array of // with each individual character, instead of just an array of
// `LiteralString`, but there would be a cost and it's not clear that // `LiteralString`, but there would be a cost and it's not clear that
// it's worth it. // it's worth it.
Type::tuple( TupleType::from_elements(
self.db(), self.db(),
std::iter::repeat(Type::LiteralString) std::iter::repeat(Type::LiteralString)
.take(string_literal_ty.python_len(self.db())), .take(string_literal_ty.python_len(self.db())),

View file

@ -0,0 +1,17 @@
"""
Regression test that makes sure we do not short-circuit here after
determining that the overall type will be `Never` and still infer
a type for the second tuple element `2`.
Relevant discussion:
https://github.com/astral-sh/ruff/pull/15218#discussion_r1900811073
"""
from typing_extensions import Never
def never() -> Never:
return never()
(never(), 2)