Skip to content

Liftable derivation #7327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class ReifyQuotes extends MacroTransform {
private def addTags(expr: Tree)(implicit ctx: Context): Tree = {

def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = {
val splicedTree = tpd.ref(spliced)
val splicedTree = tpd.ref(spliced).withSpan(spliced.termSymbol.coord.toSpan)
val rhs = transform(splicedTree.select(tpnme.splice))
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs)
val local = ctx.newSymbol(
Expand Down Expand Up @@ -328,7 +328,7 @@ class ReifyQuotes extends MacroTransform {
}
)
}
/* Lambdas are generated outside the quote that is beeing reified (i.e. in outer.owner).
/* Lambdas are generated outside the quote that is being reified (i.e. in outer.owner).
* In case the case that level == -1 the code is not in a quote, it is in an inline method,
* hence we should take that as owner directly.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class YCheckPositions extends Phases.Phase {
if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && ctx.typerState.isGlobalCommittable)
if (!tree.isType) { // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should.
val currentSource = sources.head
assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource")
// assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource")
}

// Recursivlely check children while keeping track of current source
Expand Down
24 changes: 24 additions & 0 deletions library/src-bootstrapped/scala/quoted/Liftable.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scala.quoted

import scala.reflect.ClassTag
import scala.deriving._

/** A typeclass for types that can be turned to `quoted.Expr[T]`
* without going through an explicit `'{...}` operation.
Expand Down Expand Up @@ -305,4 +306,27 @@ object Liftable {
'{ BigDecimal(${Expr(x.toString)}) }
}

inline given productLiftable[T <: Product: Type](given
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid duplication of most of this logic you can split it into

inline given [T <: Product: Type](given
    m : Mirror.ProductOf[T],
    em: Expr[Mirror.ProductOf[T]]): Liftable[T] =
      productLiftable(given m, em)(summonAll[Tuple.Map[m.MirroredElemTypes, Liftable]])

private def productLiftable[T <: Product: Type](given
    m : Mirror.ProductOf[T],
    em: Expr[Mirror.ProductOf[T]])(liftables: List[Any]): Liftable[T] = new Liftable[T] {
  def toExpr(x: T) =
    val genRepr = Tuple.fromProductTyped(x)
    val elemsWithLiftables = liftables.zip(genRepr.asInstanceOf[Product].productIterator.toList)
    val tupleOfExprs = elemsWithLiftables.map {
      case (l: Liftable[a], x) => l.toExpr(x.asInstanceOf[a])
    }
    val exprOfTuple = Expr.ofTuple(tupleOfExprs)
    '{$em.fromProduct($exprOfTuple.asInstanceOf[Product])}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid inlining too much

m : Mirror.ProductOf[T],
em: Expr[Mirror.ProductOf[T]]): Liftable[T] = new Liftable[T] {
def toExpr(x: T) =
val genRepr = Tuple.fromProductTyped(x)
val liftables = summonAll[Tuple.Map[m.MirroredElemTypes, Liftable]]
val elemsWithLiftables = liftables.zip(genRepr.asInstanceOf[Product].productIterator.toList)
val tupleOfExprs = elemsWithLiftables.map {
case (l: Liftable[a], x) => l.toExpr(x.asInstanceOf[a])
}
val exprOfTuple = Expr.ofTuple(tupleOfExprs)
'{$em.fromProduct($exprOfTuple.asInstanceOf[Product])}
}

inline given sumLiftable[T: Type](given m: Mirror.SumOf[T]): Liftable[T] = new Liftable[T] {
def toExpr(x: T) =
val liftables = summonAll[Tuple.Map[m.MirroredElemTypes, Liftable]]
val tags = summonAll[Tuple.Map[m.MirroredElemTypes, ClassTag]]
tags.zip(liftables).flatMap { case (t: ClassTag[a], l: Liftable[?]) =>
t.unapply(x).map(xa => l.asInstanceOf[Liftable[a]].toExpr(xa)) }
.head.asInstanceOf[Expr[T]]
}

}
8 changes: 7 additions & 1 deletion library/src/dotty/DottyPredef.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dotty

object DottyPredef {
import compiletime.summonFrom
import compiletime.{ summonFrom, erasedValue }

@forceInline final def assert(assertion: => Boolean, message: => Any): Unit = {
if (!assertion)
Expand Down Expand Up @@ -38,4 +38,10 @@ object DottyPredef {
}

inline def summon[T](given x: T): x.type = x

inline def summonInline[T] = summonFrom { case x: T => x }

inline def summonAll[T <: Tuple]: List[?] = inline erasedValue[T] match
case _: Unit => Nil
case _: (t *: ts) => summonInline[t] :: summonAll[ts]
}
7 changes: 7 additions & 0 deletions tests/pos-macros/i7322/Macros_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import scala.quoted.{ QuoteContext, Expr, Type }

trait M[T] {
def f: Any
}

inline def g[T: Type](em: Expr[M[T]])(given QuoteContext) = '{$em.f}
3 changes: 3 additions & 0 deletions tests/pos-macros/i7322/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import scala.quoted.{ QuoteContext, Expr }

def h(m: Expr[M[String]])(given QuoteContext): Expr[Any] = g(m)
27 changes: 27 additions & 0 deletions tests/pos-macros/liftable-derivation/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import scala.quoted._, scala.deriving._
import scala.quoted.given

import scala.reflect.ClassTag

import Tuple.{ Head, Tail }
import scala.compiletime.{ erasedValue, summonFrom }


inline def mcr(given m: Mirror.ProductOf[Foo], m2: Mirror.ProductOf[Bar], m3: Mirror.ProductOf[Stuff.FooS], m4: Mirror.ProductOf[Stuff.BarS]): Any = ${mcrImpl(given 'm, 'm2, 'm3, 'm4)}
def mcrImpl(given m: Expr[Mirror.ProductOf[Foo]], m2: Expr[Mirror.ProductOf[Bar]], m3: Expr[Mirror.ProductOf[Stuff.FooS]], m4: Expr[Mirror.ProductOf[Stuff.BarS]])(given ctx: QuoteContext): Expr[Any] =
val x = Foo(1, "foo")
val y: Stuff = Stuff.FooS(10)
val z: A = x
val e1 = Expr(x)
val e2 = Expr(y)
val e3 = Expr(z)
'{List($e1, $e2, $e3)}

sealed trait A
case class Foo(x: Int, y: String) extends A
case class Bar(a: String, b: Double) extends A

enum Stuff {
case FooS(x: Int)
case BarS(y: String)
}
1 change: 1 addition & 0 deletions tests/pos-macros/liftable-derivation/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def Test = println(mcr)