mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-23 09:19:39 +00:00
[ty] Fix iteration over intersections with TypeVars whose bounds contain non-iterable types
This commit is contained in:
parent
fee4e2d72a
commit
dbeba3c22b
2 changed files with 67 additions and 5 deletions
|
|
@ -951,3 +951,24 @@ for x in Bar:
|
|||
# TODO: should reveal `Any`
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Iterating over an intersection with a TypeVar whose bound is a union
|
||||
|
||||
When a TypeVar has a union bound where some elements are iterable and some are not, and the TypeVar
|
||||
is intersected with an iterable type (e.g., via `isinstance`), the iteration should use the iterable
|
||||
parts of the TypeVar's bound.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def f[T: tuple[int, ...] | int](x: T):
|
||||
if isinstance(x, tuple):
|
||||
reveal_type(x) # revealed: T@f & tuple[object, ...]
|
||||
for item in x:
|
||||
# The TypeVar T is constrained to tuple[int, ...] by the isinstance check,
|
||||
# so iterating should give `int`, not `object`.
|
||||
reveal_type(item) # revealed: int
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6722,13 +6722,54 @@ impl<'db> Type<'db> {
|
|||
// the resulting element types. Negative elements don't affect iteration.
|
||||
// We only fail if all elements fail to iterate; as long as at least one
|
||||
// element can be iterated over, we can produce a result.
|
||||
//
|
||||
// For TypeVars with union bounds where some union elements are not iterable,
|
||||
// we iterate the iterable parts of the bound. This is sound because the
|
||||
// intersection constrains the TypeVar to only the iterable parts.
|
||||
// For example, for `T & tuple[object, ...]` where `T: tuple[int, ...] | int`,
|
||||
// iterating should give `int` (from the `tuple[int, ...]` part of T's bound),
|
||||
// not `object` (from ignoring T entirely).
|
||||
let try_iterate_element =
|
||||
|element: Type<'db>| -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
// First try normal iteration
|
||||
if let Ok(spec) =
|
||||
element.try_iterate_with_mode(db, EvaluationMode::Sync)
|
||||
{
|
||||
return Some(spec);
|
||||
}
|
||||
|
||||
// If that fails and the element is a TypeVar with a union bound,
|
||||
// try to iterate the iterable parts of the union.
|
||||
if let Type::TypeVar(tvar) = element {
|
||||
if let Some(TypeVarBoundOrConstraints::UpperBound(Type::Union(
|
||||
union,
|
||||
))) = tvar.typevar(db).bound_or_constraints(db)
|
||||
{
|
||||
// Collect iteration specs for all iterable union elements.
|
||||
let mut iterable_specs = union.elements(db).iter().filter_map(
|
||||
|elem| {
|
||||
elem.try_iterate_with_mode(db, EvaluationMode::Sync)
|
||||
.ok()
|
||||
},
|
||||
);
|
||||
|
||||
// If any union elements are iterable, union their specs.
|
||||
if let Some(first) = iterable_specs.next() {
|
||||
let mut builder = TupleSpecBuilder::from(&*first);
|
||||
for spec in iterable_specs {
|
||||
builder = builder.union(db, &spec);
|
||||
}
|
||||
return Some(Cow::Owned(builder.build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
let mut specs_iter = intersection
|
||||
.positive_elements_or_object(db)
|
||||
.filter_map(|element| {
|
||||
element
|
||||
.try_iterate_with_mode(db, EvaluationMode::Sync)
|
||||
.ok()
|
||||
});
|
||||
.filter_map(try_iterate_element);
|
||||
let first_spec = specs_iter.next()?;
|
||||
let mut builder = TupleSpecBuilder::from(&*first_spec);
|
||||
for spec in specs_iter {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue