|
| 1 | +import scala.deriving.* |
| 2 | +import scala.compiletime.{erasedValue, summonInline} |
| 3 | + |
| 4 | +// File that breaks the infinite loop caused by implicit search in i13146.scala |
| 5 | + |
| 6 | +inline def summonAll[P, T <: Tuple]: List[Eq[_]] = |
| 7 | + inline erasedValue[T] match |
| 8 | + case _: EmptyTuple => Nil |
| 9 | + case _: (t *: ts) => loopBreaker[P, t] :: summonAll[P, ts] |
| 10 | + |
| 11 | +/** loopBreaker stops summoning a derived typeclass instance from inside its own definition |
| 12 | + * @note aparently it needs to be defined separately from `summonAll` to avoid an infinite loop |
| 13 | + * in inlining. |
| 14 | + */ |
| 15 | +inline def loopBreaker[P, T]: Eq[T] = compiletime.summonFrom { |
| 16 | + case infiniteRecursion: (T =:= P) => compiletime.error("cannot derive Eq, it will cause an infinite loop") |
| 17 | + case recursiveEvidence: (T <:< P) => |
| 18 | + // summonInline will work because to get here `P` must also have a Mirror instance |
| 19 | + Eq.derived[T](using summonInline[Mirror.Of[T]]) |
| 20 | + |
| 21 | + case existing: Eq[T] => existing |
| 22 | +} |
| 23 | + |
| 24 | +trait Eq[-T]: |
| 25 | + def eqv(x: T, y: T): Boolean |
| 26 | + |
| 27 | +object Eq: |
| 28 | + |
| 29 | + given Eq[Int] with |
| 30 | + def eqv(x: Int, y: Int) = x == y |
| 31 | + |
| 32 | + def check(elem: Eq[_])(x: Any, y: Any): Boolean = |
| 33 | + elem.asInstanceOf[Eq[Any]].eqv(x, y) |
| 34 | + |
| 35 | + def iterator[T](p: T) = p.asInstanceOf[Product].productIterator |
| 36 | + |
| 37 | + def eqSum[T](s: Mirror.SumOf[T], elems: => List[Eq[_]]): Eq[T] = |
| 38 | + new Eq[T]: |
| 39 | + def eqv(x: T, y: T): Boolean = |
| 40 | + val ordx = s.ordinal(x) |
| 41 | + (s.ordinal(y) == ordx) && check(elems(ordx))(x, y) |
| 42 | + |
| 43 | + def eqProduct[T](p: Mirror.ProductOf[T], elems: => List[Eq[_]]): Eq[T] = |
| 44 | + new Eq[T]: |
| 45 | + def eqv(x: T, y: T): Boolean = |
| 46 | + iterator(x).zip(iterator(y)).zip(elems.iterator).forall { |
| 47 | + case ((x, y), elem) => check(elem)(x, y) |
| 48 | + } |
| 49 | + |
| 50 | + inline given derived[T](using m: Mirror.Of[T]): Eq[T] = |
| 51 | + lazy val elemInstances = summonAll[T, m.MirroredElemTypes] |
| 52 | + inline m match |
| 53 | + case s: Mirror.SumOf[T] => eqSum(s, elemInstances) |
| 54 | + case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances) |
| 55 | +end Eq |
| 56 | + |
| 57 | +enum Opt[+T] derives Eq: |
| 58 | + case Sm(t: T) |
| 59 | + case Nn |
| 60 | + |
| 61 | +case class Rat[N](n: N, d: Opt[N]) derives Eq |
| 62 | + |
| 63 | +// Loop is impossible to derive generically, uncommenting will be an error. |
| 64 | +// case class Loop(prev: Loop) derives Eq |
| 65 | +// object Loop: |
| 66 | +// val Zero = Loop(null) // just to demonstrate that this cannot be derived generically |
| 67 | + |
| 68 | +case class Nat(prev: Opt[Nat]) derives Eq |
| 69 | + |
| 70 | +enum Nat1 derives Eq: |
| 71 | + case Succ(prev: Nat1) // this recursion is ok, because the parent type will be Succ |
| 72 | + case Zero |
| 73 | + |
| 74 | +@main def Test(): Unit = |
| 75 | + import Opt.* |
| 76 | + val eqoi = summon[Eq[Opt[Int]]] |
| 77 | + assert(eqoi.eqv(Sm(23), Sm(23))) |
| 78 | + assert(!eqoi.eqv(Sm(23), Sm(13))) |
| 79 | + assert(!eqoi.eqv(Sm(23), Nn)) |
| 80 | + |
| 81 | + // check that Rat.derived$Eq reuses Opt.derived$Eq |
| 82 | + val eqri = summon[Eq[Rat[Int]]] |
| 83 | + assert(eqri.eqv(Rat(23, Sm(23)), Rat(23, Sm(23)))) |
| 84 | + assert(!eqri.eqv(Rat(23, Sm(23)), Rat(23, Nn))) |
| 85 | + assert(!eqri.eqv(Rat(23, Sm(23)), Rat(23, Sm(13)))) |
| 86 | + |
| 87 | + // val eql = summon[Eq[Loop]] |
| 88 | + |
| 89 | + val eqn = summon[Eq[Nat]] |
| 90 | + assert(eqn.eqv(Nat(Nn), Nat(Nn))) |
| 91 | + assert(!eqn.eqv(Nat(Nn), Nat(Sm(Nat(Nn))))) |
| 92 | + |
| 93 | + val eqn1 = summon[Eq[Nat1]] |
| 94 | + assert(eqn1.eqv(Nat1.Succ(Nat1.Zero), Nat1.Succ(Nat1.Zero))) |
| 95 | + assert(!eqn1.eqv(Nat1.Succ(Nat1.Zero), Nat1.Succ(Nat1.Succ(Nat1.Zero)))) |
0 commit comments