Desugaring of for with pattern match and if guard

Please explain how does the following for desugar and why the last variant does not compile, while the one before it does:

trait A {
  def a: Boolean
}
trait B extends A {
  def b: Boolean
}

def f() = {
  val s = Seq[A]()

  val as = for (case x: A <- s if x.a) yield x // this compiles, the result is Seq[A]
  val bs = for (case x: B <- s) yield x // this compiles, the result is Seq[B]
  for (case x: B <- s) yield x.b // this compiles
  for (case x: B <- s if x.b) yield x // this gives an error: Found B => B, Required A => B
}

The behavior is the same with 3.3.7 and 3.7.4, with or without -preview.

It’s explained as a bug.

It should have the same behavior as:

for case (x: B) <- s if x.b yield x

where the extra parens say, “I am a pattern.” More precisely, it signals, “I am refutable.”

Without case, the parens matter:

  for x: B <- s if x.b yield x // B => Boolean
  for (x: B) <- s if x.b yield x // pattern's type B is more specialized...

But I don’t think parens should matter with case, which always takes a refutable pattern and always filters.

I don’t see a unit test for “parenless case", so I assume it’s a bug.

4 Likes

OK. Reported as For comprehension desugaring different when pattern not enclosed in parentheses · Issue #24736 · scala/scala3 · GitHub - and thanks for the workaround.

in the meantime you can write (for (case x: B ← s) yield x).filter(_.b)

That builds the target collection twice; I would advertise the judicious use of parentheses in my reply.

It uses withFilter instead of filter.

1 Like