Skip to content
Permalink
Browse files
GROOVY-8243: support closure for functional interface that extends trait
  • Loading branch information
eric-milles authored and paulk-asert committed Mar 21, 2022
1 parent c346617 commit 0d6c603783c04f26895181a1a9bcb170857df67f
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 53 deletions.
@@ -56,6 +56,7 @@
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 static <T> T asType(Object[] ary, Class<T> clazz) {
}

/**
* 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());
}
}
}
@@ -23,7 +23,6 @@
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.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 ProxyGeneratorAdapter(
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 @@ private Class<?> adjustSuperClass(final Class<?> superClass, final Class<?>[] in
}
}
}
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) {
@@ -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'
'''
}

0 comments on commit 0d6c603

Please sign in to comment.