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

ClassCastException with structural types, value classes #6336

Closed
scabug opened this issue Sep 7, 2012 · 12 comments
Closed

ClassCastException with structural types, value classes #6336

scabug opened this issue Sep 7, 2012 · 12 comments

Comments

@scabug
Copy link

scabug commented Sep 7, 2012

A structural type with a value class parameter throws a ClassCastException at runtime.

object D {
  def main(args: Array[String]) {
    val a = new { def y[T](x: X[T]) = x.i }
    val x = new X(3)
    val t = a.y(x)
    println(t)
  }
}
class X[T](val i: T) extends AnyVal
java.lang.ClassCastException: X cannot be cast to java.lang.Integer
	at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
	at D$.main(D.scala:5)
	at D.main(D.scala)
{code}
@scabug
Copy link
Author

scabug commented Sep 7, 2012

Imported From: https://issues.scala-lang.org/browse/SI-6336?orig=1
Reporter: @harrah
Affected Versions: 2.10.0-M7, 2.10.0
Other Milestones: 2.10.0

@scabug
Copy link
Author

scabug commented Sep 7, 2012

@jsuereth said:
Again, bumping to you, just pass back if it's not a blocker/critical.

@scabug
Copy link
Author

scabug commented Sep 9, 2012

@retronym said (edited on Sep 9, 2012 8:15:49 PM UTC):
(Comments originally lodged, accidentally, on #6337)

A play-by-play in the REPL:

scala> class X[T](val i: T) extends AnyVal
defined class X

scala> object a { def y[T](x: X[T]) = x.i }
defined module a

scala> val t = a.y(new X(0))
t: Int = 0

scala> val t = (a: {def y[T](x: X[T]): T}).y(new X(0))
warning: there were 1 feature warnings; re-run with -feature for details
java.lang.ClassCastException: X cannot be cast to java.lang.Integer

A very similar problem: methods with erased value classes as parameters can't be invoked with reflection.

scala> class X[T](val i: T) extends AnyVal
defined class X

scala> object a { def y[T](x: X[T]) = x.i }
defined module a

scala> val cm = reflect.runtime.currentMirror
cm: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@4bcc946b of type class scala.tools.nsc.interpreter.IMain$TranslatingClassLoader with classpath [(memory)] and parent being scala.tools.nsc.util.ScalaClassLoader$URLClassLoader@642423ad of type class scala.tools.nsc.util.ScalaClassLoader$URLClassLoader with classpath [file:/Library/Java/JavaVirtualMachines/1.6.0_27-b07-395.jdk/Contents/Classes/classes.jar,file:/Library/Java/JavaVirtualMachines/1.6.0_27-b07-395.jdk/Contents/Classes/ui.jar,file:/Library/Java/JavaVirtualMachines/1.6.0_27-b07-395.jdk/Contents/Classes/jsse.jar,file:/Library/Java/JavaVirtualMachines/1.6.0_27-b07-395.jdk/Contents/Classes/jce.jar,file:/Library/Java/JavaVirtualMachines/1.6.0_27-b07-395.jdk/Contents/Class...
scala> val moduleA = cm.reflect(a)
moduleA: reflect.runtime.universe.InstanceMirror = instance mirror for a$@6ff64801

scala> val methodY = moduleA.symbol.typeSignature.declarations.last.asMethod
methodY: reflect.runtime.universe.MethodSymbol = method y

scala> moduleA.reflectMethod(methodY)
res10: reflect.runtime.universe.MethodMirror = method mirror for a.y[T](x: X[T]): T (bound to a$@6ff64801)

scala> res10(new X(0))
java.lang.NoClassDefFoundError: no Java class corresponding to ErasedValueType(X[T]) found
	at scala.reflect.runtime.JavaMirrors$JavaMirror.typeToJavaClass(JavaMirrors.scala:1160)

Currently, method signatures that accept value classes are compiled to accept the unboxed type:

scala> class X(val i: Int) extends AnyVal
defined class X

scala> class C1 { def foo(x: X) = x.i * 2 }
defined class C1

scala> :javap C1
Compiled from "<console>"
public class C1 extends java.lang.Object{
    public int foo(int);
    public C1();
}

The compiler unboxes the X at the call site (and eliminates allocation of a new X, where possible). But for reflective callers, this never happens.

It seems to me that both problems would be solved if we left the signatures unmolested, and created a shadow method that accepts the unboxed value:

public class C1 extends java.lang.Object{
    public int foo(X)
    public int foo$unboxed(int);
    public C1();
}

The first method would delegate to the second, and the compiler would endeavour to call the second directly where possible. This would parallel the way that a specialized method can still be called from a generic call site.

It would also avoid double definition errors like:

scala> class C3 { def foo(x: X) = x.i * 2; def foo(i: Int) = i * 2 }
<console>:11: error: double definition:
method foo:(i: Int)Int and
method foo:(x: X)Int at line 11
have same type after erasure: (i: Int)Int
       class C3 { def foo(x: X) = x.i * 2; def foo(i: Int) = i * 2 }
                                               ^

@scabug
Copy link
Author

scabug commented Sep 9, 2012

@retronym said:
Bringing this to the attention of [scala-sips] https://groups.google.com/d/topic/scala-sips/KSm99KoiYNA/discussion

@scabug
Copy link
Author

scabug commented Sep 10, 2012

@retronym said:
Another example from #6347.

scala> class ValueClass(val n: Int) extends AnyVal
defined class ValueClass
scala> def createStructural = new { | def withValueClass(valueClass: ValueClass) = () | }
createStructural: Object{def withValueClass(valueClass: ValueClass): Unit}
scala> createStructural.withValueClass(new ValueClass(1))

@scabug
Copy link
Author

scabug commented Sep 10, 2012

@jsuereth said:
That ones seems a bit intense.

@scabug
Copy link
Author

scabug commented Sep 10, 2012

@retronym said:
It's really just a different flavour of this error, it just fails sooner.

Martin's short-term suggestion on [scala-sips] is to disallow value classes in structural types. The followup might be to a) add boxed bridge methods outlined above; or b) make structural type dispatch / reflection aware of the necessary boxing/unboxing.

@scabug
Copy link
Author

scabug commented Sep 17, 2012

@jsuereth said:
This is now disallowed. Marking as resolved. Should I open a feature request to make this work in the future?

@scabug
Copy link
Author

scabug commented Sep 17, 2012

@harrah said:
Probably needs to be disallowed in the return type as well:

object D {
  def main(args: Array[String]) {
    val a = new { def y[T](x: T): X[T] = new X(x) }
    val t = a.y(3).i + 1
    println(t)
  }
}

class X[T](val i: T) extends AnyVal
java.lang.ClassCastException: java.lang.Integer cannot be cast to X
   at D$.main(D.scala:4)
   at D.main(D.scala)

@scabug
Copy link
Author

scabug commented Sep 17, 2012

@retronym said (edited on Sep 17, 2012 9:48:55 PM UTC):
One more to test (I'll check it tonight if noone beats me to it)

scala> class X[T](val i: T) extends AnyVal
defined class X

scala> (new { def y[T, XT <: X[T]](xt: XT) = xt.i }).y[Int, X[Int]](new X(0))
java.lang.VerifyError: (class: , method: <init> signature: ()V) Expecting to find object/array on stack

UPDATE:

This example work fine. The error I was seeing was from an older milestone, M5.

@scabug
Copy link
Author

scabug commented Sep 17, 2012

@jsuereth said:
Look at the comments, we still have a missing case issue.

@scabug
Copy link
Author

scabug commented Sep 18, 2012

@odersky said:
New pull request: scala/scala#1342

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