graaljs
graaljs copied to clipboard
Scala varargs interop problem
I'm in the process of migrating code from Nashorn to Graal. My host language is Scala rather than Java. I've encountered a problem with calling a varargs method from JavaScript code.
Consider the following code:
import org.graalvm.polyglot.{Context, HostAccess}
import scala.annotation.varargs
object GraalScalaTest {
def main(args: Array[String]): Unit = {
val hostAccess = HostAccess
.newBuilder()
.allowPublicAccess(true)
.build()
val ctx = Context.newBuilder("js").allowHostAccess(hostAccess).build
val bindings = ctx.getBindings("js")
bindings.putMember("r", new Receiver)
val script =
"""
|r.items(r.newItem('a'));
|r.items(r.newItem('a'), r.newItem('b'));
|""".stripMargin
ctx.eval("js", script)
}
class Item(str: String) {
override def toString: String = this.str
}
class Receiver {
def newItem(s: String): Item = new Item(s)
@varargs
def items(item1: Item, rest: Item*): Unit = {
println(s"rest has type: ${rest.getClass}")
val all = Seq(item1) ++ rest
dump(all)
}
private def dump(items: Seq[Item]): Unit = {
println(s"Items (${items.size}):")
items.foreach(item => println(s"* $item"))
}
}
}
The script code calls items
first with 1 parameter and then with 2. Note that items
has the varargs
annotation, to force the Scala compiler to generate a method that takes a varargs array as last argument (meant for Java interop).
The output is:
Items (1):
* a
rest has type: class scala.collection.mutable.WrappedArray$ofRef
Items (2):
* a
* b
This is great, but I need more host access. Thus I modify the code as follows:
val hostAccess = HostAccess
.newBuilder()
.allowPublicAccess(true)
.allowAllImplementations(true) // <--- added
.build()
Now the output is as follows:
rest has type: class scala.collection.mutable.WrappedArray$ofRef
Items (1):
* a
rest has type: class com.sun.proxy.$Proxy14
Exception in thread "main" Unsupported operation identifier 'foreach' and object 'b'(language: Java, type: GraalScalaTest$Item). Identifier is not executable or instantiable.
at com.oracle.truffle.polyglot.PolyglotEngineException.unsupported(PolyglotEngineException.java:134)
at com.oracle.truffle.polyglot.HostInteropErrors.newUnsupportedOperationException(HostInteropErrors.java:206)
at com.oracle.truffle.polyglot.HostInteropErrors.invokeUnsupported(HostInteropErrors.java:178)
at com.oracle.truffle.polyglot.ProxyInvokeNode.invokeOrExecute(HostInteropReflect.java:628)
at com.oracle.truffle.polyglot.ProxyInvokeNode.doCachedMethod(HostInteropReflect.java:592)
at com.oracle.truffle.polyglot.ProxyInvokeNodeGen.executeAndSpecialize(ProxyInvokeNodeGen.java:116)
at com.oracle.truffle.polyglot.ProxyInvokeNodeGen.execute(ProxyInvokeNodeGen.java:64)
at com.oracle.truffle.polyglot.ObjectProxyNode.executeImpl(HostInteropReflect.java:535)
at com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:99)
at com.oracle.truffle.polyglot.ObjectProxyHandler.invoke(HostInteropReflect.java:678)
at com.sun.proxy.$Proxy14.foreach(Unknown Source)
at scala.collection.generic.Growable.$plus$plus$eq(Growable.scala:62)
at scala.collection.generic.Growable.$plus$plus$eq$(Growable.scala:53)
at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:184)
at scala.collection.mutable.ListBuffer.$plus$plus$eq(ListBuffer.scala:47)
at scala.collection.TraversableLike.defaultPlusPlus$1(TraversableLike.scala:152)
at scala.collection.TraversableLike.$plus$plus(TraversableLike.scala:169)
at scala.collection.TraversableLike.$plus$plus$(TraversableLike.scala:147)
at scala.collection.immutable.List.$plus$plus(List.scala:210)
at GraalScalaTest$Receiver.items(GraalScalaTest.scala:33)
at <js> :program(Unnamed:3:28-66)
at org.graalvm.polyglot.Context.eval(Context.java:371)
at GraalScalaTest$.main(GraalScalaTest.scala:20)
at GraalScalaTest.main(GraalScalaTest.scala)
Thus, the case with 2 items fails - but 1 item works well.
Invoking javap
on the Receiver
class shows:
public class GraalScalaTest$Receiver {
public void items(GraalScalaTest$Item, GraalScalaTest$Item...);
public GraalScalaTest$Item newItem(java.lang.String);
public void items(GraalScalaTest$Item, scala.collection.Seq<GraalScalaTest$Item>);
public static final void $anonfun$dump$1(GraalScalaTest$Item);
public GraalScalaTest$Receiver();
public static final java.lang.Object $anonfun$dump$1$adapted(GraalScalaTest$Item);
}
The error doesn't happen with Java, so I suppose the problem is that Scala generates 2 items
overloads. I tried adding a Value
-> Seq
target type mapping, but that didn't help.
Is there any workaround for this?
It works if I call items
with 3 arguments. Thus, the problem is the case when a single argument is passed for the rest
array.
I found a workaround:
@varargs
def items(item1: Item, rest: Item*): Unit = {
println(s"rest has type: ${rest.getClass}")
val restSeq = rest match {
case p: java.lang.reflect.Proxy =>
val v = Context.getCurrent.asValue(p)
val item = v.asHostObject[Item]()
Seq(item)
case _ => rest.toSeq
}
val all = Seq(item1) ++ restSeq
dump(all)
}
It's more of a hack than a workaround though... Also, in the real application I'd like to keep the host code free from Graal-specific types.