How to runtime compile with reflection for a class being used within another class

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.

Upvotes: 0

Views: 122

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51648

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()
  """
)

Upvotes: 1

Related Questions