Scala reflection: compile akka actor with protobuf
Asked Answered
E

1

0

I am trying to compile an Actor with DynamicMessage with Scala Reflection Toolbox

The actor code looks like

import scala.reflect.runtime.universe
import scala.tools.reflect.ToolBox
val toolbox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val actorCode =
  """
    |import akka.actor._
    |import com.google.protobuf._
    |class SimpleActor extends Actor {
    |override def receive: Receive = {
    | case dynamicMessage: DynamicMessage => println("Dynamic message received!")
    | case _  => println("Whatever!")  // the default, catch-all
    |  }
    |}
    |object SimpleActor {
    |def props() : Props = Props(new SimpleActor())
    |}
    |
    |
    |return SimpleActor.props()
    |""".stripMargin
val tree = toolbox.parse(actorCode)
toolbox.compile(tree)().asInstanceOf[Props]

I get the error

reflective compilation has failed:

illegal cyclic reference involving type T
scala.tools.reflect.ToolBoxError: reflective compilation has failed:

illegal cyclic reference involving type T

If I run the code outside of the Toolbox it compiles and works fine. The error is given from the line

case dynamicMessage: DynamicMessage => println("Dynamic message received!")

Anyone knows the nature of this error and how to fix it?

Enright answered 1/3, 2022 at 0:3 Comment(0)
A
0

In Scala even without reflective compilation there are bugs in combination of scala-java interop and F-bounded polymorphism:

scalac reports error on valid Java class: illegal cyclic reference involving type T

and others.

And parents of com.google.protobuf.DynamicMessage explore F-bounds:

DynamicMessage 
  <: AbstractMessage
       <: AbstractMessageLite[_,_] (such inheritance is allowed in Java but not in Scala) 
            [M <: AbstractMessageLite[M, B],
             B <: AbstractMessageLite.Builder[M, B]]
                                        [M <: AbstractMessageLite[M, B],
                                         B <: AbstractMessageLite.Builder[M, B]]
                                                                    <: MessageLite.Builder
                                                                                     <: MessageLiteOrBuilder
                                                                                     <: Cloneable
            <: MessageLite
                 <: MessageLiteOrBuilder
       <: Message
            <: MessageLite...
            <: MessageOrBuilder
                 <: MessageLiteOrBuilder

But without reflective compilation your code compiles. So this is a bug in combination of reflective compilation, scala-java interop and F-bounded polymorphism.

A workaround is to use real compiler instead of toolbox:

import akka.actor.{ActorSystem, Props}
// libraryDependencies += "com.github.os72" % "protobuf-dynamic" % "1.0.1"
import com.github.os72.protobuf.dynamic.{DynamicSchema, MessageDefinition}
import com.google.protobuf.DynamicMessage
import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.nsc.{Global, Settings}

val actorCode = """
  |import akka.actor._
  |import com.google.protobuf._
  |
  |class SimpleActor extends Actor {
  |  override def receive: Receive = {
  |    case dynamicMessage: DynamicMessage => println("Dynamic message received!")
  |    case _  => println("Whatever!")  // the default, catch-all
  |  }
  |}
  |
  |object SimpleActor {
  |    def props() : Props = Props(new SimpleActor())
  |}
  |""".stripMargin

val directory = new VirtualDirectory("(memory)", None)
val runtimeMirror = createRuntimeMirror(directory, runtime.currentMirror)
compileCode(actorCode, List(), directory)
val props = runObjectMethod("SimpleActor", runtimeMirror, "props")
  .asInstanceOf[Props]

val actorSystem = ActorSystem("actorSystem")
val actor = actorSystem.actorOf(props, "helloActor")

val msg = makeDynamicMessage()

actor ! "hello" // Whatever!
actor ! msg // Dynamic message received!

actorSystem.terminate()

//see (*)
def makeDynamicMessage(): DynamicMessage = {
  val schemaBuilder = DynamicSchema.newBuilder
  schemaBuilder.setName("PersonSchemaDynamic.proto")

  val msgDef = MessageDefinition.newBuilder("Person")
    .addField("required", "int32", "id", 1)
    .build

  schemaBuilder.addMessageDefinition(msgDef)
  val schema = schemaBuilder.build

  val msgBuilder = schema.newMessageBuilder("Person")
  val msgDesc = msgBuilder.getDescriptorForType
  msgBuilder
    .setField(msgDesc.findFieldByName("id"), 1)
    .build
}

def compileCode(
  code: String, 
  classpathDirectories: List[AbstractFile], 
  outputDirectory: AbstractFile
): Unit = {
  val settings = new Settings
  classpathDirectories.foreach(dir => settings.classpath.prepend(dir.toString))
  settings.outputDirs.setSingleOutput(outputDirectory)
  settings.usejavacp.value = true
  val global = new Global(settings)
  (new global.Run).compileSources(List(new BatchSourceFile("(inline)", code)))
}

def runObjectMethod(
  objectName: String,
  runtimeMirror: Mirror, 
  methodName: String, 
  arguments: Any*
): Any = {
  val objectSymbol         = runtimeMirror.staticModule(objectName)
  val objectModuleMirror   = runtimeMirror.reflectModule(objectSymbol)
  val objectInstance       = objectModuleMirror.instance
  val objectType           = objectSymbol.typeSignature
  val methodSymbol         = objectType.decl(TermName(methodName)).asMethod
  val objectInstanceMirror = runtimeMirror.reflect(objectInstance)
  val methodMirror         = objectInstanceMirror.reflectMethod(methodSymbol)
  methodMirror(arguments: _*)
}

def createRuntimeMirror(directory: AbstractFile, parentMirror: Mirror): Mirror = {
  val classLoader = new AbstractFileClassLoader(directory, parentMirror.classLoader)
  universe.runtimeMirror(classLoader)
}

Tensorflow in Scala reflection (here was a similar situation with a bug in combination of reflective compilation, Scala-Java interop, and path-dependent types)

Dynamic compilation of multiple Scala classes at runtime

How to eval code that uses InterfaceStability annotation (that fails with "illegal cyclic reference involving class InterfaceStability")? (also "illegal cyclic reference" during reflective compilation)

Scala Presentation Compiler - Minimal Example

(*) Protocol buffer objects generated at runtime

Ashlar answered 18/9, 2022 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.