Skip to content

Commit 5dee30b

Browse files
authored
Merge pull request #15814 from dotty-staging/fix-13146
Use fullyDefinedType for mirror synthesis
2 parents 6a84737 + 9a9c40d commit 5dee30b

File tree

4 files changed

+171
-1
lines changed

4 files changed

+171
-1
lines changed

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
540540
(using Context): TreeWithErrors =
541541
if checkFormal(formal) then
542542
formal.member(tpnme.MirroredType).info match
543-
case TypeBounds(mirroredType, _) => synth(TypeOps.stripTypeVars(mirroredType), formal, span)
543+
case TypeBounds(mirroredType, _) =>
544+
val defined = fullyDefinedType(mirroredType, "Mirror.*Of argument", ctx.source.atSpan(span))
545+
val stripped = TypeOps.stripTypeVars(defined)
546+
synth(stripped, formal, span)
544547
case other => EmptyTreeNoError
545548
else EmptyTreeNoError
546549

tests/run/i13146.scala

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import scala.deriving.*
2+
import scala.compiletime.{erasedValue, summonInline}
3+
4+
inline def summonAll[T <: Tuple]: List[Eq[_]] =
5+
inline erasedValue[T] match
6+
case _: EmptyTuple => Nil
7+
case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts]
8+
9+
trait Eq[-T]:
10+
def eqv(x: T, y: T): Boolean
11+
12+
object Eq:
13+
given Eq[Int] with
14+
def eqv(x: Int, y: Int) = x == y
15+
16+
def check(elem: Eq[_])(x: Any, y: Any): Boolean =
17+
elem.asInstanceOf[Eq[Any]].eqv(x, y)
18+
19+
def iterator[T](p: T) = p.asInstanceOf[Product].productIterator
20+
21+
def eqSum[T](s: Mirror.SumOf[T], elems: => List[Eq[_]]): Eq[T] =
22+
new Eq[T]:
23+
def eqv(x: T, y: T): Boolean =
24+
val ordx = s.ordinal(x)
25+
(s.ordinal(y) == ordx) && check(elems(ordx))(x, y)
26+
27+
def eqProduct[T](p: Mirror.ProductOf[T], elems: => List[Eq[_]]): Eq[T] =
28+
new Eq[T]:
29+
def eqv(x: T, y: T): Boolean =
30+
iterator(x).zip(iterator(y)).zip(elems.iterator).forall {
31+
case ((x, y), elem) => check(elem)(x, y)
32+
}
33+
34+
inline given derived[T](using m: Mirror.Of[T]): Eq[T] =
35+
lazy val elemInstances = summonAll[m.MirroredElemTypes]
36+
inline m match
37+
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
38+
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
39+
end Eq
40+
41+
enum Opt[+T]:
42+
case Sm(t: T)
43+
case Nn
44+
45+
object Opt:
46+
given derivedEq[T]: Eq[Opt[T]] = Eq.derived
47+
48+
@main def Test(): Unit =
49+
import Opt.*
50+
val eqoi = summon[Eq[Opt[Int]]]
51+
// assert(eqoi.eqv(Sm(23), Sm(23))) -> eqoi.eqv makes an infinite loop
52+
// assert(!eqoi.eqv(Sm(23), Sm(13))) -> eqoi.eqv makes an infinite loop
53+
// assert(!eqoi.eqv(Sm(23), Nn)) -> eqoi.eqv makes an infinite loop

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))))

tests/run/i13146poly.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.deriving.*
2+
3+
trait Functor[F[_]]
4+
5+
object Functor:
6+
given [C]: Functor[[T] =>> C]()
7+
given Functor[[T] =>> Tuple1[T]]()
8+
given t2 [T]: Functor[[U] =>> (T, U)]()
9+
given t3 [T, U]: Functor[[V] =>> (T, U, V)]()
10+
11+
def derived[F[_]](using m: Mirror { type MirroredType[X] = F[X] ; type MirroredElemTypes[_] }, r: Functor[m.MirroredElemTypes]): Functor[F] = new Functor[F] {}
12+
13+
case class Mono(i: Int) derives Functor
14+
case class Poly[A](a: A) derives Functor
15+
//case class Poly11[F[_]](fi: F[Int]) derives Functor
16+
case class Poly2[A, B](a: A, b: B) derives Functor
17+
case class Poly3[A, B, C](a: A, b: B, c: C) derives Functor
18+
19+
@main def Test = ()

0 commit comments

Comments
 (0)