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

MethodHandle invoke does not play well with macros #17079

Open
markehammons opened this issue Mar 10, 2023 · 4 comments
Open

MethodHandle invoke does not play well with macros #17079

markehammons opened this issue Mar 10, 2023 · 4 comments
Assignees
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug

Comments

@markehammons
Copy link

markehammons commented Mar 10, 2023

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.

@markehammons markehammons added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 10, 2023
@markehammons
Copy link
Author

This example is a bit long, mainly to show my attempts using compile-time reflection. In theory though, this should work with fn2 at least, and that is a much simpler example of the problem.

@prolativ
Copy link
Contributor

@markehammons how exactly do you run these snippets? File2 seems to be incorrect, as import scala.util.Try is missing and you cannot put expressions top-level. I changed this to

import scala.util.Try

object Test:
  Try(FnTest.fn3()).recover(t => println(t.getMessage()))
  Try(FnTest.fn2()).recover(t => println(t.getMessage()))
  Try(FnTest.fn()).recover(t => println(t.getMessage()))

But then everything compiles without errors (I'm using scala-cli to compile the files).
Interestingly, when I try to test this in REPL (copy-pasting the snippets one after another) I get errors like

-- Error: ----------------------------------------------------------------------
 3 |Try(FnTest.fn3()).recover(t => println(t.getMessage()))
   |    ^^^^^^^^^^^^
   |    Could not find class rs$line$1$FnTest$ in classpath

@prolativ prolativ added stat:cannot reproduce and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 10, 2023
@markehammons
Copy link
Author

@markehammons how exactly do you run these snippets? File2 seems to be incorrect, as import scala.util.Try is missing and you cannot put expressions top-level. I changed this to

import scala.util.Try

object Test:
  Try(FnTest.fn3()).recover(t => println(t.getMessage()))
  Try(FnTest.fn2()).recover(t => println(t.getMessage()))
  Try(FnTest.fn()).recover(t => println(t.getMessage()))

But then everything compiles without errors (I'm using scala-cli to compile the files).
Interestingly, when I try to test this in REPL (copy-pasting the snippets one after another) I get errors like

-- Error: ----------------------------------------------------------------------
 3 |Try(FnTest.fn3()).recover(t => println(t.getMessage()))
   |    ^^^^^^^^^^^^
   |    Could not find class rs$line$1$FnTest$ in classpath

The errors are runtime errors, not compiletime.

@prolativ
Copy link
Contributor

Ok, I can now reproduce it when I run it with

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()))

as File2

@prolativ prolativ added area:metaprogramming:reflection Issues related to the quotes reflection API and removed stat:cannot reproduce stat:needs info labels Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Projects
None yet
Development

No branches or pull requests

3 participants