My code:
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
object Stuff {
val rm: universe.Mirror = runtime.currentMirror
val tb: ToolBox[universe.type] = rm.mkToolBox()
val Example1 = tq"namespace.to.Example1"
val Example2 = tq"namespace.to.Example2"
val classDef = tb
.define(
q"""
case class MyClass extends $Example1 { //
def doWork(): $Example2 = {
Example2("hello") // <-- I believe this is the offending line
}
}
""".asInstanceOf[ClassDef]
).asClass
}
where Example2
:
case class Example2(member: String)
I run into this error:
Cause: scala.tools.reflect.ToolBoxError: reflective compilation has failed:
not found: value Example2
I've tried this too:
def doWork(): $Example2 = {
$Example2("hello")
}
but that runs into:
class namespace.to.Example2 is not a value
Using $Example2
as the type of the method works, but how do I make the reflection understand creating an instance of Example2
?
EDIT:
How would I also reference Example2
if it were another class that I had to compile at runtime? If I want this at runtime without having a namespace.to.Example2
(class doesn't exist)
case class Example2(member: String) {
def aMe: Example2 = {
// do any work
}
}
object Stuff {
val rm: universe.Mirror = runtime.currentMirror
val tb: ToolBox[universe.type] = rm.mkToolBox()
val classDef = tb
.define(
q"""
case class MyClass {
def doWork(): $Example2 = {
val ex = new $Example2("hello") // <-- this would not work?
ex.aMethod()
}
}
}
Because there is no val Example2 = tq"namespace.to.Example2"
, the new Example2("hello")
probably doesn't work.
Use splicing (${...}
) and either the constructor of case class
val Example1T = tq"namespace.to.Example1"
val Example2T = tq"namespace.to.Example2"
val classSym = tb.define(
q"""
case class MyClass() extends $Example1T {
def doWork(): $Example2T = {
new $Example2T("hello")
}
}
""".asInstanceOf[ClassDef]
).asClass
or apply
method of companion object
val Example1T = tq"namespace.to.Example1"
val Example2T = tq"namespace.to.Example2"
val Example2ObjT = q"namespace.to.Example2"
val classSym = tb.define(
q"""
case class MyClass() extends $Example1T {
def doWork(): $Example2T = {
$Example2ObjT("hello")
}
}
""".asInstanceOf[ClassDef]
).asClass
The string interpolator tq"..."
creates a type tree, q"..."
creates a term tree
https://docs.scala-lang.org/overviews/quasiquotes/intro.html
If Example1
and Example2
are ordinary classes (not runtime-generated) you can use them statically
// val Example1T = typeOf[Example1]
val Example1T = symbolOf[Example1]
// val Example2T = typeOf[Example2]
val Example2T = symbolOf[Example2]
// val Example2ObjT = symbolOf[Example2.type].asClass.module
val Example2ObjT = Example2T.companion
Normally you can use imports inside quasiquotes with tb.eval
, tb.compile
, tb.typecheck
q"""
import namespace.to._
new Example2("hello")
Example2("hello")
"""
but not with tb.define
because it accepts ClassDef
/ModuleDef
(<: ImplDef <: Tree
) rather than arbitrary Tree
and adding imports makes a tree a Block
(<: Tree
).
How would I also reference
Example2
if it were another class that I had to compile at runtime?
Make use of the class symbol returned by tb.define
on the previous step (you can splice trees, symbols, types into trees)
val example2ClassSym = tb.define(
q"""
case class Example2(member: String) {
def aMethod(): Example2 = {
Example2("aaa")
}
}
""".asInstanceOf[ClassDef]
).asClass
val myClassSym = tb.define(
q"""
case class MyClass() {
def doWork(): $example2ClassSym = {
val ex = new $example2ClassSym("hello")
ex.aMethod()
}
}
""".asInstanceOf[ClassDef]
).asClass
Or if this is enough, just
tb.eval(
q"""
val ex = new $example2ClassSym("hello")
ex.aMethod()
"""
)
tb.eval(
q"""
import ${example2ClassSym.owner.asClass.module}._
val ex = new Example2("hello")
ex.aMethod()
"""
)