From 926d5bd1b1d11fdf39db7aaac53584c136aaf1cd Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 2 Aug 2022 16:08:19 +0200 Subject: [PATCH 1/3] fix i13146 - use fullyDefinedType --- .../dotty/tools/dotc/typer/Synthesizer.scala | 3 +- tests/run/i13146.scala | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/run/i13146.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index b8bd98fd4056..b20b6f10ed7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -540,7 +540,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): (using Context): TreeWithErrors = if checkFormal(formal) then formal.member(tpnme.MirroredType).info match - case TypeBounds(mirroredType, _) => synth(TypeOps.stripTypeVars(mirroredType), formal, span) + case TypeBounds(mirroredType, _) => + synth(fullyDefinedType(mirroredType, "Mirror.*Of argument", ctx.source.atSpan(span)), formal, span) case other => EmptyTreeNoError else EmptyTreeNoError diff --git a/tests/run/i13146.scala b/tests/run/i13146.scala new file mode 100644 index 000000000000..cbaee29a5ec0 --- /dev/null +++ b/tests/run/i13146.scala @@ -0,0 +1,53 @@ +import scala.deriving.* +import scala.compiletime.{erasedValue, summonInline} + +inline def summonAll[T <: Tuple]: List[Eq[_]] = + inline erasedValue[T] match + case _: EmptyTuple => Nil + case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts] + +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[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]: + case Sm(t: T) + case Nn + +object Opt: + given derivedEq[T]: Eq[Opt[T]] = Eq.derived + +@main def Test(): Unit = + import Opt.* + val eqoi = summon[Eq[Opt[Int]]] + // assert(eqoi.eqv(Sm(23), Sm(23))) -> eqoi.eqv makes an infinite loop + // assert(!eqoi.eqv(Sm(23), Sm(13))) -> eqoi.eqv makes an infinite loop + // assert(!eqoi.eqv(Sm(23), Nn)) -> eqoi.eqv makes an infinite loop From 9ec5e3ddf6f22c7d974976857cdec73f18685a35 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 3 Aug 2022 12:21:15 +0200 Subject: [PATCH 2/3] test algorithm for contravariant Eq --- tests/run/i13146a.scala | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/run/i13146a.scala diff --git a/tests/run/i13146a.scala b/tests/run/i13146a.scala new file mode 100644 index 000000000000..37e135ac9f76 --- /dev/null +++ b/tests/run/i13146a.scala @@ -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)))) From 9a9c40d039d6c833ab1b753f37655a2a53b27e5a Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 3 Aug 2022 14:09:22 +0200 Subject: [PATCH 3/3] stripTypeVars after fullyDefinedType --- .../dotty/tools/dotc/typer/Synthesizer.scala | 4 +++- tests/run/i13146poly.scala | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/run/i13146poly.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index b20b6f10ed7f..f3098cfd673f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -541,7 +541,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if checkFormal(formal) then formal.member(tpnme.MirroredType).info match case TypeBounds(mirroredType, _) => - synth(fullyDefinedType(mirroredType, "Mirror.*Of argument", ctx.source.atSpan(span)), formal, span) + val defined = fullyDefinedType(mirroredType, "Mirror.*Of argument", ctx.source.atSpan(span)) + val stripped = TypeOps.stripTypeVars(defined) + synth(stripped, formal, span) case other => EmptyTreeNoError else EmptyTreeNoError diff --git a/tests/run/i13146poly.scala b/tests/run/i13146poly.scala new file mode 100644 index 000000000000..849f4dc7eb52 --- /dev/null +++ b/tests/run/i13146poly.scala @@ -0,0 +1,19 @@ +import scala.deriving.* + +trait Functor[F[_]] + +object Functor: + given [C]: Functor[[T] =>> C]() + given Functor[[T] =>> Tuple1[T]]() + given t2 [T]: Functor[[U] =>> (T, U)]() + given t3 [T, U]: Functor[[V] =>> (T, U, V)]() + + def derived[F[_]](using m: Mirror { type MirroredType[X] = F[X] ; type MirroredElemTypes[_] }, r: Functor[m.MirroredElemTypes]): Functor[F] = new Functor[F] {} + +case class Mono(i: Int) derives Functor +case class Poly[A](a: A) derives Functor +//case class Poly11[F[_]](fi: F[Int]) derives Functor +case class Poly2[A, B](a: A, b: B) derives Functor +case class Poly3[A, B, C](a: A, b: B, c: C) derives Functor + +@main def Test = ()