Browse Source

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.
Jack Conradson 7 years ago
parent
commit
0b7fb4e7b9

+ 27 - 50
modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java

@@ -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()));
      }
 
     /**

+ 191 - 232
modules/lang-painless/src/main/java/org/elasticsearch/painless/FunctionRef.java

@@ -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;
-
-    /** 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;
+    private FunctionRef(
+            String interfaceMethodName, MethodType interfaceMethodType,
+            String delegateClassName, boolean isDelegateInterface,
+            int delegateInvokeType, String delegateMethodName, MethodType delegateMethodType,
+            MethodType factoryMethodType) {
+
+        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;
     }
 }

+ 15 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java

@@ -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
+        );
+    }
 }

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java

@@ -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;
     }
 }

+ 3 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java

@@ -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);
     }
 }

+ 40 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java

@@ -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;
+    }
 }

+ 1 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java

@@ -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);
             }
         }
     }

+ 3 - 31
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECapturingFunctionRef.java

@@ -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);
         }
     }
 

+ 2 - 50
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java

@@ -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);

+ 4 - 35
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java

@@ -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);

+ 14 - 9
modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java

@@ -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() {

+ 2 - 2
modules/lang-painless/src/test/java/org/elasticsearch/painless/LambdaTests.java

@@ -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() {