Painless: Clean up FunctionRef (#32644)
This change consolidates all the logic for generating a FunctionReference (renamed from FunctionRef) from several arbitrary constructors to a single static function that is used at both compile-time and run-time. This increases long-term maintainability as it is much easier to follow when and how a function reference is being generated. It moves most of the duplicated logic out of the ECapturingFuncRef, EFuncRef and ELambda nodes and Def as well.
This commit is contained in:
parent
dcc816427e
commit
0b7fb4e7b9
|
@ -279,10 +279,6 @@ public final class Def {
|
||||||
String type = signature.substring(1, separator);
|
String type = signature.substring(1, separator);
|
||||||
String call = signature.substring(separator+1, separator2);
|
String call = signature.substring(separator+1, separator2);
|
||||||
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
|
int numCaptures = Integer.parseInt(signature.substring(separator2+1));
|
||||||
Class<?> captures[] = new Class<?>[numCaptures];
|
|
||||||
for (int capture = 0; capture < captures.length; capture++) {
|
|
||||||
captures[capture] = callSiteType.parameterType(i + 1 + capture);
|
|
||||||
}
|
|
||||||
MethodHandle filter;
|
MethodHandle filter;
|
||||||
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
|
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
|
||||||
if (signature.charAt(0) == 'S') {
|
if (signature.charAt(0) == 'S') {
|
||||||
|
@ -294,11 +290,15 @@ public final class Def {
|
||||||
interfaceType,
|
interfaceType,
|
||||||
type,
|
type,
|
||||||
call,
|
call,
|
||||||
captures);
|
numCaptures);
|
||||||
} else if (signature.charAt(0) == 'D') {
|
} else if (signature.charAt(0) == 'D') {
|
||||||
// the interface type is now known, but we need to get the implementation.
|
// the interface type is now known, but we need to get the implementation.
|
||||||
// this is dynamically based on the receiver type (and cached separately, underneath
|
// this is dynamically based on the receiver type (and cached separately, underneath
|
||||||
// this cache). It won't blow up since we never nest here (just references)
|
// this cache). It won't blow up since we never nest here (just references)
|
||||||
|
Class<?> captures[] = new Class<?>[numCaptures];
|
||||||
|
for (int capture = 0; capture < captures.length; capture++) {
|
||||||
|
captures[capture] = callSiteType.parameterType(i + 1 + capture);
|
||||||
|
}
|
||||||
MethodType nestedType = MethodType.methodType(interfaceType, captures);
|
MethodType nestedType = MethodType.methodType(interfaceType, captures);
|
||||||
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
||||||
localMethods,
|
localMethods,
|
||||||
|
@ -331,57 +331,34 @@ public final class Def {
|
||||||
*/
|
*/
|
||||||
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||||
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
||||||
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
||||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(interfaceType).functionalMethod;
|
PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType);
|
||||||
if (interfaceMethod == null) {
|
if (interfaceMethod == null) {
|
||||||
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
||||||
}
|
}
|
||||||
int arity = interfaceMethod.typeParameters.size();
|
int arity = interfaceMethod.typeParameters.size();
|
||||||
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
||||||
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
|
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
|
||||||
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
|
||||||
implMethod.javaMethod.getName(), receiverClass);
|
implMethod.javaMethod.getName(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a method handle to an implementation of clazz, given method reference signature. */
|
/** Returns a method handle to an implementation of clazz, given method reference signature. */
|
||||||
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, Class<?>... captures) throws Throwable {
|
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures) throws Throwable {
|
||||||
final FunctionRef ref;
|
final FunctionRef ref = FunctionRef.create(painlessLookup, localMethods, null, clazz, type, call, captures);
|
||||||
if ("this".equals(type)) {
|
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
|
||||||
// user written method
|
methodHandlesLookup,
|
||||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(clazz).functionalMethod;
|
ref.interfaceMethodName,
|
||||||
if (interfaceMethod == null) {
|
ref.factoryMethodType,
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
ref.interfaceMethodType,
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface");
|
ref.delegateClassName,
|
||||||
}
|
ref.delegateInvokeType,
|
||||||
int arity = interfaceMethod.typeParameters.size() + captures.length;
|
ref.delegateMethodName,
|
||||||
LocalMethod localMethod = localMethods.get(Locals.buildLocalMethodKey(call, arity));
|
ref.delegateMethodType,
|
||||||
if (localMethod == null) {
|
ref.isDelegateInterface ? 1 : 0
|
||||||
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
|
);
|
||||||
// because the arity does not match the expected interface type.
|
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, ref.factoryMethodType.parameterArray()));
|
||||||
if (call.contains("$")) {
|
|
||||||
throw new IllegalArgumentException("Incorrect number of parameters for [" + interfaceMethod.javaMethod.getName() +
|
|
||||||
"] in [" + clazz + "]");
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
|
|
||||||
}
|
|
||||||
ref = new FunctionRef(clazz, interfaceMethod, call, localMethod.methodType, captures.length);
|
|
||||||
} else {
|
|
||||||
// whitelist lookup
|
|
||||||
ref = FunctionRef.resolveFromLookup(painlessLookup, clazz, type, call, captures.length);
|
|
||||||
}
|
|
||||||
final CallSite callSite = LambdaBootstrap.lambdaBootstrap(
|
|
||||||
methodHandlesLookup,
|
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryMethodType,
|
|
||||||
ref.interfaceMethodType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateMethodType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,17 +20,17 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||||
import org.elasticsearch.painless.lookup.PainlessClass;
|
|
||||||
import org.elasticsearch.painless.lookup.PainlessConstructor;
|
import org.elasticsearch.painless.lookup.PainlessConstructor;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookup;
|
import org.elasticsearch.painless.lookup.PainlessLookup;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
||||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
||||||
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
|
import static org.objectweb.asm.Opcodes.H_INVOKEINTERFACE;
|
||||||
|
@ -39,251 +39,210 @@ import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
|
||||||
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
|
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to a function or lambda.
|
* Contains all the values necessary to write the instruction to initiate a
|
||||||
* <p>
|
* {@link LambdaBootstrap} for either a function reference or a user-defined
|
||||||
* Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap}
|
* lambda function.
|
||||||
* either statically from bytecode with invokedynamic, or at runtime from Java.
|
|
||||||
*/
|
*/
|
||||||
public class FunctionRef {
|
public class FunctionRef {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new FunctionRef which will resolve {@code type::call} from the whitelist.
|
||||||
|
* @param painlessLookup the whitelist against which this script is being compiled
|
||||||
|
* @param localMethods user-defined and synthetic methods generated directly on the script class
|
||||||
|
* @param location the character number within the script at compile-time
|
||||||
|
* @param targetClass functional interface type to implement.
|
||||||
|
* @param typeName the left hand side of a method reference expression
|
||||||
|
* @param methodName the right hand side of a method reference expression
|
||||||
|
* @param numberOfCaptures number of captured arguments
|
||||||
|
*/
|
||||||
|
public static FunctionRef create(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods, Location location,
|
||||||
|
Class<?> targetClass, String typeName, String methodName, int numberOfCaptures) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(painlessLookup);
|
||||||
|
Objects.requireNonNull(targetClass);
|
||||||
|
Objects.requireNonNull(typeName);
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
|
||||||
|
String targetClassName = PainlessLookupUtility.typeToCanonicalTypeName(targetClass);
|
||||||
|
PainlessMethod interfaceMethod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(targetClass);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException("cannot convert function reference [" + typeName + "::" + methodName + "] " +
|
||||||
|
"to a non-functional interface [" + targetClassName + "]", iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
String interfaceMethodName = interfaceMethod.javaMethod.getName();
|
||||||
|
MethodType interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
||||||
|
String delegateClassName;
|
||||||
|
boolean isDelegateInterface;
|
||||||
|
int delegateInvokeType;
|
||||||
|
String delegateMethodName;
|
||||||
|
MethodType delegateMethodType;
|
||||||
|
|
||||||
|
Class<?> delegateMethodReturnType;
|
||||||
|
List<Class<?>> delegateMethodParameters;
|
||||||
|
int interfaceTypeParametersSize = interfaceMethod.typeParameters.size();
|
||||||
|
|
||||||
|
if ("this".equals(typeName)) {
|
||||||
|
Objects.requireNonNull(localMethods);
|
||||||
|
|
||||||
|
if (numberOfCaptures < 0) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
String localMethodKey = Locals.buildLocalMethodKey(methodName, numberOfCaptures + interfaceTypeParametersSize);
|
||||||
|
LocalMethod localMethod = localMethods.get(localMethodKey);
|
||||||
|
|
||||||
|
if (localMethod == null) {
|
||||||
|
throw new IllegalArgumentException("function reference [this::" + localMethodKey + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found" + (localMethodKey.contains("$") ? " due to an incorrect number of arguments" : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = CLASS_NAME;
|
||||||
|
isDelegateInterface = false;
|
||||||
|
delegateInvokeType = H_INVOKESTATIC;
|
||||||
|
delegateMethodName = localMethod.name;
|
||||||
|
delegateMethodType = localMethod.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = localMethod.returnType;
|
||||||
|
delegateMethodParameters = localMethod.typeParameters;
|
||||||
|
} else if ("new".equals(methodName)) {
|
||||||
|
if (numberOfCaptures != 0) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessConstructor painlessConstructor;
|
||||||
|
|
||||||
|
try {
|
||||||
|
painlessConstructor = painlessLookup.lookupPainlessConstructor(typeName, interfaceTypeParametersSize);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException("function reference [" + typeName + "::new/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found", iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = painlessConstructor.javaConstructor.getDeclaringClass().getName();
|
||||||
|
isDelegateInterface = false;
|
||||||
|
delegateInvokeType = H_NEWINVOKESPECIAL;
|
||||||
|
delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
|
||||||
|
delegateMethodType = painlessConstructor.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = painlessConstructor.javaConstructor.getDeclaringClass();
|
||||||
|
delegateMethodParameters = painlessConstructor.typeParameters;
|
||||||
|
} else {
|
||||||
|
if (numberOfCaptures != 0 && numberOfCaptures != 1) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean captured = numberOfCaptures == 1;
|
||||||
|
PainlessMethod painlessMethod;
|
||||||
|
|
||||||
|
try {
|
||||||
|
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, true, methodName, interfaceTypeParametersSize);
|
||||||
|
|
||||||
|
if (captured) {
|
||||||
|
throw new IllegalStateException("internal error");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException staticIAE) {
|
||||||
|
try {
|
||||||
|
painlessMethod = painlessLookup.lookupPainlessMethod(typeName, false, methodName,
|
||||||
|
captured ? interfaceTypeParametersSize : interfaceTypeParametersSize - 1);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"function reference " + "[" + typeName + "::" + methodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"matching [" + targetClassName + ", " + interfaceMethodName + "/" + interfaceTypeParametersSize + "] " +
|
||||||
|
"not found", iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateClassName = painlessMethod.javaMethod.getDeclaringClass().getName();
|
||||||
|
isDelegateInterface = painlessMethod.javaMethod.getDeclaringClass().isInterface();
|
||||||
|
|
||||||
|
if (Modifier.isStatic(painlessMethod.javaMethod.getModifiers())) {
|
||||||
|
delegateInvokeType = H_INVOKESTATIC;
|
||||||
|
} else if (isDelegateInterface) {
|
||||||
|
delegateInvokeType = H_INVOKEINTERFACE;
|
||||||
|
} else {
|
||||||
|
delegateInvokeType = H_INVOKEVIRTUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
delegateMethodName = painlessMethod.javaMethod.getName();
|
||||||
|
delegateMethodType = painlessMethod.methodType;
|
||||||
|
|
||||||
|
delegateMethodReturnType = painlessMethod.returnType;
|
||||||
|
|
||||||
|
if (delegateMethodType.parameterList().size() > painlessMethod.typeParameters.size()) {
|
||||||
|
delegateMethodParameters = new ArrayList<>(painlessMethod.typeParameters);
|
||||||
|
delegateMethodParameters.add(0, delegateMethodType.parameterType(0));
|
||||||
|
} else {
|
||||||
|
delegateMethodParameters = painlessMethod.typeParameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location != null) {
|
||||||
|
for (int typeParameter = 0; typeParameter < interfaceTypeParametersSize; ++typeParameter) {
|
||||||
|
Class<?> from = interfaceMethod.typeParameters.get(typeParameter);
|
||||||
|
Class<?> to = delegateMethodParameters.get(numberOfCaptures + typeParameter);
|
||||||
|
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceMethod.returnType != void.class) {
|
||||||
|
AnalyzerCaster.getLegalCast(location, delegateMethodReturnType, interfaceMethod.returnType, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodType factoryMethodType = MethodType.methodType(targetClass,
|
||||||
|
delegateMethodType.dropParameterTypes(numberOfCaptures, delegateMethodType.parameterCount()));
|
||||||
|
delegateMethodType = delegateMethodType.dropParameterTypes(0, numberOfCaptures);
|
||||||
|
|
||||||
|
return new FunctionRef(interfaceMethodName, interfaceMethodType,
|
||||||
|
delegateClassName, isDelegateInterface, delegateInvokeType, delegateMethodName, delegateMethodType,
|
||||||
|
factoryMethodType
|
||||||
|
);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
if (location != null) {
|
||||||
|
throw location.createError(iae);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** functional interface method name */
|
/** functional interface method name */
|
||||||
public final String interfaceMethodName;
|
public final String interfaceMethodName;
|
||||||
/** factory (CallSite) method signature */
|
|
||||||
public final MethodType factoryMethodType;
|
|
||||||
/** functional interface method signature */
|
/** functional interface method signature */
|
||||||
public final MethodType interfaceMethodType;
|
public final MethodType interfaceMethodType;
|
||||||
/** class of the delegate method to be called */
|
/** class of the delegate method to be called */
|
||||||
public final String delegateClassName;
|
public final String delegateClassName;
|
||||||
|
/** whether a call is made on a delegate interface */
|
||||||
|
public final boolean isDelegateInterface;
|
||||||
/** the invocation type of the delegate method */
|
/** the invocation type of the delegate method */
|
||||||
public final int delegateInvokeType;
|
public final int delegateInvokeType;
|
||||||
/** the name of the delegate method */
|
/** the name of the delegate method */
|
||||||
public final String delegateMethodName;
|
public final String delegateMethodName;
|
||||||
/** delegate method signature */
|
/** delegate method signature */
|
||||||
public final MethodType delegateMethodType;
|
public final MethodType delegateMethodType;
|
||||||
|
/** factory (CallSite) method signature */
|
||||||
|
public final MethodType factoryMethodType;
|
||||||
|
|
||||||
/** interface method */
|
private FunctionRef(
|
||||||
public final PainlessMethod interfaceMethod;
|
String interfaceMethodName, MethodType interfaceMethodType,
|
||||||
/** delegate method type parameters */
|
String delegateClassName, boolean isDelegateInterface,
|
||||||
public final List<Class<?>> delegateTypeParameters;
|
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
|
||||||
/** delegate method return type */
|
MethodType factoryMethodType) {
|
||||||
public final Class<?> delegateReturnType;
|
|
||||||
|
|
||||||
/** factory method type descriptor */
|
this.interfaceMethodName = interfaceMethodName;
|
||||||
public final String factoryDescriptor;
|
this.interfaceMethodType = interfaceMethodType;
|
||||||
/** functional interface method as type */
|
this.delegateClassName = delegateClassName;
|
||||||
public final Type interfaceType;
|
this.isDelegateInterface = isDelegateInterface;
|
||||||
/** delegate method type method as type */
|
this.delegateInvokeType = delegateInvokeType;
|
||||||
public final Type delegateType;
|
|
||||||
|
|
||||||
/** whether a call is made on a delegate interface */
|
|
||||||
public final boolean isDelegateInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef, which will resolve {@code type::call} from the whitelist.
|
|
||||||
* @param painlessLookup the whitelist against which this script is being compiled
|
|
||||||
* @param expected functional interface type to implement.
|
|
||||||
* @param type the left hand side of a method reference expression
|
|
||||||
* @param call the right hand side of a method reference expression
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public static FunctionRef resolveFromLookup(
|
|
||||||
PainlessLookup painlessLookup, Class<?> expected, String type, String call, int numCaptures) {
|
|
||||||
|
|
||||||
if ("new".equals(call)) {
|
|
||||||
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(expected).functionalMethod,
|
|
||||||
lookup(painlessLookup, expected, type), numCaptures);
|
|
||||||
} else {
|
|
||||||
return new FunctionRef(expected, painlessLookup.lookupPainlessClass(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;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = javaConstructor.getDeclaringClass().getName();
|
|
||||||
this.isDelegateInterface = false;
|
|
||||||
this.delegateInvokeType = H_NEWINVOKESPECIAL;
|
|
||||||
this.delegateMethodName = PainlessLookupUtility.CONSTRUCTOR_NAME;
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateConstructor.typeParameters;
|
|
||||||
this.delegateReturnType = void.class;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (already resolved)
|
|
||||||
* @param expected functional interface type to implement
|
|
||||||
* @param interfaceMethod functional interface method
|
|
||||||
* @param delegateMethod implementation method
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, PainlessMethod delegateMethod, int numCaptures) {
|
|
||||||
MethodType delegateMethodType = delegateMethod.methodType;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = delegateMethod.javaMethod.getDeclaringClass().getName();
|
|
||||||
this.isDelegateInterface = delegateMethod.javaMethod.getDeclaringClass().isInterface();
|
|
||||||
|
|
||||||
if (Modifier.isStatic(delegateMethod.javaMethod.getModifiers())) {
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
} else if (delegateMethod.javaMethod.getDeclaringClass().isInterface()) {
|
|
||||||
this.delegateInvokeType = H_INVOKEINTERFACE;
|
|
||||||
} else {
|
|
||||||
this.delegateInvokeType = H_INVOKEVIRTUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.delegateMethodName = delegateMethod.javaMethod.getName();
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateMethod.typeParameters;
|
|
||||||
this.delegateReturnType = delegateMethod.returnType;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (already resolved)
|
|
||||||
* @param expected functional interface type to implement
|
|
||||||
* @param interfaceMethod functional interface method
|
|
||||||
* @param delegateMethod implementation method
|
|
||||||
* @param numCaptures number of captured arguments
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected, PainlessMethod interfaceMethod, LocalMethod delegateMethod, int numCaptures) {
|
|
||||||
MethodType delegateMethodType = delegateMethod.methodType;
|
|
||||||
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = CLASS_NAME;
|
|
||||||
this.isDelegateInterface = false;
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
|
|
||||||
this.delegateMethodName = delegateMethod.name;
|
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
|
||||||
|
|
||||||
this.interfaceMethod = interfaceMethod;
|
|
||||||
this.delegateTypeParameters = delegateMethod.typeParameters;
|
|
||||||
this.delegateReturnType = delegateMethod.returnType;
|
|
||||||
|
|
||||||
this.factoryDescriptor = factoryMethodType.toMethodDescriptorString();
|
|
||||||
this.interfaceType = Type.getMethodType(interfaceMethodType.toMethodDescriptorString());
|
|
||||||
this.delegateType = Type.getMethodType(this.delegateMethodType.toMethodDescriptorString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FunctionRef (low level).
|
|
||||||
* It is for runtime use only.
|
|
||||||
*/
|
|
||||||
public FunctionRef(Class<?> expected,
|
|
||||||
PainlessMethod interfaceMethod, String delegateMethodName, MethodType delegateMethodType, int numCaptures) {
|
|
||||||
this.interfaceMethodName = interfaceMethod.javaMethod.getName();
|
|
||||||
this.factoryMethodType = MethodType.methodType(expected,
|
|
||||||
delegateMethodType.dropParameterTypes(numCaptures, delegateMethodType.parameterCount()));
|
|
||||||
this.interfaceMethodType = interfaceMethod.methodType.dropParameterTypes(0, 1);
|
|
||||||
|
|
||||||
this.delegateClassName = CLASS_NAME;
|
|
||||||
this.delegateInvokeType = H_INVOKESTATIC;
|
|
||||||
this.delegateMethodName = delegateMethodName;
|
this.delegateMethodName = delegateMethodName;
|
||||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
this.delegateMethodType = delegateMethodType;
|
||||||
this.isDelegateInterface = false;
|
this.factoryMethodType = factoryMethodType;
|
||||||
|
|
||||||
this.interfaceMethod = null;
|
|
||||||
this.delegateTypeParameters = null;
|
|
||||||
this.delegateReturnType = null;
|
|
||||||
|
|
||||||
this.factoryDescriptor = null;
|
|
||||||
this.interfaceType = null;
|
|
||||||
this.delegateType = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up {@code type} from the whitelist, and returns a matching constructor.
|
|
||||||
*/
|
|
||||||
private static PainlessConstructor lookup(PainlessLookup painlessLookup, Class<?> expected, String type) {
|
|
||||||
// check its really a functional interface
|
|
||||||
// for e.g. Comparable
|
|
||||||
PainlessMethod method = painlessLookup.lookupPainlessClass(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.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
|
|
||||||
PainlessConstructor impl = struct.constructors.get(PainlessLookupUtility.buildPainlessConstructorKey(method.typeParameters.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.
|
|
||||||
*/
|
|
||||||
private static PainlessMethod lookup(PainlessLookup painlessLookup, Class<?> expected,
|
|
||||||
String type, String call, boolean receiverCaptured) {
|
|
||||||
// check its really a functional interface
|
|
||||||
// for e.g. Comparable
|
|
||||||
PainlessMethod method = painlessLookup.lookupPainlessClass(expected).functionalMethod;
|
|
||||||
if (method == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup requested method
|
|
||||||
PainlessClass struct = painlessLookup.lookupPainlessClass(painlessLookup.canonicalTypeNameToType(type));
|
|
||||||
final PainlessMethod impl;
|
|
||||||
// look for a static impl first
|
|
||||||
PainlessMethod staticImpl =
|
|
||||||
struct.staticMethods.get(PainlessLookupUtility.buildPainlessMethodKey(call, method.typeParameters.size()));
|
|
||||||
if (staticImpl == null) {
|
|
||||||
// otherwise a virtual impl
|
|
||||||
final int arity;
|
|
||||||
if (receiverCaptured) {
|
|
||||||
// receiver captured
|
|
||||||
arity = method.typeParameters.size();
|
|
||||||
} else {
|
|
||||||
// receiver passed
|
|
||||||
arity = method.typeParameters.size() - 1;
|
|
||||||
}
|
|
||||||
impl = struct.methods.get(PainlessLookupUtility.buildPainlessMethodKey(call, arity));
|
|
||||||
} else {
|
|
||||||
impl = staticImpl;
|
|
||||||
}
|
|
||||||
if (impl == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown reference [" + type + "::" + call + "] matching " +
|
|
||||||
"[" + expected + "]");
|
|
||||||
}
|
|
||||||
return impl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_EXPLICIT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
|
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
|
||||||
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
|
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
|
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
|
||||||
|
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
|
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
|
||||||
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
|
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
|
||||||
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
|
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
|
||||||
|
@ -439,4 +440,18 @@ public final class MethodWriter extends GeneratorAdapter {
|
||||||
invokeVirtual(type, method);
|
invokeVirtual(type, method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void invokeLambdaCall(FunctionRef functionRef) {
|
||||||
|
invokeDynamic(
|
||||||
|
functionRef.interfaceMethodName,
|
||||||
|
functionRef.factoryMethodType.toMethodDescriptorString(),
|
||||||
|
LAMBDA_BOOTSTRAP_HANDLE,
|
||||||
|
Type.getMethodType(functionRef.interfaceMethodType.toMethodDescriptorString()),
|
||||||
|
functionRef.delegateClassName,
|
||||||
|
functionRef.delegateInvokeType,
|
||||||
|
functionRef.delegateMethodName,
|
||||||
|
Type.getMethodType(functionRef.delegateMethodType.toMethodDescriptorString()),
|
||||||
|
functionRef.isDelegateInterface ? 1 : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,13 @@ public final class PainlessClass {
|
||||||
public final Map<String, MethodHandle> getterMethodHandles;
|
public final Map<String, MethodHandle> getterMethodHandles;
|
||||||
public final Map<String, MethodHandle> setterMethodHandles;
|
public final Map<String, MethodHandle> setterMethodHandles;
|
||||||
|
|
||||||
public final PainlessMethod functionalMethod;
|
public final PainlessMethod functionalInterfaceMethod;
|
||||||
|
|
||||||
PainlessClass(Map<String, PainlessConstructor> constructors,
|
PainlessClass(Map<String, PainlessConstructor> constructors,
|
||||||
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
|
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
|
||||||
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
|
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
|
||||||
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
|
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
|
||||||
PainlessMethod functionalMethod) {
|
PainlessMethod functionalInterfaceMethod) {
|
||||||
|
|
||||||
this.constructors = Collections.unmodifiableMap(constructors);
|
this.constructors = Collections.unmodifiableMap(constructors);
|
||||||
|
|
||||||
|
@ -54,6 +54,6 @@ public final class PainlessClass {
|
||||||
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
|
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
|
||||||
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
|
this.setterMethodHandles = Collections.unmodifiableMap(setterMethodHandles);
|
||||||
|
|
||||||
this.functionalMethod = functionalMethod;
|
this.functionalInterfaceMethod = functionalInterfaceMethod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ final class PainlessClassBuilder {
|
||||||
final Map<String, MethodHandle> getterMethodHandles;
|
final Map<String, MethodHandle> getterMethodHandles;
|
||||||
final Map<String, MethodHandle> setterMethodHandles;
|
final Map<String, MethodHandle> setterMethodHandles;
|
||||||
|
|
||||||
PainlessMethod functionalMethod;
|
PainlessMethod functionalInterfaceMethod;
|
||||||
|
|
||||||
PainlessClassBuilder() {
|
PainlessClassBuilder() {
|
||||||
constructors = new HashMap<>();
|
constructors = new HashMap<>();
|
||||||
|
@ -49,11 +49,11 @@ final class PainlessClassBuilder {
|
||||||
getterMethodHandles = new HashMap<>();
|
getterMethodHandles = new HashMap<>();
|
||||||
setterMethodHandles = new HashMap<>();
|
setterMethodHandles = new HashMap<>();
|
||||||
|
|
||||||
functionalMethod = null;
|
functionalInterfaceMethod = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
PainlessClass build() {
|
PainlessClass build() {
|
||||||
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
|
return new PainlessClass(constructors, staticMethods, methods, staticFields, fields,
|
||||||
getterMethodHandles, setterMethodHandles, functionalMethod);
|
getterMethodHandles, setterMethodHandles, functionalInterfaceMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,14 @@ public final class PainlessLookup {
|
||||||
return classesToPainlessClasses.get(targetClass);
|
return classesToPainlessClasses.get(targetClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessConstructor lookupPainlessConstructor(String targetClassName, int constructorArity) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessConstructor(targetClass, constructorArity);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessConstructor lookupPainlessConstructor(Class<?> targetClass, int constructorArity) {
|
public PainlessConstructor lookupPainlessConstructor(Class<?> targetClass, int constructorArity) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
|
|
||||||
|
@ -83,6 +91,14 @@ public final class PainlessLookup {
|
||||||
return painlessConstructor;
|
return painlessConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessMethod lookupPainlessMethod(String targetClassName, boolean isStatic, String methodName, int methodArity) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessMethod(targetClass, isStatic, methodName, methodArity);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStatic, String methodName, int methodArity) {
|
public PainlessMethod lookupPainlessMethod(Class<?> targetClass, boolean isStatic, String methodName, int methodArity) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
Objects.requireNonNull(methodName);
|
Objects.requireNonNull(methodName);
|
||||||
|
@ -111,6 +127,14 @@ public final class PainlessLookup {
|
||||||
return painlessMethod;
|
return painlessMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessField lookupPainlessField(String targetClassName, boolean isStatic, String fieldName) {
|
||||||
|
Objects.requireNonNull(targetClassName);
|
||||||
|
|
||||||
|
Class<?> targetClass = canonicalTypeNameToType(targetClassName);
|
||||||
|
|
||||||
|
return lookupPainlessField(targetClass, isStatic, fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
|
public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
|
||||||
Objects.requireNonNull(targetClass);
|
Objects.requireNonNull(targetClass);
|
||||||
Objects.requireNonNull(fieldName);
|
Objects.requireNonNull(fieldName);
|
||||||
|
@ -134,4 +158,20 @@ public final class PainlessLookup {
|
||||||
|
|
||||||
return painlessField;
|
return painlessField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PainlessMethod lookupFunctionalInterfacePainlessMethod(Class<?> targetClass) {
|
||||||
|
PainlessClass targetPainlessClass = classesToPainlessClasses.get(targetClass);
|
||||||
|
|
||||||
|
if (targetPainlessClass == null) {
|
||||||
|
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
PainlessMethod functionalInterfacePainlessMethod = targetPainlessClass.functionalInterfaceMethod;
|
||||||
|
|
||||||
|
if (functionalInterfacePainlessMethod == null) {
|
||||||
|
throw new IllegalArgumentException("target class [" + typeToCanonicalTypeName(targetClass) + "] is not a functional interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
return functionalInterfacePainlessMethod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -875,7 +875,7 @@ public final class PainlessLookupBuilder {
|
||||||
} else if (javaMethods.size() == 1) {
|
} else if (javaMethods.size() == 1) {
|
||||||
java.lang.reflect.Method javaMethod = javaMethods.get(0);
|
java.lang.reflect.Method javaMethod = javaMethods.get(0);
|
||||||
String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
|
String painlessMethodKey = buildPainlessMethodKey(javaMethod.getName(), javaMethod.getParameterCount());
|
||||||
painlessClassBuilder.functionalMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
painlessClassBuilder.functionalInterfaceMethod = painlessClassBuilder.methods.get(painlessMethodKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.DefBootstrap;
|
import org.elasticsearch.painless.DefBootstrap;
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
|
@ -35,8 +34,6 @@ import org.objectweb.asm.Type;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a capturing function reference.
|
* Represents a capturing function reference.
|
||||||
*/
|
*/
|
||||||
|
@ -76,23 +73,8 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
// static case
|
// static case
|
||||||
if (captured.clazz != def.class) {
|
if (captured.clazz != def.class) {
|
||||||
try {
|
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location,
|
||||||
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected,
|
expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
|
||||||
PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < ref.interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = ref.interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = ref.delegateTypeParameters.get(i);
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref.interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, ref.delegateReturnType, ref.interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
|
@ -114,17 +96,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
||||||
} else {
|
} else {
|
||||||
// typed interface, typed implementation
|
// typed interface, typed implementation
|
||||||
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
|
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,22 +19,16 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
import org.elasticsearch.painless.Locals;
|
import org.elasticsearch.painless.Locals;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
|
||||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a function reference.
|
* Represents a function reference.
|
||||||
*/
|
*/
|
||||||
|
@ -63,39 +57,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
||||||
defPointer = "S" + type + "." + call + ",0";
|
defPointer = "S" + type + "." + call + ",0";
|
||||||
} else {
|
} else {
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
try {
|
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, type, call, 0);
|
||||||
if ("this".equals(type)) {
|
|
||||||
// user's own function
|
|
||||||
PainlessMethod interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
|
|
||||||
if (interfaceMethod == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface");
|
|
||||||
}
|
|
||||||
LocalMethod delegateMethod = locals.getMethod(call, interfaceMethod.typeParameters.size());
|
|
||||||
if (delegateMethod == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
|
||||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], function not found");
|
|
||||||
}
|
|
||||||
ref = new FunctionRef(expected, interfaceMethod, delegateMethod, 0);
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = delegateMethod.typeParameters.get(i);
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, delegateMethod.returnType, interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// whitelist lookup
|
|
||||||
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected, type, call, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,17 +66,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
||||||
void write(MethodWriter writer, Globals globals) {
|
void write(MethodWriter writer, Globals globals) {
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
writer.writeDebugInfo(location);
|
writer.writeDebugInfo(location);
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: don't do this: its just to cutover :)
|
// TODO: don't do this: its just to cutover :)
|
||||||
writer.push((String)null);
|
writer.push((String)null);
|
||||||
|
|
|
@ -19,11 +19,9 @@
|
||||||
|
|
||||||
package org.elasticsearch.painless.node;
|
package org.elasticsearch.painless.node;
|
||||||
|
|
||||||
import org.elasticsearch.painless.AnalyzerCaster;
|
|
||||||
import org.elasticsearch.painless.FunctionRef;
|
import org.elasticsearch.painless.FunctionRef;
|
||||||
import org.elasticsearch.painless.Globals;
|
import org.elasticsearch.painless.Globals;
|
||||||
import org.elasticsearch.painless.Locals;
|
import org.elasticsearch.painless.Locals;
|
||||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
|
||||||
import org.elasticsearch.painless.Locals.Variable;
|
import org.elasticsearch.painless.Locals.Variable;
|
||||||
import org.elasticsearch.painless.Location;
|
import org.elasticsearch.painless.Location;
|
||||||
import org.elasticsearch.painless.MethodWriter;
|
import org.elasticsearch.painless.MethodWriter;
|
||||||
|
@ -40,8 +38,6 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lambda expression node.
|
* Lambda expression node.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -122,7 +118,7 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we know the method statically, infer return type and any unknown/def types
|
// we know the method statically, infer return type and any unknown/def types
|
||||||
interfaceMethod = locals.getPainlessLookup().lookupPainlessClass(expected).functionalMethod;
|
interfaceMethod = locals.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(expected);
|
||||||
if (interfaceMethod == null) {
|
if (interfaceMethod == null) {
|
||||||
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
|
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
|
||||||
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
|
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
|
||||||
|
@ -184,25 +180,8 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
defPointer = "Sthis." + name + "," + captures.size();
|
defPointer = "Sthis." + name + "," + captures.size();
|
||||||
} else {
|
} else {
|
||||||
defPointer = null;
|
defPointer = null;
|
||||||
try {
|
ref = FunctionRef.create(
|
||||||
LocalMethod localMethod =
|
locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", desugared.name, captures.size());
|
||||||
new LocalMethod(desugared.name, desugared.returnType, desugared.typeParameters, desugared.methodType);
|
|
||||||
ref = new FunctionRef(expected, interfaceMethod, localMethod, captures.size());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw createError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check casts between the interface method and the delegate method are legal
|
|
||||||
for (int i = 0; i < interfaceMethod.typeParameters.size(); ++i) {
|
|
||||||
Class<?> from = interfaceMethod.typeParameters.get(i);
|
|
||||||
Class<?> to = desugared.parameters.get(i + captures.size()).clazz;
|
|
||||||
AnalyzerCaster.getLegalCast(location, from, to, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceMethod.returnType != void.class) {
|
|
||||||
AnalyzerCaster.getLegalCast(location, desugared.returnType, interfaceMethod.returnType, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,17 +197,7 @@ public final class ELambda extends AExpression implements ILambda {
|
||||||
writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
|
writer.visitVarInsn(MethodWriter.getType(capture.clazz).getOpcode(Opcodes.ILOAD), capture.getSlot());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.invokeDynamic(
|
writer.invokeLambdaCall(ref);
|
||||||
ref.interfaceMethodName,
|
|
||||||
ref.factoryDescriptor,
|
|
||||||
LAMBDA_BOOTSTRAP_HANDLE,
|
|
||||||
ref.interfaceType,
|
|
||||||
ref.delegateClassName,
|
|
||||||
ref.delegateInvokeType,
|
|
||||||
ref.delegateMethodName,
|
|
||||||
ref.delegateType,
|
|
||||||
ref.isDelegateInterface ? 1 : 0
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// placeholder
|
// placeholder
|
||||||
writer.push((String)null);
|
writer.push((String)null);
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.lang.invoke.LambdaConversionException;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
|
||||||
|
|
||||||
public class FunctionRefTests extends ScriptTestCase {
|
public class FunctionRefTests extends ScriptTestCase {
|
||||||
|
|
||||||
|
@ -193,14 +192,15 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
|
exec("List l = [2, 1]; l.sort(Integer::bogus); return l.get(0);");
|
||||||
});
|
});
|
||||||
assertThat(e.getMessage(), startsWith("Unknown reference"));
|
assertThat(e.getMessage(), containsString("function reference [Integer::bogus/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testQualifiedMethodMissing() {
|
public void testQualifiedMethodMissing() {
|
||||||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
|
exec("List l = [2, 1]; l.sort(org.joda.time.ReadableDateTime::bogus); return l.get(0);", false);
|
||||||
});
|
});
|
||||||
assertThat(e.getMessage(), startsWith("Unknown reference"));
|
assertThat(e.getMessage(),
|
||||||
|
containsString("function reference [org.joda.time.ReadableDateTime::bogus/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClassMissing() {
|
public void testClassMissing() {
|
||||||
|
@ -223,11 +223,12 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.add(Integer::bogus); return l.get(0);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Cannot convert function reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("cannot convert function reference [Integer::bogus] to a non-functional interface [def]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIncompatible() {
|
public void testIncompatible() {
|
||||||
expectScriptThrows(BootstrapMethodError.class, () -> {
|
expectScriptThrows(ClassCastException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::startsWith); return l.get(0);");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -236,28 +237,32 @@ public class FunctionRefTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("Optional.empty().orElseGet(String::startsWith);");
|
exec("Optional.empty().orElseGet(String::startsWith);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnough() {
|
public void testWrongArityNotEnough() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
exec("List l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage().contains("Unknown reference"));
|
assertThat(expected.getMessage(), containsString(
|
||||||
|
"function reference [String::isEmpty/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityDef() {
|
public void testWrongArityDef() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
|
exec("def y = Optional.empty(); return y.orElseGet(String::startsWith);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::startsWith/0] matching [java.util.function.Supplier"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnoughDef() {
|
public void testWrongArityNotEnoughDef() {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
exec("def l = new ArrayList(); l.add(2); l.add(1); l.sort(String::isEmpty);");
|
||||||
});
|
});
|
||||||
assertThat(expected.getMessage(), containsString("Unknown reference"));
|
assertThat(expected.getMessage(),
|
||||||
|
containsString("function reference [String::isEmpty/2] matching [java.util.Comparator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReturnVoid() {
|
public void testReturnVoid() {
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class LambdaTests extends ScriptTestCase {
|
||||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||||
exec("def y = Optional.empty(); return y.orElseGet(x -> x);");
|
exec("def y = Optional.empty(); return y.orElseGet(x -> x);");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage(), expected.getMessage().contains("Incorrect number of parameters"));
|
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWrongArityNotEnough() {
|
public void testWrongArityNotEnough() {
|
||||||
|
@ -200,7 +200,7 @@ public class LambdaTests extends ScriptTestCase {
|
||||||
exec("def l = new ArrayList(); l.add(1); l.add(1); "
|
exec("def l = new ArrayList(); l.add(1); l.add(1); "
|
||||||
+ "return l.stream().mapToInt(() -> 5).sum();");
|
+ "return l.stream().mapToInt(() -> 5).sum();");
|
||||||
});
|
});
|
||||||
assertTrue(expected.getMessage().contains("Incorrect number of parameters"));
|
assertTrue(expected.getMessage(), expected.getMessage().contains("due to an incorrect number of arguments"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testLambdaInFunction() {
|
public void testLambdaInFunction() {
|
||||||
|
|
Loading…
Reference in New Issue