Skip to content

MethodHandle invoke does not play well with macros #17079

Open
@markehammons

Description

@markehammons

Compiler version

Scala 3.3.0-RC3

Minimized code

File1:

import java.lang.invoke.MethodHandle
import scala.quoted.*
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType

class FnTest:
  def target(i: Int, j: Int): Int = i + j

object FnTest:
  val x = 4
  inline def fn() = ${
    fnImpl('mh, 'x)
  }

  val methodType =
    MethodType.methodType(classOf[Int], classOf[Int], classOf[Int])
  def target(i: Int): Int = i
  val mh = MethodHandles
    .publicLookup()
    .nn
    .findVirtual(classOf[FnTest], "target", methodType)
    .nn
    .bindTo(FnTest())
    .nn

  def fnImpl(mh: Expr[MethodHandle], num: Expr[Int])(using Quotes) =
    import quotes.reflect.*

    val invoke = Symbol
      .classSymbol("java.lang.invoke.MethodHandle")
      .declaredMethod("invoke")
      .head

    val mhInvocation = Apply(
      Select(
        Inlined(
          None,
          Nil,
          Inlined(None, Nil, mh.asTerm)
        ),
        invoke
      ),
      List(
        Typed(
          Inlined(
            None,
            Nil,
            Repeated(
              List(
                Inlined(None, Nil, Inlined(None, Nil, num.asTerm)),
                Inlined(None, Nil, Inlined(None, Nil, num.asTerm))
              ),
              Inferred(TypeRepr.of[Int])
            )
          ),
          Inferred(TypeRepr.of[Seq[Int]])
        )
      )
    )
    val invocation = Inlined(
      Some(TypeTree.of[FnTest.type]),
      Nil,
      TypeApply(
        Select(
          mhInvocation,
          TypeRepr.of[Any].classSymbol.get.declaredMethod("asInstanceOf").head
        ),
        List(TypeTree.of[Int])
      )
    ).asExprOf[Int]

    invocation

  inline def fn2() = ${
    fn2Impl('mh, 'x)
  }

  def fn2Impl(mh: Expr[MethodHandle], num: Expr[Int])(using Quotes) =
    import quotes.reflect.*
    val code = '{
      $mh.invoke(${ Varargs(List(num, num)) }*).asInstanceOf[Int]
    }

    report.info(code.asTerm.show(using Printer.TreeStructure))

    code

  inline def fn3() = ${
    fn3Impl('mh, 'x)
  }

  def fn3Impl(mh: Expr[MethodHandle], num: Expr[Int])(using Quotes) =
    import quotes.reflect.*
    val code = '{
      $mh.invoke($num, $num).asInstanceOf[Int]
    }

    report.info(code.asTerm.show(using Printer.TreeStructure))
    code

File2:

import scala.util.Try

@main def run() =
  Try(FnTest.fn3()).recover(t => println(t.getMessage()))
  Try(FnTest.fn2()).recover(t => println(t.getMessage()))
  Try(FnTest.fn()).recover(t => println(t.getMessage()))

Output

expected (int,int)int but found (int[])int
expected (int,int)int but found (Seq)Object

Expectation

All three of these macros should work, while only fn3 works in practice. What this means is that to deal with n-arity invocations of invoke on MethodHandles I have to manually write out the inputs for each arity by hand.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions