-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Hey all,
while playing around with extractors and compile time ops, I noticed something which might be a compiler bug. When one has an unapply: Option[(String, String)], one normally only needs one layer of parentheses in a pattern match, i.e. case Unapply(a, b) => .... But when I derived the (String, String) type via match types and used two different approaches which to me seemed equivalent, one of them required an extra level of parentheses, i.e. case Unapply((a, b)) => ....
If I had to guess, one of those two methods is not reduced to a tuple at the time the patmat code checks whether it is a tuple, but of course I have no knowledge of the compiler internals. The code is below, you can find a scastie here: https://scastie.scala-lang.org/HfRYp0utTNKjRQjptpVrFQ
Compiler version
3.1.2
Minimized code
import scala.compiletime.ops.int.*
import scala.compiletime.*
// make the string tuple of length N type
type TupleN[X] = X match
case 0 => EmptyTuple
case 2 => (String, String)
// same, but written differently
type TupleNRec[X] = X match
case 0 => EmptyTuple
case _ => Tuple.Concat[TupleNRec[X - 1], Tuple1[String]]
// basic unapply to use in pattern match
trait Unapplyable[A] {
def unapply(s: String): Option[A]
}
// create a extractor for a string tuple of size N
inline def extractN[N]: Unapplyable[TupleN[N]] =
val length = constValue[N]
new Unapplyable[TupleN[N]]:
def unapply(s: String): Option[TupleN[N]] =
val split = s.split(";")
if split.length == length then Some(Tuple.fromArray(split).asInstanceOf[TupleN[N]])
else None
// the same, but using the recursive type defintion
inline def extractNRec[N]: Unapplyable[TupleNRec[N]] =
val length = constValue[N]
new Unapplyable[TupleNRec[N]]:
def unapply(s: String): Option[TupleNRec[N]] =
val split = s.split(";")
if split.length == length then Some(Tuple.fromArray(split).asInstanceOf[TupleNRec[N]])
else None
// scala seems to think that those are the same types
summon[TupleN[2] =:= TupleNRec[2]]
// the second arm only compiles with ((a, b)), not (a, b)
val test = "a;b" match
case extractN[2](a, b) => ()
case extractNRec[2](a, b) => () Output
Wrong number of argument patterns for Playground.extractNRec[2.type]; expected: ((String, String))
Expectation
It should compile. Both should use the same number of parentheses.