Reflecting method references

Paul Sandoz paul.sandoz at oracle.com
Thu Aug 1 23:27:59 UTC 2024



> On Jul 26, 2024, at 12:20 PM, Tagir Valeev <amaembo at gmail.com> wrote:
> 
> Hello!
> 
> I'm continuing working on my toy decompiler-interpreter-asserts-library and trying to restore a method reference from the code model. I've noticed that there's a LambdaOp.methodReference() method. However, it works for only one method reference case: instance method without qualifier expression. In other cases, it returns null. More specifically:
> String::isEmpty -- works
> Integer::valueOf -- doesn't work
> "xyz"::equals -- doesn't work
> String::new -- doesn't work
> 
> Probably it's not implemented yet and still in the queue, I just wanted to ensure that current behavior is not final.

Correct, work in progress! I added that quickly as an experiment and it seems worthwhile pursuing further i.e. could this lambda operation have modeled a method reference? (Originally we modeled method references directly, but it was easier to manage by desugaring.)


> 
> Concentrating on a single working case (String::isEmpty), I need to construct a functional lambda. We don't need to capture anything here, which simplifies things a bit, but still the construction takes some effort. My best attempt is the following (error handling removed for brevity):
> 
> CoreOp.InvokeOp invokeOp = lambdaOp.methodReference().get();
> MethodHandle handle = invokeOp.invokeDescriptor().resolveToHandle(lookup);
> Class<?> fnClass = toClass(lambdaOp.functionalInterface());
> // It looks like finding SAM is necessary. Is there an easier way of getting it?

Yeah, I have also wanted some public method for this, there is an implementation buried in the bytecode generator and MethodHandleProxies. Methods on Object needs to be skipped.


> Method sam = Stream.of(fnClass.getMethods())
>        .filter(m -> Modifier.isAbstract(m.getModifiers())).findFirst().get();
> MethodType samMethodType = MethodType.methodType(sam.getReturnType(), sam.getParameterTypes());
> MethodType dynamicType = toMethodType(lambdaOp.invokableType());
> CallSite callSite = LambdaMetafactory.metafactory(lookup, sam.getName(), MethodType.methodType(fnClass),
>             samMethodType, handle, dynamicType);
> Object lambda = callSite.dynamicInvoker().invoke();
> ... use lambda ...
> 
> The toMethodType() is the following helper:
> 
> private MethodType toMethodType(FunctionType functionType) {
>   Class<?> rType = toClass(functionType.returnType());
>   Class<?>[] pTypes = functionType.parameterTypes().stream().map(this::toClass).toArray(Class[]::new);
>   return MethodType.methodType(rType, pTypes);
> }
> 
> Which in turn uses another helper toClass to convert TypeElement to Class<?>
> 
> So, quite a lot of code. The first thing which could be improved is providing toMethodType(lookup) instance method directly inside FunctionType. It could be nice to have a convenient bridge between TypeElement world and MethodType world.
> 

There's a method on MethodRef that can help:

  static MethodTypeDesc toNominalDescriptor(FunctionType t) {

I placed it there, temporarily, because I was uncertain if we should place Java specific functionality on FunctionType. WDYT?


> But probably it would be reasonable to resolve a LambdaOp directly to CallSite, encapsulating all this stuff I write? In my application, I don't need to do something fancy with nested lambdas, I just want to run them normally. Not sure, but probably this could be reasonable in other applications as well. Just something to think about.
> 

Can you share some details as to why you need this? Do you also intend to also support lambda expressions?

I am wondering if we can leverage functionality in the bytecode generator and expose it as functionality e.g. for any LambdaOp generate a CallSite for it, which may include generating bytecode for the lambda if its not like a method reference?

Paul.


More information about the babylon-dev mailing list