Skip to content

Commit 811dbaa

Browse files
committed
Quoted ToExpr derivation macro regression test
1 parent 220625f commit 811dbaa

File tree

5 files changed

+200
-0
lines changed

5 files changed

+200
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import scala.compiletime.{erasedValue, summonFrom}
2+
import scala.deriving._
3+
import scala.quoted._
4+
5+
object ToExprMaker {
6+
7+
inline given derived[T](using inline m: Mirror.Of[T]): ToExpr[T] = ${ derivedExpr('m) }
8+
9+
private def derivedExpr[T](mirrorExpr: Expr[Mirror.Of[T]])(using Quotes, Type[T]): Expr[ToExpr[T]] = {
10+
val tpe = summonExprOrError[Type[T]]
11+
mirrorExpr match {
12+
case '{ $mirrorExpr : Mirror.Sum { type MirroredElemTypes = mirroredElemTypes } } =>
13+
val liftables = elemTypesToExprs[mirroredElemTypes]
14+
val liftablesFn = '{ (x: Int) => ${ switchExpr('x, liftables) } }
15+
'{ new SumToExpr[T, mirroredElemTypes]($mirrorExpr, $liftablesFn)(using $tpe) }
16+
case '{ $mirrorExpr : Mirror.Product { type MirroredElemTypes = mirroredElemTypes } } =>
17+
val liftableExprs = Expr.ofSeq(elemTypesToExprs[mirroredElemTypes])
18+
'{ new ProductToExpr[T, mirroredElemTypes]($mirrorExpr, $liftableExprs)(using $tpe) }
19+
}
20+
}
21+
22+
// TODO hide from users
23+
class SumToExpr[T, MElemTypes](
24+
mirror: Mirror.Sum { type MirroredElemTypes = MElemTypes; type MirroredMonoType = T },
25+
liftables: Int => ToExpr[_]
26+
)(using Type[T]) extends ToExpr[T]:
27+
def apply(x: T)(using Quotes): Expr[T] =
28+
val ordinal = mirror.ordinal(x)
29+
val liftable = liftables.apply(ordinal).asInstanceOf[ToExpr[T]]
30+
liftable.apply(x)
31+
end SumToExpr
32+
33+
// TODO hide from users
34+
class ProductToExpr[T, MElemTypes](
35+
mirror: Mirror.Product { type MirroredElemTypes = MElemTypes; type MirroredMonoType = T },
36+
liftables: Seq[ToExpr[_]]
37+
)(using Type[T]) extends ToExpr[T]:
38+
def apply(x: T)(using Quotes): Expr[T] =
39+
val mirrorExpr = summonExprOrError[Mirror.ProductOf[T]]
40+
val xProduct = x.asInstanceOf[Product]
41+
val anyToExprs = liftables.asInstanceOf[Seq[ToExpr[Any]]]
42+
val elemExprs =
43+
xProduct.productIterator.zip(anyToExprs.iterator).map {
44+
(elem, lift) => lift(elem)
45+
}.toSeq
46+
val elemsTupleExpr = Expr.ofTupleFromSeq(elemExprs)
47+
'{ $mirrorExpr.fromProduct($elemsTupleExpr) }
48+
end ProductToExpr
49+
50+
private def elemTypesToExprs[X: Type](using Quotes): List[Expr[ToExpr[_]]] =
51+
Type.of[X] match
52+
case '[ head *: tail ] => summonExprOrError[ToExpr[head]] :: elemTypesToExprs[tail]
53+
case '[ EmptyTuple ] => Nil
54+
55+
private def elemType[X: Type](ordinal: Int)(using Quotes): Type[_] =
56+
Type.of[X] match
57+
case '[ head *: tail ] =>
58+
if ordinal == 0 then Type.of[head]
59+
else elemType[tail](ordinal - 1)
60+
61+
private def summonExprOrError[T: Type](using Quotes): Expr[T] =
62+
Expr.summon[T] match
63+
case Some(expr) => expr
64+
case None =>
65+
quotes.reflect.report.throwError(s"Could not find implicit ${Type.show[T]}")
66+
67+
private def switchExpr(scrutinee: Expr[Int], seq: List[Expr[ToExpr[_]]])(using Quotes): Expr[ToExpr[_]] =
68+
import quotes.reflect._
69+
val cases = seq.zipWithIndex.map {
70+
(expr, i) => CaseDef(Literal(IntConstant(i)), None, expr.asTerm)
71+
}
72+
Match(scrutinee.asTerm, cases).asExprOf[ToExpr[_]]
73+
74+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import scala.quoted._
2+
3+
object LibA {
4+
5+
// Requires explicit inline given definition
6+
// Advantage: simple definition of the extension (slightly smaller than quoted-ToExpr-derivation-macro-2)
7+
// Drawback: the derived code will be duplicated at each use site
8+
9+
sealed trait Opt[+T]
10+
object Opt:
11+
inline given [T]: ToExpr[Opt[T]] = ToExprMaker.derived
12+
13+
case class Sm[T](t: T) extends Opt[T]
14+
object Sm:
15+
inline given [T]: ToExpr[Sm[T]] = ToExprMaker.derived
16+
17+
case object Nn extends Opt[Nothing]:
18+
inline given ToExpr[Nn.type] = ToExprMaker.derived
19+
20+
import Opt.*
21+
22+
inline def optTwo = ${optTwoExpr}
23+
inline def smTwo = ${smTwoExpr}
24+
inline def none = ${noneExpr}
25+
26+
private def optTwoExpr(using Quotes): Expr[Opt[Int]] =
27+
summon[ToExpr[Opt[Int]]].apply(Sm(2))
28+
29+
private def smTwoExpr(using Quotes): Expr[Sm[Int]] =
30+
summon[ToExpr[Sm[Int]]].apply(Sm(2))
31+
32+
private def noneExpr(using Quotes): Expr[Opt[Int]] =
33+
summon[ToExpr[Nn.type]].apply(Nn)
34+
}
35+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import scala.quoted._
2+
3+
object LibB {
4+
5+
// Requires explicit given definition
6+
// Advantage: No duplication of code
7+
// Drawback: Need to explicitly add the Quotes, Type and ToExpr parameters (not too bad)
8+
9+
sealed trait Opt[+T]
10+
object Opt:
11+
given [T: Type: ToExpr](using Quotes): ToExpr[Opt[T]] = ToExprMaker.derived
12+
13+
case class Sm[T](t: T) extends Opt[T]
14+
object Sm:
15+
given [T: Type: ToExpr](using Quotes): ToExpr[Sm[T]] = ToExprMaker.derived
16+
17+
case object Nn extends Opt[Nothing]:
18+
given (using Quotes): ToExpr[Nn.type] = ToExprMaker.derived
19+
20+
import Opt.*
21+
22+
inline def optTwo = ${optTwoExpr}
23+
inline def smTwo = ${smTwoExpr}
24+
inline def none = ${noneExpr}
25+
26+
private def optTwoExpr(using Quotes): Expr[Opt[Int]] =
27+
summon[ToExpr[Opt[Int]]].apply(Sm(2))
28+
29+
private def smTwoExpr(using Quotes): Expr[Sm[Int]] =
30+
summon[ToExpr[Sm[Int]]].apply(Sm(2))
31+
32+
private def noneExpr(using Quotes): Expr[Opt[Int]] =
33+
summon[ToExpr[Nn.type]].apply(Nn)
34+
}
35+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import scala.quoted._
2+
3+
object LibC {
4+
5+
// Requires explicit given definition
6+
// Advantage: No duplication of code
7+
// Drawback: Need to explicitly add the Quotes, Type and ToExpr parameters (not too bad)
8+
9+
enum Opt[+T]:
10+
case Sm[+T](x: T) extends Opt[T]
11+
case Nn extends Opt[Nothing]
12+
object Opt:
13+
given [T: Type: ToExpr](using Quotes): ToExpr[Opt[T]] = ToExprMaker.derived
14+
given [T: Type: ToExpr](using Quotes): ToExpr[Sm[T]] = ToExprMaker.derived
15+
given (using Quotes): ToExpr[Nn.type] = ToExprMaker.derived
16+
17+
import Opt.*
18+
19+
inline def optTwo = ${optTwoExpr}
20+
inline def smTwo = ${smTwoExpr}
21+
inline def none = ${noneExpr}
22+
23+
private def optTwoExpr(using Quotes): Expr[Opt[Int]] =
24+
summon[ToExpr[Opt[Int]]].apply(Sm(2))
25+
26+
private def smTwoExpr(using Quotes): Expr[Sm[Int]] =
27+
summon[ToExpr[Sm[Int]]].apply(Sm(2))
28+
29+
private def noneExpr(using Quotes): Expr[Opt[Int]] =
30+
summon[ToExpr[Nn.type]].apply(Nn)
31+
}
32+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object Test extends App {
2+
{
3+
import LibA._
4+
assert(optTwo == Sm(2))
5+
assert(smTwo == Sm(2))
6+
assert(none == Nn)
7+
}
8+
9+
{
10+
import LibB._
11+
assert(optTwo == Sm(2))
12+
assert(smTwo == Sm(2))
13+
assert(none == Nn)
14+
}
15+
16+
17+
{
18+
import LibC._
19+
import Opt._
20+
assert(optTwo == Sm(2))
21+
assert(smTwo == Sm(2))
22+
assert(none == Nn)
23+
}
24+
}

0 commit comments

Comments
 (0)