You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/03/21 04:45:59 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-8243: support closure for functional interface that extends trait

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

paulk pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new 5d284dc  GROOVY-8243: support closure for functional interface that extends trait
5d284dc is described below

commit 5d284dc6041dc4c43beb5db7a43d6b9ad56b66fb
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Mar 20 14:44:23 2022 -0500

    GROOVY-8243: support closure for functional interface that extends trait
---
 .../groovy/runtime/DefaultGroovyMethods.java       | 41 +++++++++---------
 .../groovy/runtime/ProxyGeneratorAdapter.java      | 48 +++++++++++-----------
 .../traitx/TraitASTTransformationTest.groovy       | 32 +++++++++++----
 3 files changed, 68 insertions(+), 53 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 3f8c806..4f72bc3 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -56,6 +56,7 @@ import groovy.util.PermutationGenerator;
 import groovy.util.ProxyGenerator;
 import org.apache.groovy.io.StringBuilderWriter;
 import org.apache.groovy.util.ReversedList;
+import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.classgen.Verifier;
 import org.codehaus.groovy.reflection.ClassInfo;
 import org.codehaus.groovy.reflection.MixinInMetaClass;
@@ -11959,38 +11960,34 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     }
 
     /**
-     * Coerces the closure to an implementation of the given class.  The class
+     * Coerces the closure to an implementation of the given class. The class
      * is assumed to be an interface or class with a single method definition.
      * The closure is used as the implementation of that single method.
      *
-     * @param cl    the implementation of the single method
-     * @param clazz the target type
-     * @return a Proxy of the given type which wraps this closure.
+     * @param impl the implementation of the single method
+     * @param type the target type
+     * @return A proxy of the given type which wraps this closure.
+     *
      * @since 1.0
      */
-    @SuppressWarnings("unchecked")
-    public static <T> T asType(Closure cl, Class<T> clazz) {
-        if (clazz.isInterface() && !(clazz.isInstance(cl))) {
-            if (Traits.isTrait(clazz)) {
-                Method samMethod = CachedSAMClass.getSAMMethod(clazz);
-                if (samMethod!=null) {
-                    Map impl = Collections.singletonMap(samMethod.getName(),cl);
-                    return (T) ProxyGenerator.INSTANCE.instantiateAggregate(impl, Collections.singletonList(clazz));
+    public static <T> T asType(final Closure impl, final Class<T> type) {
+        if (type.isInterface() && !type.isInstance(impl)) {
+            if (Traits.isTrait(type) || ClassHelper.make(type).getAllInterfaces().stream().anyMatch(Traits::isTrait)) { // GROOVY-8243
+                Method sam = CachedSAMClass.getSAMMethod(type);
+                if (sam != null) {
+                    return (T) ProxyGenerator.INSTANCE.instantiateAggregate(Collections.singletonMap(sam.getName(), impl), Collections.singletonList(type));
                 }
             }
-            return (T) Proxy.newProxyInstance(
-                    clazz.getClassLoader(),
-                    new Class[]{clazz},
-                    new ConvertedClosure(cl));
+            return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new ConvertedClosure(impl));
         }
+
         try {
-            return asType((Object) cl, clazz);
-        } catch (GroovyCastException ce) {
+            return asType((Object) impl, type);
+        } catch (GroovyCastException gce) {
             try {
-                return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(cl, clazz);
-            } catch (GroovyRuntimeException cause) {
-                throw new GroovyCastException("Error casting closure to " + clazz.getName() +
-                        ", Reason: " + cause.getMessage());
+                return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(impl, type);
+            } catch (GroovyRuntimeException gre) {
+                throw new GroovyCastException("Error casting closure to " + type.getName() + ", Reason: " + gre.getMessage());
             }
         }
     }
diff --git a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
index 79b6729..13e571f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
@@ -23,7 +23,6 @@ import groovy.lang.GeneratedGroovyProxy;
 import groovy.lang.GroovyClassLoader;
 import groovy.lang.GroovyObject;
 import groovy.lang.GroovyRuntimeException;
-import groovy.transform.Trait;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
@@ -42,7 +41,6 @@ import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
 
-import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -236,20 +234,16 @@ public class ProxyGeneratorAdapter extends ClassVisitor {
         cachedNoArgConstructor = constructor;
     }
 
-    private Class<?> adjustSuperClass(final Class<?> superClass, final Class<?>[] interfaces) {
-        boolean isSuperClassAnInterface = superClass.isInterface();
-        if (!isSuperClassAnInterface) {
+    private Class<?> adjustSuperClass(final Class<?> superClass, Class<?>[] interfaces) {
+        if (!superClass.isInterface()) {
             return superClass;
         }
-        Class<?> result = Object.class;
-        Set<ClassNode> traits = new LinkedHashSet<>();
-        // check if it's a trait
-        collectTraits(superClass, traits);
-        if (interfaces != null) {
-            for (Class<?> anInterface : interfaces) {
-                collectTraits(anInterface, traits);
-            }
+        if (interfaces == null || interfaces.length == 0) {
+            interfaces = new Class[]{superClass};
         }
+        assert Arrays.asList(interfaces).contains(superClass);
+
+        Set<ClassNode> traits = collectTraits(interfaces);
         if (!traits.isEmpty()) {
             String name = superClass.getName() + "$TraitAdapter";
             ClassNode cn = new ClassNode(name, ACC_PUBLIC | ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, traits.toArray(ClassNode.EMPTY_ARRAY), null);
@@ -267,22 +261,28 @@ public class ProxyGeneratorAdapter extends ClassVisitor {
                 }
             }
         }
-        return result;
+
+        return Object.class;
     }
 
-    private static void collectTraits(final Class<?> clazz, final Set<ClassNode> traits) {
-        Annotation annotation = clazz.getAnnotation(Trait.class);
-        if (annotation != null) {
-            ClassNode trait = ClassHelper.make(clazz);
-            traits.add(trait.getPlainNodeReference());
-            LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<ClassNode>();
-            Traits.collectSelfTypes(trait, selfTypes, true, true);
-            for (ClassNode selfType : selfTypes) {
-                if (Traits.isTrait(selfType)) {
-                    traits.add(selfType.getPlainNodeReference());
+    private static Set<ClassNode> collectTraits(final Class<?>[] interfaces) {
+        Set<ClassNode> traits = new LinkedHashSet<>();
+        for (Class<?> face : interfaces) {
+            for (ClassNode node : ClassHelper.make(face).getAllInterfaces()) {
+                if (Traits.isTrait(node)) {
+                    traits.add(node.getPlainNodeReference());
+                    // check trait for @SelfType types that are / extend traits
+                    LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<>();
+                    Traits.collectSelfTypes(node, selfTypes, true, true);
+                    for (ClassNode selfType : selfTypes) {
+                        if (Traits.isTrait(selfType)) {
+                            traits.add(selfType.getPlainNodeReference());
+                        }
+                    }
                 }
             }
         }
+        return traits;
     }
 
     private static InnerLoader createInnerLoader(final ClassLoader parent, final Class<?>[] interfaces) {
diff --git a/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy b/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
index 09a2fb6..b266c38 100644
--- a/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -1497,7 +1497,7 @@ final class TraitASTTransformationTest {
     }
 
     @Test
-    void testSAMCoercionOfTraitOnAssignment() {
+    void testSAMCoercion1() {
         assertScript '''
             trait SAMTrait {
                 String foo() { bar()+bar() }
@@ -1521,7 +1521,7 @@ final class TraitASTTransformationTest {
     }
 
     @Test
-    void testSAMCoercionOfTraitOnMethod() {
+    void testSAMCoercion2() {
         assertScript '''
             trait SAMTrait {
                 String foo() { bar()+bar() }
@@ -1550,26 +1550,44 @@ final class TraitASTTransformationTest {
     }
 
     @Test
-    void testImplicitSAMCoercionBug() {
+    void testSAMCoercion3() {
         assertScript '''
             trait Greeter {
-                String greet() { "Hello $name" }
                 abstract String getName()
+                String greet() { "Hello $name" }
             }
             Greeter greeter = { 'Alice' }
             assert greeter.greet() == 'Hello Alice'
+            assert greeter.getName().equals('Alice')
         '''
     }
 
     @Test
-    void testExplicitSAMCoercionBug() {
+    void testSAMCoercion4() {
         assertScript '''
             trait Greeter {
-                String greet() { "Hello $name" }
                 abstract String getName()
+                String greet() { "Hello $name" }
             }
-            Greeter greeter = { 'Alice' } as Greeter
+            def greeter = { 'Alice' } as Greeter
             assert greeter.greet() == 'Hello Alice'
+            assert greeter.getName().equals('Alice')
+        '''
+    }
+
+    @Test // GROOVY-8243
+    void testSAMCoercion5() {
+        assertScript '''
+            trait T {
+                abstract def foo(int i)
+                def bar(double j) { "trait $j".toString() }
+            }
+            interface I extends T {
+            }
+
+            def obj = { "proxy $it".toString() } as I
+            assert obj.foo(123) == 'proxy 123'
+            assert obj.bar(4.5) == 'trait 4.5'
         '''
     }