From bcc677e77f0da7449265775323ea63454b2c5efc Mon Sep 17 00:00:00 2001 From: Tejas-Ketkar Date: Fri, 18 Jul 2025 12:11:02 -0700 Subject: [PATCH 1/2] Bug Fix 708 Bug Fix 708 - Iterability Checker --- pyrefly/lib/alt/solve.rs | 13 ++++++++++++- pyrefly/lib/test/pattern_match.rs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pyrefly/lib/alt/solve.rs b/pyrefly/lib/alt/solve.rs index 66cef9433..4825779c4 100644 --- a/pyrefly/lib/alt/solve.rs +++ b/pyrefly/lib/alt/solve.rs @@ -584,7 +584,18 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } Type::Union(ts) => ts .iter() - .flat_map(|t| self.iterate(t, range, errors)) + .filter_map(|t| { + let is_iterable = self.unwrap_iterable(t).is_some() + || matches!(t, Type::Tuple(_)) + || matches!(t, Type::ClassType(cls) if self.as_tuple(cls).is_some()); + + if is_iterable { + Some(self.iterate(t, range, errors)) + } else { + None + } + }) + .flatten() .collect(), _ => { let ty = self diff --git a/pyrefly/lib/test/pattern_match.rs b/pyrefly/lib/test/pattern_match.rs index ecc38d9ca..7ff7026c9 100644 --- a/pyrefly/lib/test/pattern_match.rs +++ b/pyrefly/lib/test/pattern_match.rs @@ -60,3 +60,22 @@ def my_func(x: dict[MyEnumType, int]) -> int: return 0 "#, ); + +testcase!( + test_union_pattern_match_sequence, + r#" +from typing import * + +class T: ... + +def my_func(x: T | tuple[T, T]): + match x: + case T(), T(): # Should not error: Type `T` is not iterable + print("Two T") + case T(): + print("One T") + +my_func(T()) +my_func((T(), T())) +"#, +); From 23a6ca8dbdf618121f66fab16496bbf3abe3a48a Mon Sep 17 00:00:00 2001 From: Tejas-Ketkar Date: Sat, 19 Jul 2025 12:24:41 -0700 Subject: [PATCH 2/2] Added Helper Function is_iterable --- pyrefly/lib/alt/solve.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyrefly/lib/alt/solve.rs b/pyrefly/lib/alt/solve.rs index 29b15ffed..9313eb2ff 100644 --- a/pyrefly/lib/alt/solve.rs +++ b/pyrefly/lib/alt/solve.rs @@ -560,6 +560,12 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } } + fn is_iterable(&self, ty: &Type) -> bool { + self.unwrap_iterable(ty).is_some() + || matches!(ty, Type::Tuple(_)) + || matches!(ty, Type::ClassType(cls) if self.as_tuple(cls).is_some()) + } + /// Given an `iterable` type, determine the iteration type; this is the type /// of `x` if we were to loop using `for x in iterable`. /// @@ -585,11 +591,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { Type::Union(ts) => ts .iter() .filter_map(|t| { - let is_iterable = self.unwrap_iterable(t).is_some() - || matches!(t, Type::Tuple(_)) - || matches!(t, Type::ClassType(cls) if self.as_tuple(cls).is_some()); - - if is_iterable { + if self.is_iterable(t) { Some(self.iterate(t, range, errors)) } else { None