2

I have the following code and I would like to compile it on the fly and run it.

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("Hello, world!")
  }
}

So far I have tried something like below:

import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox

object MainScala {
  def main(args: Array[String]): Unit = {
    val toolbox = currentMirror.mkToolBox()
    val source =
      """
        |object HelloWorld {
        |  def main(args: Array[String]): Unit = {
        |    println("Hello, world!")
        |  }
        |}
        |""".stripMargin
    val tree = toolbox.parse(source)
    val binary = toolbox.compile(tree)
    var c = binary.getClass
    val m = c.getMethod("main", classOf[Array[String]])
    val params = Array("Apple", "Banana", "Orange")
    m.invoke(null, null)
  }
}

After toolbox.compile(tree) I am not able to get the Class object of the compiled code.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Vishal
  • 19,879
  • 23
  • 80
  • 93

1 Answers1

5

If you look at the type of binary you'll see it's () => Any. So when you ask .getClass you actually get a subclass of Function1, which doesn't have .main.

Toolbox isn't supposed to be used like that.

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/reflect/ToolBox.scala#L120-L129

https://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree-creation-via-parse-on-toolboxes

Try

val source =
  """
    |object HelloWorld {
    |  def main(args: Array[String]): Unit = {
    |    println("Hello, world!")
    |  }
    |}
    |
    |HelloWorld.main(Array())
    |""".stripMargin
val tree = toolbox.parse(source)
val binary = toolbox.compile(tree)
binary() // Hello, world!

or

val params = """Array("Apple", "Banana", "Orange")"""
val source =
  s"""
    |object HelloWorld {
    |  def main(args: Array[String]): Unit = {
    |    println(args.toList)
    |  }
    |}
    |
    |HelloWorld.main($params)
    |""".stripMargin
val tree = toolbox.parse(source)
val binary = toolbox.compile(tree)
binary() // List(Apple, Banana, Orange)

or

import scala.reflect.runtime.universe._
val params = q"""Array("Apple", "Banana", "Orange")"""
val tree =
  q"""
    object HelloWorld {
      def main(args: Array[String]): Unit = {
        println(args.toList)
      }
    }

    HelloWorld.main($params)
    """
val binary = toolbox.compile(tree)
binary() // List(Apple, Banana, Orange)

or

val params = """Array("Apple", "Banana", "Orange")"""
val source =
  """
    |object HelloWorld {
    |  def main(args: Array[String]): Unit = {
    |    println(args.toList)
    |  }
    |}
    |""".stripMargin
val tree = toolbox.parse(source)
val symbol = toolbox.define(tree.asInstanceOf[ImplDef])
val params1 = toolbox.parse(params)
val tree1 = q"$symbol.main($params1)"
val binary = toolbox.compile(tree1)
binary() // List(Apple, Banana, Orange)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Does it support unloading classes? – boggy Oct 01 '22 at 02:50
  • @costa I guess loading/reloading/unloading classes (and ability/not ability to do that) is a function of a class loader ([1](https://stackoverflow.com/questions/148681/unloading-classes-in-java) [2](https://stackoverflow.com/questions/2095974/how-to-unload-an-already-loaded-class-in-java) [3](https://stackoverflow.com/questions/43089729) [4](https://stackoverflow.com/questions/16307149) [5](https://stackoverflow.com/questions/31462279)). Runtime mirror is a wrapper over class loader (it accepts a class loader as an argument) and the mirror creates toolbox. – Dmytro Mitin Oct 01 '22 at 03:23
  • @costa I mean `scala.reflect.runtime.universe.runtimeMirror(classLoader)`. `runtime.currentMirror` is a macro for `this.getClass.getClassLoader`. – Dmytro Mitin Oct 01 '22 at 03:29