Skip to content

Commit be9e2bc

Browse files
add test with type-class derivation of migration for case classes
1 parent bbf0f0c commit be9e2bc

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import scala.deriving.*
2+
import scala.Tuple.*
3+
import scala.compiletime.*
4+
import scala.compiletime.ops.int.S
5+
6+
trait Migration[From, To]:
7+
def apply(x: From): To
8+
9+
object Migration:
10+
11+
extension [From](x: From)
12+
def migrateTo[To](using m: Migration[From, To]): To = m(x)
13+
14+
given[T]: Migration[T, T] with
15+
override def apply(x: T): T = x
16+
17+
private type IndexOf[Elems <: Tuple, X] <: Int = Elems match {
18+
case (elem *: elems) =>
19+
elem match {
20+
case X => 0
21+
case _ => S[IndexOf[elems, X]]
22+
}
23+
case EmptyTuple => Nothing
24+
}
25+
26+
private inline def migrate[F,T](x: F): T = summonFrom {
27+
case migration: Migration[F,T] => migration(x)
28+
}
29+
30+
private inline def migrateElem[F,T, ToIdx <: Int]
31+
(from: Mirror.ProductOf[F], to: Mirror.ProductOf[T])
32+
(x: Product): Any =
33+
type Label = Elem[to.MirroredElemLabels, ToIdx]
34+
type FromIdx = IndexOf[from.MirroredElemLabels, Label]
35+
inline constValueOpt[FromIdx] match
36+
case Some(fromIdx) =>
37+
type FromType = Elem[from.MirroredElemTypes, FromIdx]
38+
type ToType = Elem[to.MirroredElemTypes, ToIdx]
39+
val elem = x.productElement(fromIdx).asInstanceOf[FromType]
40+
migrate[FromType, ToType](elem)
41+
case None =>
42+
type HasDefault = Elem[to.MirroredElemHasDefaults, ToIdx] // NOTE when are the annotations checked ?? bug expr is not checked for match types
43+
inline erasedValue[HasDefault] match
44+
case _: true => to.defaultArgument(constValue[ToIdx])
45+
case _: false => compiletime.error("An element has no equivalent in source or default")
46+
end migrateElem
47+
48+
private inline def migrateElems[F,T, ToIdx <: Int]
49+
(from: Mirror.ProductOf[F], to: Mirror.ProductOf[T])
50+
(x: Product): Seq[Any] =
51+
inline erasedValue[ToIdx] match
52+
case _: Tuple.Size[to.MirroredElemLabels] => Seq()
53+
case _ => migrateElem[F,T,ToIdx](from, to)(x) +: migrateElems[F,T,S[ToIdx]](from, to)(x)
54+
55+
private inline def migrateProduct[F,T](from: Mirror.ProductOf[F], to: Mirror.ProductOf[T])
56+
(x: Product): T =
57+
val elems = migrateElems[F,T,0](from, to)(x)
58+
to.fromProduct(new Product:
59+
override def canEqual(that: Any): Boolean = false
60+
override def productArity: Int = x.productArity
61+
override def productElement(n: Int): Any = elems(n)
62+
)
63+
64+
implicit inline def migration[F,T](using from: Mirror.Of[F], to: Mirror.Of[T]): Migration[F,T] =
65+
new Migration[F,T]:
66+
override def apply(x: F): T = inline from match
67+
case fromP: Mirror.ProductOf[F] => inline to match
68+
case _: Mirror.SumOf[T] => compiletime.error("Can not migrate a product to a sum")
69+
case toP: Mirror.ProductOf[T] => migrateProduct[F, T](fromP, toP)(x.asInstanceOf[Product])
70+
71+
end Migration
72+
73+
import Migration.*
74+
object Test extends App:
75+
76+
case class A1(x: Int)
77+
case class A2(x: Int)
78+
given Migration[A1, A2] = migration
79+
assert(A1(2).migrateTo[A2] == A2(2))
80+
81+
case class B1(x: Int, y: String)
82+
case class B2(y: String, x: Int)
83+
given Migration[B1, B2] = migration
84+
assert(B1(5, "hi").migrateTo[B2] == B2("hi", 5))
85+
86+
case class C1(x: A1)
87+
case class C2(x: A2)
88+
given Migration[C1, C2] = migration
89+
assert(C1(A1(0)).migrateTo[C2] == C2(A2(0)))
90+
91+
case class D1(x: Double)
92+
case class D2(b: Boolean = true, x: Double)
93+
given Migration[D1, D2] = migration
94+
assert(D1(9).migrateTo[D2] == D2(true, 9))
95+
96+
case class E1(x: D1, y: D1)
97+
case class E2(y: D2, s: String = "hi", x: D2)
98+
given Migration[E1, E2] = migration
99+
assert(E1(D1(1), D1(2)).migrateTo[E2] == E2(D2(true, 2), "hi", D2(true, 1)))
100+
101+
case class F1(x: Int)
102+
case class F2(x: Int = 3)
103+
given Migration[F1, F2] = migration
104+
assert(F1(7).migrateTo[F2] == F2(7))
105+

0 commit comments

Comments
 (0)