A quasiquote
q"""{object MyObject {
def method1() = "m1"
}}
"""
or
reify{
object MyObject {
def method1() = "m1"
}
}.tree
are just ways to write a tree
Block(
List(
ModuleDef(Modifiers(), TermName("MyObject"),
Template(
List(Select(Ident(scala), TypeName("AnyRef"))),
noSelfType,
List(
DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(),
Block(List(pendingSuperCall), Literal(Constant(())))
),
DefDef(Modifiers(), TermName("method1"), List(), List(List()), TypeTree(),
Literal(Constant("m1"))
)
)
)
)
),
Literal(Constant(()))
)
The same can be obtained with context.parse
(compile-time) / toolBox.parse
(runtime) from ordinary String
val str: String =
"""object MyObject {
| def method1() = "m1"
|}""".stripMargin
toolBox.parse(str)
There is compile time of macros and runtime of macros. There is compile time of main code and its runtime. Runtime of macros is compile time of main code.
MyObject
in
object MyObject {
def method1() = "m1"
}
and MyObject
in
q"""{object MyObject {
def method1() = "m1"
}}
"""
exist in different contexts. The former exists in the current context, the latter exists in the context of macro's call site.
You can insert (splice) a tree into a tree. You can not insert actual object into a tree. If you have actual object (compiled tree) it's too late to insert it into a tree.
When you see something being inserted into a tree, this means that "something" is just a compact way to write a tree i.e. an instance of type class Liftable
object MyObject {
def method1() = "m1"
}
implicit val myObjectLiftable: Liftable[MyObject.type] = new Liftable[MyObject.type] {
override def apply(value: MyObject.type): Tree =
q"""
object MyObject {
def method1() = "m1"
}"""
}
q"""
class SomeClass {
$MyObject
}"""
I guess your macro can look like
def foo[A](a: A) = macro impl[A]
or
def foo[A] = macro impl[A]
so you can call it like foo(MyObject)
or foo[MyObject.type]
and inside
def impl[A: c.WeakTypeTag](c: blackbox.Context)...
you have access to weakTypeOf[A]
, then its symbol. Having symbol you can have signatures of methods etc.
Actually, in some sense there is a way to "insert" an object into a quasiquote. This is serialization/deserialization objects between stages
import java.io.FileOutputStream
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
import scala.util.Using
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Output
object MyObject {
def method1() = "m1"
}
def myMacro(): String = macro myMacroImpl
def myMacroImpl(c: blackbox.Context)(): c.Tree = {
import c.universe._
val kryo = new Kryo
kryo.register(classOf[MyObject.type])
Using(new Output(new FileOutputStream("file"))) { output =>
kryo.writeClassAndObject(output, MyObject)
}
val kryoPrefix = q"_root_.com.esotericsoftware.kryo"
q"""
val kryo = new $kryoPrefix.Kryo
kryo.register(classOf[MyObject.type])
_root_.scala.util.Using(new $kryoPrefix.io.Input(new _root_.java.io.FileInputStream("file"))) { input =>
kryo.readClassAndObject(input).asInstanceOf[MyObject.type]
}.get.method1()
"""
}
myMacro()
sbt clean compile run
prints m1
(file file
must exist, this doesn't work in IntelliJ).
scalaVersion := "2.13.10"
libraryDependencies += "com.esotericsoftware" % "kryo" % "5.3.0"
//libraryDependencies += "com.esotericsoftware.kryo" % "kryo5" % "5.3.0"
Similarly you can use @tribbloid's code in splain
case class MyClass(i: Int, s: String)
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import splain.test.AutoLift.SerializingLift
def myMacro(): Any = macro Macros.myMacroImpl
class Macros(val c: whitebox.Context) extends SerializingLift.Mixin {
import c.universe._
def myMacroImpl(): c.Tree = {
q"""
${MyClass(1, "a")}
"""
}
}
val res = Macro.myMacro() //scalac: splain.test.AutoLift.SerializingLift.fromPreviousStage[mypackage.MyClass]("rO0ABXNyAA5hcHAxODEuTXlDbGFzczOlcqPGO50dAgACSQABaUwAAXN0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAAAF0AAFh")
res: MyClass // since the macro is whitebox, it can return more precise type than declared (Any)
println(res) //MyClass(1,a)
scalaVersion := "2.13.10"
libraryDependencies += "io.tryp" % "splain" % "1.0.1" cross CrossVersion.full
Unfortunately this doesn't work with (case) objects because of a bug.
In multi-stage compilation, should we use a standard serialisation method to ship objects through stages?
https://contributors.scala-lang.org/t/in-multi-stage-compilation-should-we-use-a-standard-serialisation-method-to-ship-objects-through-stages/5699
https://github.com/EsotericSoftware/kryo
https://com-lihaoyi.github.io/upickle/#uPack
One more technique to define a macro if a value is from the next stage (and avoid cross-stage evaluation) is to construct a tree of function and then apply this function after macro expansion
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
case class MyClass(i: Int, s: String)
def myMacro(): Any = macro myMacroImpl
def myMacroImpl(c: whitebox.Context)(): c.Tree = {
import c.universe._
q"""
def foo[A](a: A): Unit = println("foo: a=" + a)
foo(_: ${typeOf[MyClass]})
"""
}
val f = myMacro()
//scalac: {
// def foo[A](a: A): Unit = println("foo: a=".$plus(a));
// ((x$1: MyClass) => foo((x$1: MyClass)))
//}
f: (MyClass => Unit) // checking the type
f(MyClass(1, "a"))
//foo: a=MyClass(1,a)
Scala 2.13: Case class with extendable variable attributes?
Invoke a template Scala function with a type stored as wild card classTag?