Skip to content

Commit

Permalink
test algorithm for contravariant Eq
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Aug 3, 2022
1 parent 926d5bd commit 9ec5e3d
Showing 1 changed file with 95 additions and 0 deletions.
95 changes: 95 additions & 0 deletions tests/run/i13146a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import scala.deriving.*
import scala.compiletime.{erasedValue, summonInline}

// File that breaks the infinite loop caused by implicit search in i13146.scala

inline def summonAll[P, T <: Tuple]: List[Eq[_]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => loopBreaker[P, t] :: summonAll[P, ts]

/** loopBreaker stops summoning a derived typeclass instance from inside its own definition
* @note aparently it needs to be defined separately from `summonAll` to avoid an infinite loop
* in inlining.
*/
inline def loopBreaker[P, T]: Eq[T] = compiletime.summonFrom {
case infiniteRecursion: (T =:= P) => compiletime.error("cannot derive Eq, it will cause an infinite loop")
case recursiveEvidence: (T <:< P) =>
// summonInline will work because to get here `P` must also have a Mirror instance
Eq.derived[T](using summonInline[Mirror.Of[T]])

case existing: Eq[T] => existing
}

trait Eq[-T]:
def eqv(x: T, y: T): Boolean

object Eq:

given Eq[Int] with
def eqv(x: Int, y: Int) = x == y

def check(elem: Eq[_])(x: Any, y: Any): Boolean =
elem.asInstanceOf[Eq[Any]].eqv(x, y)

def iterator[T](p: T) = p.asInstanceOf[Product].productIterator

def eqSum[T](s: Mirror.SumOf[T], elems: => List[Eq[_]]): Eq[T] =
new Eq[T]:
def eqv(x: T, y: T): Boolean =
val ordx = s.ordinal(x)
(s.ordinal(y) == ordx) && check(elems(ordx))(x, y)

def eqProduct[T](p: Mirror.ProductOf[T], elems: => List[Eq[_]]): Eq[T] =
new Eq[T]:
def eqv(x: T, y: T): Boolean =
iterator(x).zip(iterator(y)).zip(elems.iterator).forall {
case ((x, y), elem) => check(elem)(x, y)
}

inline given derived[T](using m: Mirror.Of[T]): Eq[T] =
lazy val elemInstances = summonAll[T, m.MirroredElemTypes]
inline m match
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
end Eq

enum Opt[+T] derives Eq:
case Sm(t: T)
case Nn

case class Rat[N](n: N, d: Opt[N]) derives Eq

// Loop is impossible to derive generically, uncommenting will be an error.
// case class Loop(prev: Loop) derives Eq
// object Loop:
// val Zero = Loop(null) // just to demonstrate that this cannot be derived generically

case class Nat(prev: Opt[Nat]) derives Eq

enum Nat1 derives Eq:
case Succ(prev: Nat1) // this recursion is ok, because the parent type will be Succ
case Zero

@main def Test(): Unit =
import Opt.*
val eqoi = summon[Eq[Opt[Int]]]
assert(eqoi.eqv(Sm(23), Sm(23)))
assert(!eqoi.eqv(Sm(23), Sm(13)))
assert(!eqoi.eqv(Sm(23), Nn))

// check that Rat.derived$Eq reuses Opt.derived$Eq
val eqri = summon[Eq[Rat[Int]]]
assert(eqri.eqv(Rat(23, Sm(23)), Rat(23, Sm(23))))
assert(!eqri.eqv(Rat(23, Sm(23)), Rat(23, Nn)))
assert(!eqri.eqv(Rat(23, Sm(23)), Rat(23, Sm(13))))

// val eql = summon[Eq[Loop]]

val eqn = summon[Eq[Nat]]
assert(eqn.eqv(Nat(Nn), Nat(Nn)))
assert(!eqn.eqv(Nat(Nn), Nat(Sm(Nat(Nn)))))

val eqn1 = summon[Eq[Nat1]]
assert(eqn1.eqv(Nat1.Succ(Nat1.Zero), Nat1.Succ(Nat1.Zero)))
assert(!eqn1.eqv(Nat1.Succ(Nat1.Zero), Nat1.Succ(Nat1.Succ(Nat1.Zero))))

0 comments on commit 9ec5e3d

Please sign in to comment.