Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
scabug opened this issue Aug 2, 2012 · 6 comments

Comments

@scabug
Copy link

scabug commented Aug 2, 2012

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 #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

@scabug
Copy link
Author

scabug commented Aug 2, 2012

Imported From: https://issues.scala-lang.org/browse/SI-6168?orig=1
Reporter: DaveScala (davescala)
Affected Versions: 2.9.1, 2.9.2, 2.10.0-M6
See #6169, #1377
Attachments:

  • Context.java (created on Aug 2, 2012 10:38:47 AM UTC, 715 bytes)
  • main.scala (created on Aug 2, 2012 10:38:47 AM UTC, 583 bytes)
  • SomeClass.java (created on Aug 2, 2012 10:38:47 AM UTC, 284 bytes)
  • SomeClass2.java (created on Aug 2, 2012 10:38:47 AM UTC, 286 bytes)
  • Test.java (created on Aug 2, 2012 10:38:47 AM UTC, 236 bytes)

@scabug
Copy link
Author

scabug commented Aug 2, 2012

DaveScala (davescala) said:
Test files

@scabug
Copy link
Author

scabug commented Aug 2, 2012

@paulp said:
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.

@scabug
Copy link
Author

scabug commented Aug 2, 2012

Rodrigo Cano (rcano) said:
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>

@scabug
Copy link
Author

scabug commented Aug 2, 2012

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

@scabug
Copy link
Author

scabug commented Mar 25, 2013

@retronym said:
scala/scala#2310

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants