Uploaded image for project: 'Scala Programming Language'
  1. Scala Programming Language
  2. SI-8905

Methods inherited from generic traits sometimes have types erased to Object

    Details

    • Type: Bug
    • Status: Open
    • Priority: Critical
    • Resolution: Unresolved
    • Affects Version/s: Scala 2.10.4, Scala 2.11.0, Scala 2.11.1, Scala 2.11.2, Scala 2.11.3
    • Fix Version/s: Backlog
    • Component/s: None
    • Labels:
    • Environment:

      sbt launcher version 0.13.6
      java version "1.7.0_65"
      OS X

      Description

      It seems that methods inherited from generic traits sometimes have their types erased to Object, which causes compilation or runtime errors when calling these methods from Java. This seems like it it might be related to SI-3452.

      Here's a simple reproduction (also available as a gist with a build.sbt, in case you want to clone it to quickly test this out):

      In Scala:

      import java.util.Comparator
       
      trait RDDLike[T] {
        def max(comp: Comparator[T]): T = {
          (1.0).asInstanceOf[T]
        }
      }
       
      class DoubleRDD extends RDDLike[java.lang.Double] { }
      

      In Java:

      import java.util.Comparator;
       
      public class Test {
        private static class DoubleComparator implements Comparator<Double> {
          public int compare(Double o1, Double o2) {
            return o1.compareTo(o2);
          }
        }
       
        public static void main(String[] args) {
          DoubleRDD rdd = new DoubleRDD();
          RDDLike<Double> rddLike = rdd;
       
          // This call works fine:
          double rddLikeMax = rddLike.max(new DoubleComparator());
          // In Scala 2.10.4, this code compiles but this call fails at runtime:
          // java.lang.NoSuchMethodError: DoubleRDD.max(Ljava/util/Comparator;)Ljava/lang/Double;
          double rddMax = rdd.max(new DoubleComparator());
        }
       
      }
      

      In Scala 2.10.4, this code compiles fine but throws a NoSuchMethodError at runtime when I call the max method via the DoubleRDD class. In all versions of Scala 2.11 that I've tested, the Java code fails to compile:

      error: incompatible types
      [error]     double rddMax = rdd.max(new DoubleComparator());
      [error]                            ^
      [error]   required: double
      [error]   found:    Object
      [error] 1 error
      

      Based on javap, it seems that Scala 2.10.4 emits the right signature for DoubleRDD.max:

      javap -s target/scala-2.10/classes/DoubleRDD.class
      Compiled from "RDD.scala"
      public class DoubleRDD implements RDDLike<java.lang.Double> {
        public java.lang.Double max(java.util.Comparator<java.lang.Double>);
          Signature: (Ljava/util/Comparator;)Ljava/lang/Object;
       
        public DoubleRDD();
          Signature: ()V
      }
      

      Scala 2.11.2 seems to erase to Object:

      javap -s target/scala-2.11/classes/DoubleRDD.class
      Compiled from "RDD.scala"
      public class DoubleRDD implements RDDLike<java.lang.Double> {
        public java.lang.Object max(java.util.Comparator);
          Signature: (Ljava/util/Comparator;)Ljava/lang/Object;
       
        public DoubleRDD();
          Signature: ()V
      }
      

      However, if I override the implementation of the max method in my class, then everything works fine. This compiles and runs as expected:

      class DoubleRDD extends RDDLike[java.lang.Double] {
        override def max(comp: Comparator[java.lang.Double]): java.lang.Double = {
          (1.0).asInstanceOf[java.lang.Double]
        }
      }
      

      Surprisingly, though, the generated bytecode now contains two max methods:

      javap -s target/scala-2.10/classes/DoubleRDD.class
      Compiled from "RDD.scala"
      public class DoubleRDD implements RDDLike<java.lang.Double> {
        public java.lang.Double max(java.util.Comparator<java.lang.Double>);
          Signature: (Ljava/util/Comparator;)Ljava/lang/Double;
       
        public java.lang.Object max(java.util.Comparator);
          Signature: (Ljava/util/Comparator;)Ljava/lang/Object;
       
        public DoubleRDD();
          Signature: ()V
      }
      

      This happens in both 2.10.4 and 2.11.2.

        Attachments

          Issue Links

            Activity

            Hide
            retronym Jason Zaugg added a comment -

            The tension in the compiler is that the Mixin phase of the compiler, which add "trait forwarder" methods for mixed-in methods, does not add a bridge method if the erased signature of the forwarder differs (ie: is more specific than) the interface method that is implements. This is because, for historical reasons, the Mixin phase comes after the Erasure phase (which adds bridges.)

            In my patch for SI-3452, I originally tried to add these bridge methods. However, this caused certain programs to fail compilation, if the extra bridge method clashed with an existing method the user defined. So I backed out of that path, and instead opted to ensure that the forwarder would not differ in erased signature from the interface method. This is what now leads to the weaker return type you see from Java clients.

            A workaround would be to introduce an intermediate abstract class as follows:

            import java.util.Comparator
             
            trait RDDLike[T] {
              def max(comp: Comparator[T]): T = {
                (1.0).asInstanceOf[T]
              }
            }
             
            abstract class AbstractRDD[T] extends RDDLike[T]
             
            class DoubleRDD extends AbstractRDD[java.lang.Double] { }
            

            Show
            retronym Jason Zaugg added a comment - The tension in the compiler is that the Mixin phase of the compiler, which add "trait forwarder" methods for mixed-in methods, does not add a bridge method if the erased signature of the forwarder differs (ie: is more specific than) the interface method that is implements. This is because, for historical reasons, the Mixin phase comes after the Erasure phase (which adds bridges.) In my patch for SI-3452 , I originally tried to add these bridge methods. However, this caused certain programs to fail compilation, if the extra bridge method clashed with an existing method the user defined. So I backed out of that path, and instead opted to ensure that the forwarder would not differ in erased signature from the interface method. This is what now leads to the weaker return type you see from Java clients. A workaround would be to introduce an intermediate abstract class as follows: import java.util.Comparator trait RDDLike[T] { def max(comp: Comparator[T]): T = { (1.0).asInstanceOf[T] } }   abstract class AbstractRDD[T] extends RDDLike[T] class DoubleRDD extends AbstractRDD[java.lang.Double] { }

              People

              • Assignee:
                retronym Jason Zaugg
                Reporter:
                joshrosen Josh Rosen
              • Votes:
                2 Vote for this issue
                Watchers:
                6 Start watching this issue

                Dates

                • Created:
                  Updated: