Scala Programming Language
  1. Scala Programming Language
  2. SI-6168

Java generic signatures such as A<B>.C incorrectly parsed when reading classfiles

    Details

      Description

      see attachments for
      In java client code:

      public class Test {
        public static void main(String[] args, int argv) {
          SomeClass a = new SomeClass();
          SomeClass2 a2 = new SomeClass2();
          SomeClass b = a.f.set(23).f.set(23);
          SomeClass2 b2 = a2.f.set(23).f.set(23);
        }
      }
      

      This compiles and runs because the java compiler makes use of checkcast and NameAndType to downcast (reify so to say, it is a kind of runtime type info) the type parameter ParentType to SomeClass resp. SomeClass2. The code also looks very clean.

      The scala compiler doesn't do this, although NameAndType is available)

      object Main extends App {
        var a : SomeClass = new SomeClass
        var a2 : SomeClass2 = new SomeClass2
        var b : SomeClass = a.f.set(23).f.set(23)
        var b2 : SomeClass2 = a.f.set(23).f.set(23)
      }
      
      C:\scala-2.10.0-M6\myexamples\javainterop>scalac main.scala
      main.scala:11: error: value f is not a member of type parameter ParentType
        var b : SomeClass = a.f.set(23).f.set(23)
                                        ^
      main.scala:12: error: value f is not a member of type parameter ParentType
        var b2 : SomeClass2 = a.f.set(23).f.set(23)
                                          ^
      two errors found
      

      You have to manually explicitly use implicit conversions.

      object Main extends App {
        var a : SomeClass = new SomeClass
        var a2 : SomeClass2 = new SomeClass2
        import language.implicitConversions
        implicit def setParentType2SomeClass(x:Any) = x.asInstanceOf[SomeClass]
        implicit def setParentType2SomeClass2(x:Any) = x.asInstanceOf[SomeClass2]
        var b : SomeClass = a.f.set(23).f.set(23)
        var b2 : SomeClass2 = a2.f.set(23).f.set(23)
      }
      

      For 1 concrete class SomeClass this works but for two and more this becomes ambiguous.

      For two or more the only work around to make it compilable and runnable is:

      object Main extends App {
        var a : SomeClass = new SomeClass
        var a2 : SomeClass2 = new SomeClass2
        var b : SomeClass = a.f.set(23).asInstanceOf[SomeClass].f.set(23).asInstanceOf[SomeClass]
        var b2 : SomeClass2 = a2.f.set(23).asInstanceOf[SomeClass2].f.set(23).asInstanceOf[SomeClass2]
      }
      

      which looks awful (this is almost an advertisement for java succinctness)

      javap for java code

      Compiled from "Test.java"
      public class Test extends java.lang.Object
        SourceFile: "Test.java"
        InnerClass:
         public abstract #31= #26 of #37; //Field=class Context$Field of class Context
      
        minor version: 0
        major version: 50
        Constant pool:
      const #1 = Method       #11.#20;        //  java/lang/Object."<init>":()V
      const #2 = class        #21;    //  SomeClass
      const #3 = Method       #2.#20; //  SomeClass."<init>":()V
      const #4 = class        #22;    //  SomeClass2
      const #5 = Method       #4.#20; //  SomeClass2."<init>":()V
      const #6 = Field        #2.#23; //  SomeClass.f:LContext$Field;
      const #7 = Method       #24.#25;        //  java/lang/Integer.valueOf:(I)Ljava/l
      ang/Integer;
      const #8 = Method       #26.#27;        //  Context$Field.set:(Ljava/lang/Object
      ;)Ljava/lang/Object;
      const #9 = Field        #4.#23; //  SomeClass2.f:LContext$Field;
      const #10 = class       #28;    //  Test
      const #11 = class       #29;    //  java/lang/Object
      const #12 = Asciz       <init>;
      const #13 = Asciz       ()V;
      const #14 = Asciz       Code;
      const #15 = Asciz       LineNumberTable;
      const #16 = Asciz       main;
      const #17 = Asciz       ([Ljava/lang/String;I)V;
      const #18 = Asciz       SourceFile;
      const #19 = Asciz       Test.java;
      const #20 = NameAndType #12:#13;//  "<init>":()V
      const #21 = Asciz       SomeClass;
      const #22 = Asciz       SomeClass2;
      const #23 = NameAndType #30:#33;//  f:LContext$Field;
      const #24 = class       #34;    //  java/lang/Integer
      const #25 = NameAndType #35:#36;//  valueOf:(I)Ljava/lang/Integer;
      const #26 = class       #38;    //  Context$Field
      const #27 = NameAndType #39:#40;//  set:(Ljava/lang/Object;)Ljava/lang/Object;
      const #28 = Asciz       Test;
      const #29 = Asciz       java/lang/Object;
      const #30 = Asciz       f;
      const #31 = Asciz       Field;
      const #32 = Asciz       InnerClasses;
      const #33 = Asciz       LContext$Field;;
      const #34 = Asciz       java/lang/Integer;
      const #35 = Asciz       valueOf;
      const #36 = Asciz       (I)Ljava/lang/Integer;;
      const #37 = class       #41;    //  Context
      const #38 = Asciz       Context$Field;
      const #39 = Asciz       set;
      const #40 = Asciz       (Ljava/lang/Object;)Ljava/lang/Object;;
      const #41 = Asciz       Context;
      
      {
      public Test();
        Code:
         Stack=1, Locals=1, Args_size=1
         0:   aload_0
         1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
         4:   return
        LineNumberTable:
         line 1: 0
      
      
      public static void main(java.lang.String[], int);
        Code:
         Stack=2, Locals=6, Args_size=2
         0:   new     #2; //class SomeClass
         3:   dup
         4:   invokespecial   #3; //Method SomeClass."<init>":()V
         7:   astore_2
         8:   new     #4; //class SomeClass2
         11:  dup
         12:  invokespecial   #5; //Method SomeClass2."<init>":()V
         15:  astore_3
         16:  aload_2
         17:  getfield        #6; //Field SomeClass.f:LContext$Field;
         20:  bipush  23
         22:  invokestatic    #7; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Int
      eger;
         25:  invokevirtual   #8; //Method Context$Field.set:(Ljava/lang/Object;)Ljava
      /lang/Object;
         28:  checkcast       #2; //class SomeClass
         31:  getfield        #6; //Field SomeClass.f:LContext$Field;
         34:  bipush  23
         36:  invokestatic    #7; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Int
      eger;
         39:  invokevirtual   #8; //Method Context$Field.set:(Ljava/lang/Object;)Ljava
      /lang/Object;
         42:  checkcast       #2; //class SomeClass
         45:  astore  4
         47:  aload_3
         48:  getfield        #9; //Field SomeClass2.f:LContext$Field;
         51:  bipush  23
         53:  invokestatic    #7; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Int
      eger;
         56:  invokevirtual   #8; //Method Context$Field.set:(Ljava/lang/Object;)Ljava
      /lang/Object;
         59:  checkcast       #4; //class SomeClass2
         62:  getfield        #9; //Field SomeClass2.f:LContext$Field;
         65:  bipush  23
         67:  invokestatic    #7; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Int
      eger;
         70:  invokevirtual   #8; //Method Context$Field.set:(Ljava/lang/Object;)Ljava
      /lang/Object;
         73:  checkcast       #4; //class SomeClass2
         76:  astore  5
         78:  return
        LineNumberTable:
         line 3: 0
         line 4: 8
         line 5: 16
         line 6: 47
         line 7: 78
      
      
      }
      

      I think SI-1377 is a bit overdone. As the above shows there are still valid use cases for checkcast (i.e. to avoid verbosity and ambiguity in the client surface code)

      Associated thread:
      https://groups.google.com/forum/?hl=en&fromgroups#!topic/scala-user/Tl8TZ2LUsbM

      1. Context.java
        0.7 kB
        DaveScala
      2. main.scala
        0.6 kB
        DaveScala
      3. SomeClass.java
        0.3 kB
        DaveScala
      4. SomeClass2.java
        0.3 kB
        DaveScala
      5. Test.java
        0.2 kB
        DaveScala

        Issue Links

          Activity

          Hide
          DaveScala added a comment -

          Test files

          Show
          DaveScala added a comment - Test files
          Hide
          Paul Phillips added a comment -

          checkcast is an implementation detail, it's not relevant to this bug - checkcast happens whenever it needs to, but if the type of something is "Any" then there's nothing to check. The bug, assuming it is one, is that the scalac classfile parser doesn't in this case find the applied type argument of an enclosing class.

          Show
          Paul Phillips added a comment - checkcast is an implementation detail, it's not relevant to this bug - checkcast happens whenever it needs to, but if the type of something is "Any" then there's nothing to check. The bug, assuming it is one, is that the scalac classfile parser doesn't in this case find the applied type argument of an enclosing class.
          Hide
          Rodrigo Cano added a comment -

          As an argument in favor of it being a bug, even through java's reflection the information is available:

          Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_04).
          Type in expressions to have them evaluated.
          Type :help for more information.
          
          scala> val sm = new SomeClass
          sm: SomeClass = SomeClass@6433aa85
          
          scala> println(sm.getClass.getFields.head)
          public final Context$Field SomeClass.f
          
          scala> println(sm.getClass.getFields.head.getGenericType)
          Context<SomeClass>.Field<java.lang.Integer>
          
          Show
          Rodrigo Cano added a comment - As an argument in favor of it being a bug, even through java's reflection the information is available: Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_04). Type in expressions to have them evaluated. Type :help for more information. scala> val sm = new SomeClass sm: SomeClass = SomeClass@6433aa85 scala> println(sm.getClass.getFields.head) public final Context$Field SomeClass.f scala> println(sm.getClass.getFields.head.getGenericType) Context<SomeClass>.Field<java.lang.Integer>
          Hide
          Paul Phillips added a comment -

          I think it's a bug, I just didn't want to phrase that comment to suggest it is definitively a bug.

          Show
          Paul Phillips added a comment - I think it's a bug, I just didn't want to phrase that comment to suggest it is definitively a bug.
          Show
          Jason Zaugg added a comment - https://github.com/scala/scala/pull/2310

            People

            • Assignee:
              Jason Zaugg
              Reporter:
              DaveScala
            • Votes:
              1 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development