You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/03/21 15:30:53 UTC
[groovy] 01/02: GROOVY-8243: support closure for functional interface that extends trait
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit fe84dd763219f86cd28409f909c4522ed71c851d
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Mar 21 10:15:57 2022 -0500
GROOVY-8243: support closure for functional interface that extends trait
---
.../groovy/runtime/DefaultGroovyMethods.java | 41 ++++++++---------
.../groovy/runtime/ProxyGeneratorAdapter.java | 51 +++++++++++-----------
.../traitx/TraitASTTransformationTest.groovy | 32 +++++++++++---
3 files changed, 69 insertions(+), 55 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index b47fb41..69e6c3c 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -55,6 +55,7 @@ import groovy.util.OrderBy;
import groovy.util.PermutationGenerator;
import groovy.util.ProxyGenerator;
import org.apache.groovy.io.StringBuilderWriter;
+import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.reflection.MixinInMetaClass;
@@ -12310,38 +12311,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 23c4017..6869e83 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.MethodVisitor;
import org.objectweb.asm.Opcodes;
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;
@@ -201,20 +199,16 @@ public class ProxyGeneratorAdapter extends ClassVisitor implements Opcodes {
cachedNoArgConstructor = constructor;
}
- private Class adjustSuperClass(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<ClassNode>();
- // 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);
@@ -225,30 +219,35 @@ public class ProxyGeneratorAdapter extends ClassVisitor implements Opcodes {
cu.compile(Phases.CONVERSION);
su.getAST().addClass(cn);
cu.compile(Phases.CLASS_GENERATION);
- @SuppressWarnings("unchecked")
- List<GroovyClass> classes = (List<GroovyClass>) cu.getClasses();
+ List<GroovyClass> classes = cu.getClasses();
for (GroovyClass groovyClass : classes) {
if (groovyClass.getName().equals(name)) {
return loader.defineClass(name, groovyClass.getBytes());
}
}
}
- 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 228edeb..133b960 100644
--- a/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -1484,7 +1484,7 @@ final class TraitASTTransformationTest {
}
@Test
- void testSAMCoercionOfTraitOnAssignment() {
+ void testSAMCoercion1() {
assertScript '''
trait SAMTrait {
String foo() { bar()+bar() }
@@ -1508,7 +1508,7 @@ final class TraitASTTransformationTest {
}
@Test
- void testSAMCoercionOfTraitOnMethod() {
+ void testSAMCoercion2() {
assertScript '''
trait SAMTrait {
String foo() { bar()+bar() }
@@ -1537,26 +1537,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'
'''
}