Skip to content

Commit 9ec5e3d

Browse files
committed
test algorithm for contravariant Eq
1 parent 926d5bd commit 9ec5e3d

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

tests/run/i13146a.scala

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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

Comments
 (0)