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 call = signature.substring(separator+1, separator2);
|
||||
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;
|
||||
Class<?> interfaceType = method.typeParameters.get(i - 1 - replaced);
|
||||
if (signature.charAt(0) == 'S') {
|
||||
|
@ -294,11 +290,15 @@ public final class Def {
|
|||
interfaceType,
|
||||
type,
|
||||
call,
|
||||
captures);
|
||||
numCaptures);
|
||||
} else if (signature.charAt(0) == 'D') {
|
||||
// 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 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);
|
||||
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
|
||||
localMethods,
|
||||
|
@ -331,57 +331,34 @@ public final class Def {
|
|||
*/
|
||||
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
|
||||
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(interfaceType).functionalMethod;
|
||||
if (interfaceMethod == null) {
|
||||
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
||||
}
|
||||
int arity = interfaceMethod.typeParameters.size();
|
||||
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
||||
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
|
||||
PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType);
|
||||
if (interfaceMethod == null) {
|
||||
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
|
||||
}
|
||||
int arity = interfaceMethod.typeParameters.size();
|
||||
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
|
||||
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
|
||||
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. */
|
||||
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
|
||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, Class<?>... captures) throws Throwable {
|
||||
final FunctionRef ref;
|
||||
if ("this".equals(type)) {
|
||||
// user written method
|
||||
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(clazz).functionalMethod;
|
||||
if (interfaceMethod == null) {
|
||||
throw new IllegalArgumentException("Cannot convert function reference [" + type + "::" + call + "] " +
|
||||
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface");
|
||||
}
|
||||
int arity = interfaceMethod.typeParameters.size() + captures.length;
|
||||
LocalMethod localMethod = localMethods.get(Locals.buildLocalMethodKey(call, arity));
|
||||
if (localMethod == null) {
|
||||
// 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.
|
||||
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));
|
||||
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, int captures) throws Throwable {
|
||||
final FunctionRef ref = FunctionRef.create(painlessLookup, localMethods, null, clazz, type, call, captures);
|
||||
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, ref.factoryMethodType.parameterArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,17 +20,17 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Reference to a function or lambda.
|
||||
* <p>
|
||||
* Once you have created one of these, you have "everything you need" to call {@link LambdaBootstrap}
|
||||
* either statically from bytecode with invokedynamic, or at runtime from Java.
|
||||
* Contains all the values necessary to write the instruction to initiate a
|
||||
* {@link LambdaBootstrap} for either a function reference or a user-defined
|
||||
* lambda function.
|
||||
*/
|
||||
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 */
|
||||
public final String interfaceMethodName;
|
||||
/** factory (CallSite) method signature */
|
||||
public final MethodType factoryMethodType;
|
||||
/** functional interface method signature */
|
||||
public final MethodType interfaceMethodType;
|
||||
/** class of the delegate method to be called */
|
||||
public final String delegateClassName;
|
||||
/** whether a call is made on a delegate interface */
|
||||
public final boolean isDelegateInterface;
|
||||
/** the invocation type of the delegate method */
|
||||
public final int delegateInvokeType;
|
||||
/** the name of the delegate method */
|
||||
public final String delegateMethodName;
|
||||
/** delegate method signature */
|
||||
public final MethodType delegateMethodType;
|
||||
/** factory (CallSite) method signature */
|
||||
public final MethodType factoryMethodType;
|
||||
|
||||
/** interface method */
|
||||
public final PainlessMethod interfaceMethod;
|
||||
/** delegate method type parameters */
|
||||
public final List<Class<?>> delegateTypeParameters;
|
||||
/** delegate method return type */
|
||||
public final Class<?> delegateReturnType;
|
||||
private FunctionRef(
|
||||
String interfaceMethodName, MethodType interfaceMethodType,
|
||||
String delegateClassName, boolean isDelegateInterface,
|
||||
int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
|
||||
MethodType factoryMethodType) {
|
||||
|
||||
/** factory method type descriptor */
|
||||
public final String factoryDescriptor;
|
||||
/** functional interface method as type */
|
||||
public final Type interfaceType;
|
||||
/** delegate method type method as type */
|
||||
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.interfaceMethodName = interfaceMethodName;
|
||||
this.interfaceMethodType = interfaceMethodType;
|
||||
this.delegateClassName = delegateClassName;
|
||||
this.isDelegateInterface = isDelegateInterface;
|
||||
this.delegateInvokeType = delegateInvokeType;
|
||||
this.delegateMethodName = delegateMethodName;
|
||||
this.delegateMethodType = delegateMethodType.dropParameterTypes(0, numCaptures);
|
||||
this.isDelegateInterface = false;
|
||||
|
||||
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;
|
||||
this.delegateMethodType = delegateMethodType;
|
||||
this.factoryMethodType = factoryMethodType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_UTIL_TYPE;
|
||||
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.PAINLESS_ERROR_TYPE;
|
||||
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
|
||||
|
@ -439,4 +440,18 @@ public final class MethodWriter extends GeneratorAdapter {
|
|||
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> setterMethodHandles;
|
||||
|
||||
public final PainlessMethod functionalMethod;
|
||||
public final PainlessMethod functionalInterfaceMethod;
|
||||
|
||||
PainlessClass(Map<String, PainlessConstructor> constructors,
|
||||
Map<String, PainlessMethod> staticMethods, Map<String, PainlessMethod> methods,
|
||||
Map<String, PainlessField> staticFields, Map<String, PainlessField> fields,
|
||||
Map<String, MethodHandle> getterMethodHandles, Map<String, MethodHandle> setterMethodHandles,
|
||||
PainlessMethod functionalMethod) {
|
||||
PainlessMethod functionalInterfaceMethod) {
|
||||
|
||||
this.constructors = Collections.unmodifiableMap(constructors);
|
||||
|
||||
|
@ -54,6 +54,6 @@ public final class PainlessClass {
|
|||
this.getterMethodHandles = Collections.unmodifiableMap(getterMethodHandles);
|
||||
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> setterMethodHandles;
|
||||
|
||||
PainlessMethod functionalMethod;
|
||||
PainlessMethod functionalInterfaceMethod;
|
||||
|
||||
PainlessClassBuilder() {
|
||||
constructors = new HashMap<>();
|
||||
|
@ -49,11 +49,11 @@ final class PainlessClassBuilder {
|
|||
getterMethodHandles = new HashMap<>();
|
||||
setterMethodHandles = new HashMap<>();
|
||||
|
||||
functionalMethod = null;
|
||||
functionalInterfaceMethod = null;
|
||||
}
|
||||
|
||||
PainlessClass build() {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
Objects.requireNonNull(targetClass);
|
||||
|
||||
|
@ -83,6 +91,14 @@ public final class PainlessLookup {
|
|||
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) {
|
||||
Objects.requireNonNull(targetClass);
|
||||
Objects.requireNonNull(methodName);
|
||||
|
@ -111,6 +127,14 @@ public final class PainlessLookup {
|
|||
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) {
|
||||
Objects.requireNonNull(targetClass);
|
||||
Objects.requireNonNull(fieldName);
|
||||
|
@ -134,4 +158,20 @@ public final class PainlessLookup {
|
|||
|
||||
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) {
|
||||
java.lang.reflect.Method javaMethod = javaMethods.get(0);
|
||||
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;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.DefBootstrap;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
|
@ -35,8 +34,6 @@ import org.objectweb.asm.Type;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a capturing function reference.
|
||||
*/
|
||||
|
@ -76,23 +73,8 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
|||
defPointer = null;
|
||||
// static case
|
||||
if (captured.clazz != def.class) {
|
||||
try {
|
||||
ref = FunctionRef.resolveFromLookup(locals.getPainlessLookup(), expected,
|
||||
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);
|
||||
}
|
||||
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location,
|
||||
expected, PainlessLookupUtility.typeToCanonicalTypeName(captured.clazz), call, 1);
|
||||
}
|
||||
actual = expected;
|
||||
}
|
||||
|
@ -114,17 +96,7 @@ public final class ECapturingFunctionRef extends AExpression implements ILambda
|
|||
} else {
|
||||
// typed interface, typed implementation
|
||||
writer.visitVarInsn(MethodWriter.getType(captured.clazz).getOpcode(Opcodes.ILOAD), captured.getSlot());
|
||||
writer.invokeDynamic(
|
||||
ref.interfaceMethodName,
|
||||
ref.factoryDescriptor,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
ref.interfaceType,
|
||||
ref.delegateClassName,
|
||||
ref.delegateInvokeType,
|
||||
ref.delegateMethodName,
|
||||
ref.delegateType,
|
||||
ref.isDelegateInterface ? 1 : 0
|
||||
);
|
||||
writer.invokeLambdaCall(ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,22 +19,16 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
|
||||
import org.elasticsearch.painless.lookup.PainlessMethod;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Represents a function reference.
|
||||
*/
|
||||
|
@ -63,39 +57,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
|||
defPointer = "S" + type + "." + call + ",0";
|
||||
} else {
|
||||
defPointer = null;
|
||||
try {
|
||||
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);
|
||||
}
|
||||
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, type, call, 0);
|
||||
actual = expected;
|
||||
}
|
||||
}
|
||||
|
@ -104,17 +66,7 @@ public final class EFunctionRef extends AExpression implements ILambda {
|
|||
void write(MethodWriter writer, Globals globals) {
|
||||
if (ref != null) {
|
||||
writer.writeDebugInfo(location);
|
||||
writer.invokeDynamic(
|
||||
ref.interfaceMethodName,
|
||||
ref.factoryDescriptor,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
ref.interfaceType,
|
||||
ref.delegateClassName,
|
||||
ref.delegateInvokeType,
|
||||
ref.delegateMethodName,
|
||||
ref.delegateType,
|
||||
ref.isDelegateInterface ? 1 : 0
|
||||
);
|
||||
writer.invokeLambdaCall(ref);
|
||||
} else {
|
||||
// TODO: don't do this: its just to cutover :)
|
||||
writer.push((String)null);
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
|
||||
package org.elasticsearch.painless.node;
|
||||
|
||||
import org.elasticsearch.painless.AnalyzerCaster;
|
||||
import org.elasticsearch.painless.FunctionRef;
|
||||
import org.elasticsearch.painless.Globals;
|
||||
import org.elasticsearch.painless.Locals;
|
||||
import org.elasticsearch.painless.Locals.LocalMethod;
|
||||
import org.elasticsearch.painless.Locals.Variable;
|
||||
import org.elasticsearch.painless.Location;
|
||||
import org.elasticsearch.painless.MethodWriter;
|
||||
|
@ -40,8 +38,6 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.painless.WriterConstants.LAMBDA_BOOTSTRAP_HANDLE;
|
||||
|
||||
/**
|
||||
* Lambda expression node.
|
||||
* <p>
|
||||
|
@ -122,7 +118,7 @@ public final class ELambda extends AExpression implements ILambda {
|
|||
|
||||
} else {
|
||||
// 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) {
|
||||
throw createError(new IllegalArgumentException("Cannot pass lambda to " +
|
||||
"[" + PainlessLookupUtility.typeToCanonicalTypeName(expected) + "], not a functional interface"));
|
||||
|
@ -184,25 +180,8 @@ public final class ELambda extends AExpression implements ILambda {
|
|||
defPointer = "Sthis." + name + "," + captures.size();
|
||||
} else {
|
||||
defPointer = null;
|
||||
try {
|
||||
LocalMethod localMethod =
|
||||
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);
|
||||
}
|
||||
|
||||
ref = FunctionRef.create(
|
||||
locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", desugared.name, captures.size());
|
||||
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.invokeDynamic(
|
||||
ref.interfaceMethodName,
|
||||
ref.factoryDescriptor,
|
||||
LAMBDA_BOOTSTRAP_HANDLE,
|
||||
ref.interfaceType,
|
||||
ref.delegateClassName,
|
||||
ref.delegateInvokeType,
|
||||
ref.delegateMethodName,
|
||||
ref.delegateType,
|
||||
ref.isDelegateInterface ? 1 : 0
|
||||
);
|
||||
writer.invokeLambdaCall(ref);
|
||||
} else {
|
||||
// placeholder
|
||||
writer.push((String)null);
|
||||
|
|
|
@ -27,7 +27,6 @@ import java.lang.invoke.LambdaConversionException;
|
|||
import static java.util.Collections.singletonMap;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class FunctionRefTests extends ScriptTestCase {
|
||||
|
||||
|
@ -193,14 +192,15 @@ public class FunctionRefTests extends ScriptTestCase {
|
|||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
Exception e = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
|
@ -223,11 +223,12 @@ public class FunctionRefTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
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);");
|
||||
});
|
||||
}
|
||||
|
@ -236,28 +237,32 @@ public class FunctionRefTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
|
|
|
@ -184,7 +184,7 @@ public class LambdaTests extends ScriptTestCase {
|
|||
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
|
||||
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() {
|
||||
|
@ -200,7 +200,7 @@ public class LambdaTests extends ScriptTestCase {
|
|||
exec("def l = new ArrayList(); l.add(1); l.add(1); "
|
||||
+ "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() {
|
||||
|
|
Loading…
Reference in New Issue