diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index c5f38eb642..3eaee2fefe 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -408,6 +408,18 @@ impl<'db> Type<'db> { /// pass /// ``` fn iterate(&self, db: &'db dyn Db) -> IterationOutcome<'db> { + if let Type::Tuple(tuple_type) = self { + return IterationOutcome::Iterable { + element_ty: tuple_type + .elements(db) + .iter() + .fold(UnionBuilder::new(db), |builder, element| { + builder.add(*element) + }) + .build(), + }; + } + // `self` represents the type of the iterable; // `__iter__` and `__next__` are both looked up on the class of the iterable: let iterable_meta_type = self.to_meta_type(db); diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 051e8db2bf..5df68b41cd 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4355,6 +4355,28 @@ mod tests { Ok(()) } + #[test] + fn for_loop_with_heterogenous_tuple() -> anyhow::Result<()> { + let mut db = setup_db(); + + db.write_dedented( + "src/a.py", + " + for x in (1, 'a', b'foo'): + pass + ", + )?; + + assert_public_ty( + &db, + "src/a.py", + "x", + r#"Literal[1] | Literal["a"] | Literal[b"foo"]"#, + ); + + Ok(()) + } + #[test] fn except_handler_single_exception() -> anyhow::Result<()> { let mut db = setup_db();