diff --git a/crates/ty_python_semantic/resources/mdtest/loops/for.md b/crates/ty_python_semantic/resources/mdtest/loops/for.md index 0da97ff5c0..1d657575c6 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/for.md @@ -362,6 +362,45 @@ def _(x: Sequence[int], y: object): reveal_type(item) # revealed: int ``` +## Intersection where some elements are not iterable + +When iterating over an intersection type, we should only fail if all positive elements fail to +iterate. If some elements are iterable and some are not, we should iterate over the iterable ones +and intersect their element types. + +```py +from ty_extensions import Intersection + +class NotIterable: + pass + +def _(x: Intersection[list[int], NotIterable]): + # `list[int]` is iterable (yielding `int`), but `NotIterable` is not. + # We should still be able to iterate over the intersection. + for item in x: + reveal_type(item) # revealed: int +``` + +## Intersection where all elements are not iterable + +When iterating over an intersection type where all positive elements are not iterable, we should +fail to iterate. + +```py +from ty_extensions import Intersection + +class NotIterable1: + pass + +class NotIterable2: + pass + +def _(x: Intersection[NotIterable1, NotIterable2]): + # error: [not-iterable] + for item in x: + reveal_type(item) # revealed: Unknown +``` + ## Possibly-not-callable `__iter__` method ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6bcc57f03f..233095d0be 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6615,11 +6615,19 @@ impl<'db> Type<'db> { Type::Intersection(intersection) => { // For intersections, we iterate over each positive element and intersect // the resulting element types. Negative elements don't affect iteration. - let mut elements_iter = intersection.positive_elements_or_object(db); - let first_element_spec = elements_iter.next()?.try_iterate_with_mode(db, EvaluationMode::Sync).ok()?; - let mut builder = TupleSpecBuilder::from(&*first_element_spec); - for element in elements_iter { - builder = builder.intersect(db, &*element.try_iterate_with_mode(db, EvaluationMode::Sync).ok()?); + // 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. + let mut specs_iter = intersection + .positive_elements_or_object(db) + .filter_map(|element| { + element + .try_iterate_with_mode(db, EvaluationMode::Sync) + .ok() + }); + let first_spec = specs_iter.next()?; + let mut builder = TupleSpecBuilder::from(&*first_spec); + for spec in specs_iter { + builder = builder.intersect(db, &spec); } Some(Cow::Owned(builder.build())) }