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']) {