Instantiate class symbol using macro - scala

I'm using the following Scala macro (heavily inspired by the code from this SO question) to get a list of all objects contained in a given package that inherit a specific trait:
object Macros {
def allObjects[T <: AnyRef](packageName: String): List[Any] = macro allObjectsImpl[T]
def allObjectsImpl[T <: AnyRef: c.WeakTypeTag](c: Context)(packageName: c.Expr[String]) = {
import c.universe._
val baseTraitSymbol = c.weakTypeOf[T].typeSymbol
val pkg = packageName.tree match {
case Literal(Constant(name: String)) => c.mirror.staticPackage(name)
}
val types = pkg.typeSignature.members.collect {
case moduleSymbol: ModuleSymbol if moduleSymbol.moduleClass.asClass.baseClasses contains baseTraitSymbol => Ident(moduleSymbol)
}.toList
val listApply = Select(reify(List).tree, TermName("apply"))
c.Expr[List[T]](Apply(listApply, types))
}
}
Which works fine.
I want to change the macro so that instead of getting all the objects in a package, it gets all the concrete classes, and provides a list containing an instance of each of them.
The AST when creating an instance of an Object looks like this:
scala> import scala.reflect.runtime.{universe => u}
import scala.reflect.runtime.{universe=>u}
scala> u showRaw ( u reify {new Object} )
res42: String = Expr(Apply(Select(New(Ident(java.lang.Object)), termNames.CONSTRUCTOR), List()))
So I thought changing my code to this would work:
object Macros {
def allInstances[T <: AnyRef](packageName: String): List[Any] = macro allInstancesImpl[T]
def allInstancesImpl[T <: AnyRef: c.WeakTypeTag](c: Context)(packageName: c.Expr[String]) = {
import c.universe._
val baseTraitSymbol = c.weakTypeOf[T].typeSymbol
val pkg = packageName.tree match {
case Literal(Constant(name: String)) => c.mirror.staticPackage(name)
}
def isConcreteChildClass(child: ClassSymbol, base: Symbol) = {
!child.isAbstract && (child.baseClasses contains base)
}
val types = pkg.typeSignature.members.collect {
case classSymbol: ClassSymbol if isConcreteChildClass(classSymbol, baseTraitSymbol) => {
Apply(Select(New(Ident(classSymbol.primaryConstructor)), termNames.CONSTRUCTOR), List())
}
}.toList
val listApply = Select(reify(List).tree, TermName("apply"))
c.Expr[List[T]](Apply(listApply, types))
}
}
However, when I try to use the updated macro code on a test package, I get the following error:
scala> Macros.allInstances[AnyRef]("test")
<console>:9: error: class type required but ()test.TestClass found
Macros.allInstances[AnyRef]("test")
From what I'm seeing, it looks like the macro is actually returning the constructor itself instead of returning the instance that should get built by the constructor, but I can't figure out what I'm missing.

The problem is in this line (reformatted for clarity):
Apply(
Select(New(Ident(classSymbol.primaryConstructor)), termNames.CONSTRUCTOR),
List()
)
You're essentially selecting the constructor twice. You could just drop primaryConstructor:
Apply(
Select(New(Ident(classSymbol)), termNames.CONSTRUCTOR),
List()
)
Using ApplyConstructor would also work:
ApplyConstructor(Ident(classSymbol), Nil)
Or you could just go with quasiquotes:
q"new ${Ident(classSymbol)}()"
The quasiquote solution is the most future-proof.

Related

Scala 3 quotes: get default case class field value [duplicate]

Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)

Type Class Derivation accessing default values

Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)

Scala - Get string representation of object property name, not value, for comparison

I want to be able to get the string representation of an objects property name, not the properties value, so that I can compare it with a variables value inside a conditional statement.
case class CustomObj(name: T)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if(propObj.property.equals(custObj. /* the property name as a String, so "name", not the value ("Chris"*/)) {
// do something
}
How can I access what is essentially the key of the property on the CustomObj?
Try productElementNames like so
case class CustomObj(name: String)
case class PropertyObj(property: String)
val custObj = CustomObj("Chris")
val propObj = PropertyObj("name")
if (custObj.productElementNames.toList.headOption.contains(propObj.property)) { ... } else { ... }
Addressing the comment, based on Krzysztof, try shapeless solution
import shapeless._
import shapeless.ops.record._
def firstPropertyNameOf[P <: Product, L <: HList](p: P)(implicit
gen: LabelledGeneric.Aux[P, L],
toMap: ToMap[L]): Option[String] = {
toMap(gen.to(p)).map{ case (k: Symbol, _) => k.name }.toList.headOption
}
firstPropertyNameOf(custObj).contains(propObj.property) // res1: Boolean = true
I will assume you don't know the type of custObj at compile time. Then you'll have to use runtime reflection in Scala 2.12.
scala> case class CustomObj(name: String)
defined class CustomObj
scala> val custObj: Any = CustomObj("Chris")
custObj: Any = CustomObj(Chris)
scala> import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.currentMirror
scala> val sym = currentMirror.classSymbol(custObj.getClass)
sym: reflect.runtime.universe.ClassSymbol = class CustomObj
scala> val props = sym.info.members.collect{ case m if m.isMethod && m.asMethod.isCaseAccessor => m.name.toString }
props: Iterable[String] = List(name)
scala> if (props.exists(_ == "name")) println("ok")
ok

How to create a Macro to create List of val in a case class?

I am trying to create a Macro to give me a list of val for a specific case class.
object CaseClass {
def valList[T]: List[String] = macro implValList[T]
def implValList[T](c: whitebox.Context): c.Expr[List[String]] = {
import c.universe._
val listApply = Select(reify(List).tree, TermName("apply"))
val vals = weakTypeOf[T].decls.collect {
case m: TermSymbol if m.isVal => q"${m.name}"
}
c.Expr[List[String]](Apply(listApply, vals.toList))
}
}
So given
case class AClass(
val a: String,
val b: Int
)
I want a list of CaseClass.valList[AClass] = List("a", "b")
Not an expert on macros, so take it with a grain of salt. But I tested it with Intellij.
First, to use weakTypeOf you need to take a WeakTypeTag as an implicit in your macro impl like this:
def implValList[T](c: whitebox.Context)(implicit wt: c.WeakTypeTag[T]) ...
Second, to create literals, you use this construct instead of your quasiquote, (which, I believe, actually does nothing):
Literal(Constant(m.name.toString))
Last, I recommend using this guard instead of isVal:
m.isCaseAccessor && m.isGetter
Which is properly checking for case class parameter and also being a getter (case class parameters are duplicated, one as isGetter, other one as isParam). The reason for this being that isVal names for case classes surprisingly produce a name ending in whitespace.
The final implementation that works for me is as follows:
object CaseClass {
def valList[T]: List[String] = macro implValList[T]
def implValList[T](c: whitebox.Context)(implicit wt: c.WeakTypeTag[T]): c.Expr[List[String]] = {
import c.universe._
val listApply = Select(reify(List).tree, TermName("apply"))
val vals = weakTypeOf[T].decls.collect {
case m: TermSymbol if m.isCaseAccessor && m.isGetter => Literal(Constant(m.name.toString))
}
c.Expr[List[String]](Apply(listApply, vals.toList))
}
}
As an alternative (because macros are somewhat of a pain to set up - you cannot use macro in the same subproject that defines it), and you don't need it very often, you might be able to get away with a shapeless one-liner:
import shapeless._
import shapeless.ops.record.Keys
case class Foo(a: Int, b: String)
Keys[the.`LabelledGeneric[Foo]`.Repr].apply().toList.map(_.name) // List("a", "b")

Scala macro and type erasure

I'm having some problems with a macro I've written to help me log metrics represented as case class instances to to InfluxDB. I presume I'm having a type erasure problem and that the tyep parameter T is getting lost, but I'm not entirely sure what's going on. (This is also my first exposure to Scala macros.)
import scala.language.experimental.macros
import play.api.libs.json.{JsNumber, JsString, JsObject, JsArray}
abstract class Metric[T] {
def series: String
def jsFields: JsArray = macro MetricsMacros.jsFields[T]
def jsValues: JsArray = macro MetricsMacros.jsValues[T]
}
object Metrics {
case class LoggedMetric(timestamp: Long, series: String, fields: JsArray, values: JsArray)
case object Kick
def log[T](metric: Metric[T]): Unit = {
println(LoggedMetric(
System.currentTimeMillis,
metric.series,
metric.jsFields,
metric.jsValues
))
}
}
And here's an example metric case class:
case class SessionCountMetric(a: Int, b: String) extends Metric[SessionCountMetric] {
val series = "sessioncount"
}
Here's what happens when I try to log it:
scala> val m = SessionCountMetric(1, "a")
m: com.confabulous.deva.SessionCountMetric = SessionCountMetric(1,a)
scala> Metrics.log(m)
LoggedMetric(1411450638296,sessioncount,[],[])
Even though the macro itself seems to work fine:
scala> m.jsFields
res1: play.api.libs.json.JsArray = ["a","b"]
scala> m.jsValues
res2: play.api.libs.json.JsArray = [1,"a"]
Here's the actual macro itself:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object MetricsMacros {
private def fieldNames[T: c.WeakTypeTag](c: Context)= {
val tpe = c.weakTypeOf[T]
tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
}
def jsFields[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => Literal(Constant(name.toString))).toList
)
}
def jsValues[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => q"${c.prefix.tree}.$name").toList
)
}
}
Update
I tried Eugene's second suggestion like this:
abstract class Metric[T] {
def series: String
}
trait MetricSerializer[T] {
def fields: Seq[String]
def values(metric: T): Seq[Any]
}
object MetricSerializer {
implicit def materializeSerializer[T]: MetricSerializer[T] = macro MetricsMacros.materializeSerializer[T]
}
object Metrics {
def log[T: MetricSerializer](metric: T): Unit = {
val serializer = implicitly[MetricSerializer[T]]
println(serializer.fields)
println(serializer.values(metric))
}
}
with the macro now looking like this:
object MetricsMacros {
def materializeSerializer[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = c.weakTypeOf[T]
val names = tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
val fields = Apply(
q"Seq",
names.map(name => Literal(Constant(name.toString))).toList
)
val values = Apply(
q"Seq",
names.map(name => q"metric.$name").toList
)
q"""
new MetricSerializer[$tpe] {
def fields = $fields
def values(metric: Metric[$tpe]) = $values
}
"""
}
}
However, when I call Metrics.log -- specifically when it calls implicitly[MetricSerializer[T]] I get the following error:
error: value a is not a member of com.confabulous.deva.Metric[com.confabulous.deva.SessionCountMetric]
Why is it trying to use Metric[com.confabulous.deva.SessionCountMetric] instead of SessionCountMetric?
Conclusion
Fixed it.
def values(metric: Metric[$tpe]) = $values
should have been
def values(metric: $tpe) = $values
You're in a situation that's very close to one described in a recent question: scala macros: defer type inference.
As things stand right now, you'll have to turn log into a macro. An alternative would also to turn Metric.jsFields and Metric.jsValues into JsFieldable and JsValuable type classes materialized by implicit macros at callsites of log (http://docs.scala-lang.org/overviews/macros/implicits.html).