mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 13:30:49 -05:00
[ty] Make tuple intersection a fallible operation (#22094)
## Summary This PR attempts to address a TODO in https://github.com/astral-sh/ruff/pull/21965#discussion_r2635378498.
This commit is contained in:
@@ -4645,7 +4645,13 @@ impl<'db> Type<'db> {
|
||||
let first_spec = specs_iter.next()?;
|
||||
let mut builder = TupleSpecBuilder::from(&*first_spec);
|
||||
for spec in specs_iter {
|
||||
builder = builder.intersect(db, &spec);
|
||||
// Two tuples cannot have incompatible specs unless the tuples themselves
|
||||
// are disjoint. `IntersectionBuilder` eagerly simplifies such
|
||||
// intersections to `Never`, so this should always return `Some`.
|
||||
let Some(intersected) = builder.intersect(db, &spec) else {
|
||||
return Some(Cow::Owned(TupleSpec::homogeneous(Type::unknown())));
|
||||
};
|
||||
builder = intersected;
|
||||
}
|
||||
Some(Cow::Owned(builder.build()))
|
||||
}
|
||||
|
||||
@@ -1931,12 +1931,14 @@ impl<'db> TupleSpecBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a new tuple-spec builder that reflects the intersection of this tuple and another tuple.
|
||||
/// Return a new tuple-spec builder that reflects the intersection of this tuple and another
|
||||
/// tuple, or `None` if the intersection is impossible (e.g., two fixed-length tuples with
|
||||
/// different lengths).
|
||||
///
|
||||
/// For example, if `self` is a tuple-spec builder for `tuple[int, str]` and `other` is a
|
||||
/// tuple-spec for `tuple[object, object]`, the result will be a tuple-spec builder for
|
||||
/// `tuple[int, str]` (since `int & object` simplifies to `int`, and `str & object` to `str`).
|
||||
pub(crate) fn intersect(mut self, db: &'db dyn Db, other: &TupleSpec<'db>) -> Self {
|
||||
pub(crate) fn intersect(mut self, db: &'db dyn Db, other: &TupleSpec<'db>) -> Option<Self> {
|
||||
match (&mut self, other) {
|
||||
// Both fixed-length with the same length: element-wise intersection.
|
||||
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Fixed(new_elements))
|
||||
@@ -1945,24 +1947,23 @@ impl<'db> TupleSpecBuilder<'db> {
|
||||
for (existing, new) in our_elements.iter_mut().zip(new_elements.all_elements()) {
|
||||
*existing = IntersectionType::from_elements(db, [*existing, *new]);
|
||||
}
|
||||
return self;
|
||||
Some(self)
|
||||
}
|
||||
|
||||
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Variable(var)) => {
|
||||
if let Ok(tuple) = var.resize(db, TupleLength::Fixed(our_elements.len())) {
|
||||
return self.intersect(db, &tuple);
|
||||
}
|
||||
}
|
||||
// Fixed-length tuples with different lengths cannot intersect.
|
||||
(TupleSpecBuilder::Fixed(_), TupleSpec::Fixed(_)) => None,
|
||||
|
||||
(TupleSpecBuilder::Variable { .. }, TupleSpec::Fixed(fixed)) => {
|
||||
if let Ok(tuple) = self
|
||||
.clone()
|
||||
.build()
|
||||
.resize(db, TupleLength::Fixed(fixed.len()))
|
||||
{
|
||||
return TupleSpecBuilder::from(&tuple).intersect(db, other);
|
||||
}
|
||||
}
|
||||
(TupleSpecBuilder::Fixed(our_elements), TupleSpec::Variable(var)) => var
|
||||
.resize(db, TupleLength::Fixed(our_elements.len()))
|
||||
.ok()
|
||||
.and_then(|tuple| self.intersect(db, &tuple)),
|
||||
|
||||
(TupleSpecBuilder::Variable { .. }, TupleSpec::Fixed(fixed)) => self
|
||||
.clone()
|
||||
.build()
|
||||
.resize(db, TupleLength::Fixed(fixed.len()))
|
||||
.ok()
|
||||
.and_then(|tuple| TupleSpecBuilder::from(&tuple).intersect(db, other)),
|
||||
|
||||
(
|
||||
TupleSpecBuilder::Variable {
|
||||
@@ -1982,29 +1983,20 @@ impl<'db> TupleSpecBuilder<'db> {
|
||||
for (existing, new) in suffix.iter_mut().zip(var.suffix_elements()) {
|
||||
*existing = IntersectionType::from_elements(db, [*existing, *new]);
|
||||
}
|
||||
return self;
|
||||
return Some(self);
|
||||
}
|
||||
|
||||
let self_built = self.clone().build();
|
||||
let self_len = self_built.len();
|
||||
if let Ok(resized) = var.resize(db, self_len) {
|
||||
return self.intersect(db, &resized);
|
||||
} else if let Ok(resized) = self_built.resize(db, var.len()) {
|
||||
return TupleSpecBuilder::from(&resized).intersect(db, other);
|
||||
}
|
||||
var.resize(db, self_len)
|
||||
.ok()
|
||||
.and_then(|resized| self.intersect(db, &resized))
|
||||
.or_else(|| {
|
||||
self_built.resize(db, var.len()).ok().and_then(|resized| {
|
||||
TupleSpecBuilder::from(&resized).intersect(db, other)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// TODO: probably incorrect? `tuple[int, str] & tuple[int, str, bytes]` should resolve to `Never`.
|
||||
// So maybe this function should be fallible (return an `Option`)?
|
||||
let intersected =
|
||||
IntersectionType::from_elements(db, self.all_elements().chain(other.all_elements()));
|
||||
TupleSpecBuilder::Variable {
|
||||
prefix: vec![],
|
||||
variable: intersected,
|
||||
suffix: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user