-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Painless: Add PainlessConstructor #32447
Changes from 7 commits
1419751
6469598
54b8992
8581deb
641c383
3c5db32
3f17455
0c9c174
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,13 +20,16 @@ | |
package org.elasticsearch.painless; | ||
|
||
import org.elasticsearch.painless.lookup.PainlessClass; | ||
import org.elasticsearch.painless.lookup.PainlessConstructor; | ||
import org.elasticsearch.painless.lookup.PainlessLookup; | ||
import org.elasticsearch.painless.lookup.PainlessLookupUtility; | ||
import org.elasticsearch.painless.lookup.PainlessMethod; | ||
import org.objectweb.asm.Type; | ||
|
||
import java.lang.invoke.MethodType; | ||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.Modifier; | ||
import java.util.List; | ||
|
||
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME; | ||
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE; | ||
|
@@ -59,8 +62,10 @@ public class FunctionRef { | |
|
||
/** interface method */ | ||
public final PainlessMethod interfaceMethod; | ||
/** delegate method */ | ||
public final PainlessMethod delegateMethod; | ||
/** delegate method type parameters */ | ||
public final List<Class<?>> delegateTypeParameters; | ||
/** delegate method return type */ | ||
public final Class<?> delegateReturnType; | ||
|
||
/** factory method type descriptor */ | ||
public final String factoryDescriptor; | ||
|
@@ -80,9 +85,45 @@ public class FunctionRef { | |
* @param call the right hand side of a method reference expression | ||
* @param numCaptures number of captured arguments | ||
*/ | ||
public FunctionRef(PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) { | ||
this(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod, | ||
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures); | ||
public static FunctionRef getFunctionRef(PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) { | ||
if ("new".equals(call)) { | ||
return new FunctionRef(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod, | ||
lookup(painlessLookup, expected, type), numCaptures); | ||
} else { | ||
return new FunctionRef(expected, painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod, | ||
lookup(painlessLookup, expected, type, call, numCaptures > 0), numCaptures); | ||
} | ||
} | ||
|
||
/** | ||
* Creates a new FunctionRef (already resolved) | ||
* @param expected functional interface type to implement | ||
* @param interfaceMethod functional interface method | ||
* @param delegateConstructor implementation constructor | ||
* @param numCaptures number of captured arguments | ||
*/ | ||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessConstructor delegateConstructor, int numCaptures) { | ||
Constructor<?> javaConstructor = delegateConstructor.javaConstructor; | ||
MethodType delegateMethodType = delegateConstructor.methodType; | ||
|
||
interfaceMethodName = interfaceMethod.name; | ||
factoryMethodType = MethodType.methodType(expected, | ||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount())); | ||
interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1); | ||
|
||
delegateClassName = javaConstructor.getDeclaringClass().getName(); | ||
isDelegateInterface = false; | ||
delegateInvokeType = H_NEWINVOKESPECIAL; | ||
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME; | ||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures); | ||
|
||
this.interfaceMethod = interfaceMethod; | ||
delegateTypeParameters = delegateConstructor.typeParameters; | ||
delegateReturnType = void.class; | ||
|
||
factoryDescriptor = factoryMethodType.toMethodDescriptorString(); | ||
interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString()); | ||
delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString()); | ||
} | ||
|
||
/** | ||
|
@@ -112,9 +153,7 @@ public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMe | |
isDelegateInterface = delegateMethod.target.isInterface(); | ||
} | ||
|
||
if ("<init>".equals(delegateMethod.name)) { | ||
delegateInvokeType = H_NEWINVOKESPECIAL; | ||
} else if (Modifier.isStatic(delegateMethod.modifiers)) { | ||
if (Modifier.isStatic(delegateMethod.modifiers)) { | ||
delegateInvokeType = H_INVOKESTATIC; | ||
} else if (delegateMethod.target.isInterface()) { | ||
delegateInvokeType = H_INVOKEINTERFACE; | ||
|
@@ -126,7 +165,8 @@ public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMe | |
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures); | ||
|
||
this.interfaceMethod = interfaceMethod; | ||
this.delegateMethod = delegateMethod; | ||
delegateTypeParameters = delegateMethod.arguments; | ||
delegateReturnType = delegateMethod.rtn; | ||
|
||
factoryDescriptor = factoryMethodType.toMethodDescriptorString(); | ||
interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString()); | ||
|
@@ -151,13 +191,37 @@ public FunctionRef(Class<?> expected, | |
isDelegateInterface = false; | ||
|
||
this.interfaceMethod = null; | ||
delegateMethod = null; | ||
delegateTypeParameters = null; | ||
delegateReturnType = null; | ||
|
||
factoryDescriptor = null; | ||
interfaceType = null; | ||
delegateType = null; | ||
} | ||
|
||
/** | ||
* Looks up {@code type} from the whitelist, and returns a matching constructor. | ||
*/ | ||
private static PainlessConstructor lookup(PainlessLookup painlessLookup, Class<?> expected, String type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe call this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
// check its really a functional interface | ||
// for e.g. Comparable | ||
PainlessMethod method = painlessLookup.getPainlessStructFromJavaClass(expected).functionalMethod; | ||
if (method == null) { | ||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::new] " + | ||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"); | ||
} | ||
|
||
// lookup requested constructor | ||
PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(painlessLookup.getJavaClassFromPainlessType(type)); | ||
PainlessConstructor impl = struct.constructors.get(PainlessLookupUtility.buildPainlessConstructorKey(method.arguments.size())); | ||
|
||
if (impl == null) { | ||
throw new IllegalArgumentException("Unknown reference [" + type + "::new] matching [" + expected + "]"); | ||
} | ||
|
||
return impl; | ||
} | ||
|
||
/** | ||
* Looks up {@code type::call} from the whitelist, and returns a matching method. | ||
*/ | ||
|
@@ -174,27 +238,22 @@ private static PainlessMethod lookup(PainlessLookup painlessLookup, Class<?> exp | |
// lookup requested method | ||
PainlessClass struct = painlessLookup.getPainlessStructFromJavaClass(painlessLookup.getJavaClassFromPainlessType(type)); | ||
final PainlessMethod impl; | ||
// ctor ref | ||
if ("new".equals(call)) { | ||
impl = struct.constructors.get(PainlessLookupUtility.buildPainlessMethodKey("<init>", method.arguments.size())); | ||
} else { | ||
// look for a static impl first | ||
PainlessMethod staticImpl = | ||
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.arguments.size())); | ||
if (staticImpl == null) { | ||
// otherwise a virtual impl | ||
final int arity; | ||
if (receiverCaptured) { | ||
// receiver captured | ||
arity = method.arguments.size(); | ||
} else { | ||
// receiver passed | ||
arity = method.arguments.size() - 1; | ||
} | ||
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity)); | ||
// look for a static impl first | ||
PainlessMethod staticImpl = | ||
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.arguments.size())); | ||
if (staticImpl == null) { | ||
// otherwise a virtual impl | ||
final int arity; | ||
if (receiverCaptured) { | ||
// receiver captured | ||
arity = method.arguments.size(); | ||
} else { | ||
impl = staticImpl; | ||
// receiver passed | ||
arity = method.arguments.size() - 1; | ||
} | ||
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity)); | ||
} else { | ||
impl = staticImpl; | ||
} | ||
if (impl == null) { | ||
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " + | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Licensed to Elasticsearch under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.elasticsearch.painless.lookup; | ||
|
||
import java.lang.invoke.MethodHandle; | ||
import java.lang.invoke.MethodType; | ||
import java.lang.reflect.Constructor; | ||
import java.util.List; | ||
|
||
public class PainlessConstructor { | ||
public final Constructor<?> javaConstructor; | ||
public final List<Class<?>> typeParameters; | ||
public final MethodHandle methodHandle; | ||
public final MethodType methodType; | ||
|
||
PainlessConstructor(Constructor<?> javaConstructor, List<Class<?>> typeParameters, MethodHandle methodHandle, MethodType methodType) { | ||
this.javaConstructor = javaConstructor; | ||
this.typeParameters = typeParameters; | ||
this.methodHandle = methodHandle; | ||
this.methodType = methodType; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -336,6 +336,13 @@ public static boolean isConstantType(Class<?> type) { | |
type == String.class; | ||
} | ||
|
||
/** | ||
* Constructs a painless constructor key used to lookup painless constructors from a painless class. | ||
*/ | ||
public static int buildPainlessConstructorKey(int constructorArity) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need a method for this? Can't we just say the key is always the constructor arity? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed this to be a String so error messages will read out /arity instead of just arity so people will know this is a constructor rather than just be baffled by some random number. |
||
return constructorArity; | ||
} | ||
|
||
/** | ||
* Constructs a painless method key used to lookup painless methods from a painless class. | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would name this maybe "resolveFromLookup" or something like that, since the purpose compared to the actor is to resolve a signature into a FunctionRef object by looking up in the lookup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.