diff --git a/crates/ty_python_semantic/resources/corpus/cyclic_implicit_tuple_alias.py b/crates/ty_python_semantic/resources/corpus/cyclic_implicit_tuple_alias.py new file mode 100644 index 0000000000..35bc3d1f6c --- /dev/null +++ b/crates/ty_python_semantic/resources/corpus/cyclic_implicit_tuple_alias.py @@ -0,0 +1,8 @@ +# Regression test for https://github.com/astral-sh/ty/issues/1848 + +T = tuple[int, 'U'] + +class C(set['U']): + pass + +type U = T | C diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index f0cc3bedc8..0e69f863a6 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -411,7 +411,8 @@ impl<'db> FixedLengthTuple> { // suffix. let mut elements = self.elements().copied(); let prefix = elements.by_ref().take(prefix).collect(); - let variable = UnionType::from_elements(db, elements.by_ref().take(variable)); + let variable = + UnionType::from_elements_leave_aliases(db, elements.by_ref().take(variable)); let suffix = elements.by_ref().take(suffix).collect(); Ok(Tuple::Variable(VariableLengthTuple { prefix, @@ -788,7 +789,7 @@ impl<'db> VariableLengthTuple> { let suffix_underflow = suffix_length.saturating_sub(self_suffix_length); let prefix = (self.prefix_elements().copied().take(prefix_length)) .chain(std::iter::repeat_n(self.variable, prefix_underflow)); - let variable = UnionType::from_elements( + let variable = UnionType::from_elements_leave_aliases( db, (self.prefix_elements().copied().skip(prefix_length)) .chain(std::iter::once(self.variable)) @@ -1158,7 +1159,7 @@ impl<'db> PyIndex<'db> for &VariableLengthTuple> { // large enough that it lands in the variable-length portion. It might also be // small enough to land in the suffix. let index_past_prefix = index - self.prefix.len() + 1; - Ok(UnionType::from_elements( + Ok(UnionType::from_elements_leave_aliases( db, std::iter::once(self.variable) .chain(self.suffix_elements().copied().take(index_past_prefix)), @@ -1175,7 +1176,7 @@ impl<'db> PyIndex<'db> for &VariableLengthTuple> { // large enough that it lands in the variable-length portion. It might also be // small enough to land in the prefix. let index_past_suffix = index_from_end - self.suffix.len() + 1; - Ok(UnionType::from_elements( + Ok(UnionType::from_elements_leave_aliases( db, (self.prefix_elements().rev().copied()) .take(index_past_suffix) @@ -1260,7 +1261,7 @@ impl Tuple { impl<'db> Tuple> { pub(crate) fn homogeneous_element_type(&self, db: &'db dyn Db) -> Type<'db> { - UnionType::from_elements(db, self.all_elements()) + UnionType::from_elements_leave_aliases(db, self.all_elements()) } /// Resizes this tuple to a different length, if possible. If this tuple cannot satisfy the @@ -1718,7 +1719,7 @@ impl<'db> TupleSpecBuilder<'db> { suffix: right_suffix, }), ) => { - let variable = UnionType::from_elements( + let variable = UnionType::from_elements_leave_aliases( db, left_suffix .iter() @@ -1763,7 +1764,7 @@ impl<'db> TupleSpecBuilder<'db> { if our_elements.len() == new_elements.len() => { for (existing, new) in our_elements.iter_mut().zip(new_elements.elements()) { - *existing = UnionType::from_elements(db, [*existing, *new]); + *existing = UnionType::from_elements_leave_aliases(db, [*existing, *new]); } self } @@ -1776,8 +1777,10 @@ impl<'db> TupleSpecBuilder<'db> { // would actually lead to more precise inference, so it's probably not worth the // complexity. _ => { - let unioned = - UnionType::from_elements(db, self.all_elements().chain(other.all_elements())); + let unioned = UnionType::from_elements_leave_aliases( + db, + self.all_elements().chain(other.all_elements()), + ); TupleSpecBuilder::Variable { prefix: vec![], variable: unioned,