This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY-11681
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 896a81a963a71e88422db8aafcc2e2a65159cc80
Author: Eric Milles <eric.mil...@thomsonreuters.com>
AuthorDate: Thu Jul 10 11:15:13 2025 -0500

    GROOVY-11681: add `ExecutorService#submit(Closure)` extension
---
 .../groovy/runtime/DefaultGroovyMethods.java       | 28 ++++++++++++++++++++++
 .../codehaus/groovy/runtime/MetaClassHelper.java   | 20 +++++++++-------
 .../groovy/transform/stc/ClosuresSTCTest.groovy    | 15 ++++++++++++
 .../groovy/groovy/transform/stc/LambdaTest.groovy  | 16 +++++++++++++
 4 files changed, 70 insertions(+), 9 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java 
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 38e1e8f11a..838d327597 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -151,6 +151,9 @@ import java.util.TimerTask;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -14081,6 +14084,31 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
         return answer;
     }
 
+    
//--------------------------------------------------------------------------
+    // submit
+
+    /**
+     * Submits a value-returning task for execution and returns a {@link 
Future}
+     * representing the pending results of the task.
+     *
+     * <pre class="groovyTestCase">
+     * def executor = java.util.concurrent.Executors.newSingleThreadExecutor()
+     * try {
+     *   def future = executor.submit {
+     *     return 'works'
+     *   }
+     *   assert future.get() == 'works'
+     * } finally {
+     *   executor.shutdown()
+     * }
+     * </pre>
+     *
+     * @since 5.0.0
+     */
+    public static <T> Future<T> submit(ExecutorService self, Closure<T> task) {
+        return self.submit((Callable<T>) task);
+    }
+
     
//--------------------------------------------------------------------------
     // subsequences
 
diff --git a/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java 
b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
index d74e032a93..8203048fc0 100644
--- a/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
@@ -282,20 +282,20 @@ public class MetaClassHelper {
             // by any means could let it look like a direct match
             // we want to add one, because there is an interface between
             // the interface we search for and the interface we are in.
-            if (sub != -1) sub++;
+            if (sub != -1) sub += 1;
             // we are interested in the longest path only
             max = Math.max(max, sub);
         }
         // we do not add one for super classes, only for interfaces
         int superClassMax = getMaximumInterfaceDistance(c.getSuperclass(), 
interfaceClass);
-        if (superClassMax != -1) superClassMax++;
+        if (superClassMax != -1) superClassMax += 1;
         return Math.max(max, superClassMax);
     }
 
     private static int getParameterCount(final Class<?> closureOrLambdaClass) {
         int parameterCount = -2;
-        if (GeneratedClosure.class.isAssignableFrom(closureOrLambdaClass)) {
-            // determine parameter count from generated "doCall" method(s)
+        if (isClosureLiteral(closureOrLambdaClass)) {
+            // determine parameter count from generated "doCall" methods
             for (Method m : closureOrLambdaClass.getDeclaredMethods()) {
                 if ("doCall".equals(m.getName())) {
                     if (parameterCount != -2) {
@@ -309,6 +309,10 @@ public class MetaClassHelper {
         return parameterCount;
     }
 
+    private static boolean isClosureLiteral(final Class<?> candidate) {
+        return candidate != null && 
GeneratedClosure.class.isAssignableFrom(candidate);
+    }
+
     private static long calculateParameterDistance(final Class<?> argument, 
final CachedClass parameter) {
         /*
          * note: when shifting with 32 bit, you should only shift on a long. 
If you do
@@ -317,13 +321,13 @@ public class MetaClassHelper {
          */
 
         Class<?> parameterClass = parameter.getTheClass();
-        if (parameterClass == argument) {
+        if (parameterClass == argument || (parameterClass == Closure.class && 
isClosureLiteral(argument))) { // GROOVY-10714, GROOVY-11681
             return 0; // exact match
         }
 
         if (parameter.isInterface()) {
-            long dist = getMaximumInterfaceDistance(argument, parameterClass);
-            if (dist >= 0 || (argument != null && 
!Closure.class.isAssignableFrom(argument))) {
+            int dist = argument == null ? 2 : 
getMaximumInterfaceDistance(argument, parameterClass);
+            if (dist >= 0 || !Closure.class.isAssignableFrom(argument)) {
                 return dist << INTERFACE_SHIFT;
             }
         }
@@ -370,8 +374,6 @@ public class MetaClassHelper {
             return 0; // exact match -- GROOVY-9367
         } else if (parameterClass == Object.class) {
             return 1; // tight match
-        } else if (parameterClass.isInterface()) {
-            return 2L << INTERFACE_SHIFT;
         } else {
             // compute the distance to Object
             for (Class<?> c = parameterClass; c != null && c != Object.class; 
c = c.getSuperclass()) {
diff --git a/src/test/groovy/groovy/transform/stc/ClosuresSTCTest.groovy 
b/src/test/groovy/groovy/transform/stc/ClosuresSTCTest.groovy
index 856cc3c4bd..737a9c5243 100644
--- a/src/test/groovy/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -915,6 +915,21 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-11681
+    void testSAMsInMethodSelection9() {
+        assertScript '''
+            def executor = 
java.util.concurrent.Executors.newSingleThreadExecutor()
+            try {
+                def future = executor.submit { ->
+                    return 'works'
+                }
+                assert future.get() == 'works'
+            } finally {
+                executor.shutdown()
+            }
+        '''
+    }
+
     // GROOVY-6238
     void testDirectMethodCallOnClosureExpression() {
         assertScript '''
diff --git a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy 
b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
index 602f0c9335..d180174973 100644
--- a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
@@ -927,6 +927,22 @@ final class LambdaTest {
         '''
     }
 
+    // GROOVY-11681
+    @Test
+    void testFunctionalInterface10() {
+        assertScript shell, '''
+            def executor = 
java.util.concurrent.Executors.newSingleThreadExecutor()
+            try {
+                def future = executor.submit(() -> {
+                    return 'works'
+                })
+                assert future.get() == 'works'
+            } finally {
+                executor.shutdown()
+            }
+        '''
+    }
+
     @Test
     void testFunctionWithUpdatingLocalVariable() {
         for (mode in ['','static']) {

Reply via email to