I have an interesting problem that I am not understanding. Hopefully you
can tell me exactly what is going on here.
I have a trait that attempts to return instances of its implementing class
defined like so:
trait IStringPair[T] {
def a : String
def b : String
def build(a : String, b : String) : T
def cat(that : IStringPair[T]) = build(this.a + that.a, this.b + that.b)
override def toString = a + b
}
Here is a simple implementation:
class StringPair(val a : String, val b : String) extends
IStringPair[StringPair] {
def build(a : String, b : String) = new StringPair(a, b)
def len = a.length + b.length
}
I can paste this into a repl session and do stuff like this:
val a = new StringPair("A", "B")
val b = new StringPair("1", "2")
val c = a cat b
println(c.len)
So far, so good.
Now, I try to interop this in Java. I put the above into a package named
"types" and create the class shown at the end of this message.
Here is the issue. When I call pass, everything works as expected. When I
call fail, I get a no such method error. I have no idea what I am doing
wrong here. If I run the command :javap -c -private types.StringPair from
the repl, I get public java.lang.Object cat(types.IStringPair). This seems
like it ought to work as StringPair is an IStringPair, as shown in the
dump (public class types.StringPair extends java.lang.Object implements
types.IStringPair,scala.ScalaObject).
Anyone willing to enlighten me on this? I would certainly appreciate it.
Thanks!
//Example demonstrating the issue
package types;
public class JMain {
public static void main(String[] args) {
pass();
fail();
}
public static void pass() {
System.out.println("This works.....");
IStringPair a = new StringPair("A", "B");
IStringPair b = new StringPair("1", "2");
StringPair c = (StringPair) a.cat(b);
System.out.println(c.len());
}
public static void fail() {
System.out.println("This produces:\n[error] java.lang.NoSuchMethodError:
types.StringPair.cat(Ltypes/IStringPair;)Ltypes/StringPair;");
StringPair a = new StringPair("A", "B");
StringPair b = new StringPair("1", "2");
StringPair c = (StringPair) a.cat(b);
System.out.println(c.len());
System.out.println(c.len());
}
}
If the generated bytecode is:
public java.lang.Object cat(types.IStringPair);
Code:
0: aload_0
1: aload_1
2: invokestatic #14; //Method
types/IStringPair$class.cat:(Ltypes/IStringPair;Ltypes/IStringPair;)Ljava/l
ang/Object;
5: areturn
And StringPair is clearly (I think) an instance of an IStringPair (see
below), shouldn't it work? Type erasure turns the return type into a
java.lang.Object, which is why I do the cast on the return value in the
Java code.
//From the javap dump
public class types.StringPair extends java.lang.Object implements
types.IStringPair,scala.ScalaObject
Any tips on how to get this to work or if it is even possible?
Thanks!
I don't think so. Type erasure would normally produce a
ClassCastException. A NoSuchMethodError almost always points to a
problem with the static signatures of method calls. I haven't looked
at it closely but it may be related to
https://issues.scala-lang.org/browse/SI-3452
I think it makes sense to look in the issue tracker for other bugs
mentioning NoSuchMethodErrors and if you don't find anything file a
new report.
Johannes
--
Johannes
-----------------------------------------------
Johannes Rudolph
http://virtual-void.net
One workaround, which gives the interesting trait method a result type
erased to IStringPair; then use asT to downcast:
trait IStringPair2[T <: IStringPair2[T]] {
def build(a : String, b : String) : IStringPair2[T]
def asT: T = this.asInstanceOf[T]
}
class StringPair2(val a : String, val b : String) extends
IStringPair2[StringPair2] {
}
Java seems OK then with
StringPair2 c = a.cat(b).asT();
apm@halyard ~/tmp
$ rm nomethod/*.class
apm@halyard ~/tmp
$ scalac -Ycheck:genjvm nomethod.scala
nomethod.scala:36: warning: compiler bug: created generic signature
for method cat in nomethod.StringPair that does not conform to its
erasure
signature: (Lnomethod/IStringPair<Lnomethod/StringPair;>;)Lnomethod/StringPair;
original type: (that: nomethod.IStringPair)nomethod.StringPair
normalized type: (that: nomethod.IStringPair)nomethod.StringPair
erasure type: (that: nomethod.IStringPair)java.lang.Object
if this is reproducible, please report bug at http://lampsvn.epfl.ch/trac/scala
class StringPair(val a : String, val b : String) extends
IStringPair[StringPair] {
^
one warning found
apm@halyard ~/tmp
$ javac -Xlint:unchecked -classpath
".;O:/scala-2.9.1.final/lib/scala-library.jar" nomethod/JMain.java
apm@halyard ~/tmp
$ java -classpath ".;O:/scala-2.9.1.final/lib/scala-library.jar" nomethod.JMain
This works.....
A1B2
4
(2) This produces:
[error] java.lang.NoSuchMethodError:
types.StringPair.cat(Ltypes/IStringPair;)Ltypes/StringPair;
A1B2
4
This produces:
[error] java.lang.NoSuchMethodError:
types.StringPair.cat(Ltypes/IStringPair;)Ltypes/StringPair;
A1B2
4
apm@halyard ~/tmp
$ rm nomethod/*.class
apm@halyard ~/tmp
$ scalac nomethod.scala
apm@halyard ~/tmp
$ javac -Xlint:unchecked -classpath
".;O:/scala-2.9.1.final/lib/scala-library.jar" nomethod/JMain.java
apm@halyard ~/tmp
$ java -classpath ".;O:/scala-2.9.1.final/lib/scala-library.jar" nomethod.JMain
This works.....
A1B2
4
(2) This produces:
[error] java.lang.NoSuchMethodError:
types.StringPair.cat(Ltypes/IStringPair;)Ltypes/StringPair;
A1B2
4
This produces:
[error] java.lang.NoSuchMethodError:
types.StringPair.cat(Ltypes/IStringPair;)Ltypes/StringPair;
Exception in thread "main" java.lang.NoSuchMethodError:
nomethod.StringPair.cat(Lnomethod/IStringPair;)Lnomethod/StringPair;
at nomethod.JMain.fail(JMain.java:37)
at nomethod.JMain.main(JMain.java:9)
IStringPair<StringPair> a = new StringPair("A", "B");
IStringPair<StringPair> b = new StringPair("1", "2");
StringPair c = a.cat(b);
Also, in the original failing case, no cast is needed to compile
(suspiciously; that's without -check:genjvm)
StringPair a = new StringPair("A", "B");
StringPair b = new StringPair("1", "2");
StringPair c = a.cat(b);