mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[ty] Upcast heterogeneous and mixed tuples to homogeneous tuples where it's necessary to solve a TypeVar
(#19635)
## Summary This PR improves our generics solver such that we are able to solve the `TypeVar` in this snippet to `int | str` (the union of the elements in the heterogeneous tuple) by upcasting the heterogeneous tuple to its pure-homogeneous-tuple supertype: ```py def f[T](x: tuple[T, ...]) -> T: return x[0] def g(x: tuple[int, str]): reveal_type(f(x)) ``` ## Test Plan Mdtests. Some TODOs remain in the mdtest regarding solving `TypeVar`s for mixed tuples, but I think this PR on its own is a significant step forward for our generics solver when it comes to tuple types. --------- Co-authored-by: Douglas Creager <dcreager@dcreager.net>
This commit is contained in:
parent
d797592f70
commit
ec3d5ebda2
4 changed files with 72 additions and 29 deletions
|
@ -761,17 +761,19 @@ 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);
|
||||
match (formal_tuple, actual_tuple) {
|
||||
(TupleSpec::Fixed(formal_tuple), TupleSpec::Fixed(actual_tuple)) => {
|
||||
if formal_tuple.len() == actual_tuple.len() {
|
||||
for (formal_element, actual_element) in formal_tuple.elements().zip(actual_tuple.elements()) {
|
||||
self.infer(*formal_element, *actual_element)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Infer specializations of variable-length tuples
|
||||
(TupleSpec::Variable(_), _) | (_, TupleSpec::Variable(_)) => {}
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,33 @@ impl TupleLength {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given two [`TupleLength`]s, return the more precise instance,
|
||||
/// if it makes sense to consider one more precise than the other.
|
||||
pub(crate) fn most_precise(self, other: Self) -> Option<Self> {
|
||||
match (self, other) {
|
||||
// A fixed-length tuple is equally as precise as another fixed-length tuple if they
|
||||
// have the same length. For two differently sized fixed-length tuples, however,
|
||||
// neither tuple length is more precise than the other: the two tuple lengths are
|
||||
// entirely disjoint.
|
||||
(TupleLength::Fixed(left), TupleLength::Fixed(right)) => {
|
||||
(left == right).then_some(self)
|
||||
}
|
||||
|
||||
// A fixed-length tuple is more precise than a variable-length one.
|
||||
(fixed @ TupleLength::Fixed(_), TupleLength::Variable(..))
|
||||
| (TupleLength::Variable(..), fixed @ TupleLength::Fixed(_)) => Some(fixed),
|
||||
|
||||
// For two variable-length tuples, the tuple with the larger number
|
||||
// of required items is more precise.
|
||||
(TupleLength::Variable(..), TupleLength::Variable(..)) => {
|
||||
Some(match self.minimum().cmp(&other.minimum()) {
|
||||
Ordering::Less => other,
|
||||
Ordering::Equal | Ordering::Greater => self,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn display_minimum(self) -> String {
|
||||
let minimum_length = self.minimum();
|
||||
match self {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue