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