scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

Scala-reflect: can't pass by-name Function0 into reflected method #11182

Open neko-kai opened 5 years ago

neko-kai commented 5 years ago

Example:

import scala.reflect.runtime.universe._
import scala.reflect.runtime._

object F {
  def f(n: => Int): Int = n + 5
}

object App extends App {
  val hi: () => Int = { () => println("hi"); 5 }

  F.f(hi())
  // res0: Int = 10

  (currentMirror: universe.Mirror)
    .reflect(F)
    .reflectMethod(
      symbolOf[F.type].asClass.module
        .info.member(TermName("f")).asMethod)
    .apply(hi)
  /*
  java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(scratch_159.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke(scratch_159.scala:58)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(scratch_159.scala:39)
    at java.lang.reflect.Method.invoke(scratch_159.scala:494)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvokeraw(scratch_159.scala:332)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvoke(scratch_159.scala:336)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaTransformingMethodMirror.apply(scratch_159.scala:436)
    at #worksheet#.#worksheet#(scratch_159.scala:19)
  Caused by: java.lang.ClassCastException: A$A6$A$A6$$Lambda$17755/412629211 cannot be cast to java.lang.Integer
    at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
    at scala.Function0.apply$mcI$sp(Function0.scala:34)
    at A$A6$A$A6$F$.f(scratch_159.scala:9)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvokeraw(JavaMirrors.scala:336)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaMethodMirror.jinvoke(JavaMirrors.scala:340)
    at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaTransformingMethodMirror.apply(JavaMirrors.scala:440)
    at A$A6$A$A6.get$$instance$$res1(scratch_159.scala:23)
    at A$A6$.main(scratch_159.scala:48)
    at A$A6.main(scratch_159.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jetbrains.jps.incremental.scala.local.worksheet.WorksheetInProcessRunnerFactory$WorksheetInProcessRunnerImpl.$anonfun$loadAndRun$5(WorksheetInProcessRunnerFactory.scala:64)
    at scala.Option.map(Option.scala:146)
    at org.jetbrains.jps.incremental.scala.local.worksheet.WorksheetInProcessRunnerFactory$WorksheetInProcessRunnerImpl.loadAndRun(WorksheetInProcessRunnerFactory.scala:62)
    at org.jetbrains.jps.incremental.scala.local.worksheet.WorksheetServer.$anonfun$loadAndRun$1(WorksheetServer.scala:31)
    at org.jetbrains.jps.incremental.scala.local.worksheet.WorksheetServer.$anonfun$loadAndRun$1$adapted(WorksheetServer.scala:31)
    at scala.Option.foreach(Option.scala:257)
    at org.jetbrains.jps.incremental.scala.local.worksheet.WorksheetServer.loadAndRun(WorksheetServer.scala:31)
    at org.jetbrains.jps.incremental.scala.remote.Main$.make(Main.scala:85)
   */
}

Expected being able to pass a delayed value to a by-name method, but got an exception!

The error is in scala.reflect.runtime.JavaMirrors:

   private class JavaTransformingMethodMirror(val receiver: Any, symbol: MethodSymbol, metadata: MethodMetadata)
            extends JavaMethodMirror(symbol, metadata.ret) {
      def this(receiver: Any, symbol: MethodSymbol) = this(receiver, symbol, new MethodMetadata(symbol))
      override def bind(newReceiver: Any) = new JavaTransformingMethodMirror(newReceiver, symbol, metadata)
      import metadata._

      def apply(args: Any*): Any = {
        val args1 = new Array[Any](args.length)
        var i = 0
        while (i < args1.length) {
          val arg = args(i)
          args1(i) = (
            if (i >= paramCount)             arg                           // don't transform varargs
            else if (isByName(i))            () => arg                     // don't transform by-name value class params
            else if (isDerivedValueClass(i)) paramUnboxers(i).invoke(arg)  // do get the underlying value
            else                             arg                           // don't molest anything else
          )
          i += 1
        }
        jinvoke(args1)
      }
    }

Where in .apply

else if (isByName(i)) () => arg                     // don't transform by-name value class params

The argument is always assumed to be a computed value, not a delayed Function0, therefore it's impossible to call a method with a delayed by-name

Something like this could fix it:

else if (isByName(i) && arg.getClass == classOf[Function0]) arg
else if (isByName(i))            () => arg                     // don't transform by-name value class params
neko-kai commented 5 years ago

Addendum: A by-name function is supposed to accept Function0 normally, i.e. the following works correctly:

val function0F = F.asInstanceOf[{ def f(n: Function0[Int]): Int }]

function0F.f(hi)
// hi

The problem arises only in JavaTransformingMethodMirror