Skip to content

Commit 8029713

Browse files
Merge pull request #11647 from dotty-staging/add-quoted-liftable-derivation-macro-2
Quoted liftable derivation macro regression test
2 parents 93718bd + 811dbaa commit 8029713

File tree

8 files changed

+314
-0
lines changed

8 files changed

+314
-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+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import scala.compiletime.{erasedValue, summonFrom}
2+
import scala.deriving._
3+
import scala.quoted._
4+
5+
trait Lft[T]:
6+
def toExpr(x: T)(using Quotes): Expr[T]
7+
8+
object Lft {
9+
given Lft[Int] with
10+
def toExpr(x: Int)(using Quotes) = Expr(x)
11+
12+
inline given derived[T](using inline m: Mirror.Of[T]): Lft[T] = ${ derivedExpr('m) }
13+
14+
private def derivedExpr[T](mirrorExpr: Expr[Mirror.Of[T]])(using Quotes, Type[T]): Expr[Lft[T]] = {
15+
val tpe = summonExprOrError[Type[T]]
16+
mirrorExpr match {
17+
case '{ $mirrorExpr : Mirror.Sum { type MirroredElemTypes = mirroredElemTypes } } =>
18+
val liftables = elemTypesLfts[mirroredElemTypes]
19+
val liftablesFn = '{ (x: Int) => ${ switchExpr('x, liftables) } }
20+
'{ new LiftableSum[T, mirroredElemTypes]($mirrorExpr, $liftablesFn)(using $tpe) }
21+
case '{ $mirrorExpr : Mirror.Product { type MirroredElemTypes = mirroredElemTypes } } =>
22+
val liftableExprs = Expr.ofSeq(elemTypesLfts[mirroredElemTypes])
23+
'{ new LiftableProduct[T, mirroredElemTypes]($mirrorExpr, $liftableExprs)(using $tpe) }
24+
}
25+
}
26+
27+
class LiftableSum[T, MElemTypes](
28+
mirror: Mirror.Sum { type MirroredElemTypes = MElemTypes; type MirroredMonoType = T },
29+
liftables: Int => Lft[_]
30+
)(using Type[T]) extends Lft[T]:
31+
def toExpr(x: T)(using Quotes): Expr[T] =
32+
val ordinal = mirror.ordinal(x)
33+
val liftable = liftables.apply(ordinal).asInstanceOf[Lft[T]]
34+
liftable.toExpr(x)
35+
end LiftableSum
36+
37+
class LiftableProduct[T, MElemTypes](
38+
mirror: Mirror.Product { type MirroredElemTypes = MElemTypes; type MirroredMonoType = T },
39+
liftables: Seq[Lft[_]]
40+
)(using Type[T]) extends Lft[T]:
41+
def toExpr(x: T)(using Quotes): Expr[T] =
42+
val mirrorExpr = summonExprOrError[Mirror.ProductOf[T]]
43+
val xProduct = x.asInstanceOf[Product]
44+
val anyLiftables = liftables.asInstanceOf[Seq[Lft[Any]]]
45+
val elemExprs =
46+
xProduct.productIterator.zip(anyLiftables.iterator).map {
47+
(elem, lift) => lift.toExpr(elem)
48+
}.toSeq
49+
val elemsTupleExpr = Expr.ofTupleFromSeq(elemExprs)
50+
'{ $mirrorExpr.fromProduct($elemsTupleExpr) }
51+
end LiftableProduct
52+
53+
private def elemTypesLfts[X: Type](using Quotes): List[Expr[Lft[_]]] =
54+
Type.of[X] match
55+
case '[ head *: tail ] => summonExprOrError[Lft[head]] :: elemTypesLfts[tail]
56+
case '[ EmptyTuple ] => Nil
57+
58+
private def elemType[X: Type](ordinal: Int)(using Quotes): Type[_] =
59+
Type.of[X] match
60+
case '[ head *: tail ] =>
61+
if ordinal == 0 then Type.of[head]
62+
else elemType[tail](ordinal - 1)
63+
64+
private def summonExprOrError[T: Type](using Quotes): Expr[T] =
65+
Expr.summon[T] match
66+
case Some(expr) => expr
67+
case None =>
68+
quotes.reflect.report.throwError(s"Could not find implicit ${Type.show[T]}")
69+
70+
private def switchExpr(scrutinee: Expr[Int], seq: List[Expr[Lft[_]]])(using Quotes): Expr[Lft[_]] =
71+
import quotes.reflect._
72+
val cases = seq.zipWithIndex.map {
73+
(expr, i) => CaseDef(Literal(IntConstant(i)), None, expr.asTerm)
74+
}
75+
Match(scrutinee.asTerm, cases).asExprOf[Lft[_]]
76+
77+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
sealed trait Opt[+T]
3+
object Opt:
4+
inline given [T]: Lft[Opt[T]] = Lft.derived
5+
6+
case class Sm[T](t: T) extends Opt[T]
7+
object Sm:
8+
inline given [T]: Lft[Sm[T]] = Lft.derived
9+
10+
case object Nn extends Opt[Nothing]:
11+
inline given Lft[Nn.type] = Lft.derived
12+
13+
object Lib {
14+
15+
import scala.quoted._
16+
import Opt.*
17+
18+
inline def optTwo = ${optTwoExpr}
19+
inline def smTwo = ${smTwoExpr}
20+
inline def none = ${noneExpr}
21+
22+
private def optTwoExpr(using Quotes): Expr[Opt[Int]] =
23+
summon[Lft[Opt[Int]]].toExpr(Sm(2))
24+
25+
private def smTwoExpr(using Quotes): Expr[Sm[Int]] =
26+
summon[Lft[Sm[Int]]].toExpr(Sm(2))
27+
28+
private def noneExpr(using Quotes): Expr[Opt[Int]] =
29+
summon[Lft[Nn.type]].toExpr(Nn)
30+
}
31+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test extends App {
2+
import Opt._
3+
assert(Lib.optTwo == Sm(2))
4+
assert(Lib.smTwo == Sm(2))
5+
assert(Lib.none == Nn)
6+
}

0 commit comments

Comments
 (0)