Permalink
Browse files

Add "withTraits" method to dynamically implement traits

  • Loading branch information...
1 parent 32d1738 commit 41e21072a835b7bce2e9ac6bf4826518a4de616f @melix committed Mar 27, 2014
View
5 src/main/org/codehaus/groovy/ast/tools/WideningCategories.java
@@ -588,8 +588,9 @@ public LowestUpperBoundClassNode(String name, ClassNode upper, ClassNode... inte
usesGenerics |= anInterface.isUsingGenerics();
genericsTypesList.add(anInterface.getGenericsTypes());
for (MethodNode methodNode : anInterface.getMethods()) {
- addMethod(methodNode.getName(), methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode());
- }
+ MethodNode method = addMethod(methodNode.getName(), methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode());
+ method.setDeclaringClass(anInterface); // important for static compilation!
+ }
}
setUsingGenerics(usesGenerics);
if (usesGenerics) {
View
14 src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -14885,4 +14885,18 @@ public static void filterLine(URL self, Writer writer, String charset, Closure p
return ResourceGroovyMethods.readBytes(file);
}
+ /**
+ * Dynamically wraps an instance into something which implements the
+ * supplied trait classes. It is guaranteed that the returned object
+ * will implement the trait interfaces, but the original type of the
+ * object is lost (replaced with a proxy).
+ * @param self object to be wrapped
+ * @param traits a list of trait classes
+ * @return a proxy implementing the trait interfaces
+ */
+ public static Object withTraits(Object self, Class<?>... traits) {
+ List<Class> interfaces = new ArrayList<Class>();
+ Collections.addAll(interfaces, traits);
+ return ProxyGenerator.INSTANCE.instantiateDelegate(interfaces, self);
+ }
}
View
23 src/main/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
@@ -137,7 +137,7 @@ public ProxyGeneratorAdapter(
}
this.hasWildcard = wildcard;
- Class fixedSuperClass = adjustSuperClass(superClass);
+ Class fixedSuperClass = adjustSuperClass(superClass, interfaces);
// if we have to delegate to another object, generate the appropriate delegate field
// and collect the name of the methods for which delegation is active
this.generateDelegateField = delegateClass!=null;
@@ -179,21 +179,34 @@ public ProxyGeneratorAdapter(
cachedNoArgConstructor = constructor;
}
- private Class adjustSuperClass(Class superClass) {
+ private Class adjustSuperClass(Class superClass, final Class[] interfaces) {
boolean isSuperClassAnInterface = superClass.isInterface();
if (!isSuperClassAnInterface) {
return superClass;
}
Class result = Object.class;
+ Set<String> traits = new LinkedHashSet<String>();
// check if it's a trait
- Annotation annotation = superClass.getAnnotation(Trait.class);
- if (annotation!=null) {
+ collectTraits(superClass, traits);
+ if (interfaces!=null) {
+ for (Class anInterface : interfaces) {
+ collectTraits(anInterface, traits);
+ }
+ }
+ if (!traits.isEmpty()) {
String name = superClass.getName() + "$TraitAdapter";
- result = loader.parseClass("class "+name+" implements "+superClass.getName()+" {}");
+ result = loader.parseClass("abstract class "+name+" implements "+DefaultGroovyMethods.join((Iterable)traits,",")+" {}");
}
return result;
}
+ private void collectTraits(final Class superClass, final Set<String> traits) {
+ Annotation annotation = superClass.getAnnotation(Trait.class);
+ if (annotation!=null) {
+ traits.add(superClass.getName());
+ }
+ }
+
private static InnerLoader createInnerLoader(final ClassLoader parent) {
return AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
public InnerLoader run() {
View
31 src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -49,6 +49,7 @@
import static org.codehaus.groovy.ast.tools.WideningCategories.*;
import static org.codehaus.groovy.syntax.Types.*;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.*;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType;
/**
* The main class code visitor responsible for static type checking. It will perform various inspections like checking
@@ -2510,6 +2511,7 @@ public void visitMethodCallExpression(MethodCallExpression call) {
}
}
if (typeCheckMethodsWithGenericsOrFail(chosenReceiver.getType(), args, mn.get(0), call)) {
+ returnType = adjustWithTraits(directMethodCallCandidate,chosenReceiver.getType(), args, returnType);
storeType(call, returnType);
storeTargetMethod(call, directMethodCallCandidate);
String data = chosenReceiver != null ? chosenReceiver.getData() : null;
@@ -2566,6 +2568,35 @@ public void visitMethodCallExpression(MethodCallExpression call) {
}
/**
+ * A special method handling the "withTrait" call for which the type checker knows more than
+ * what the type signature is able to tell. If "withTrait" is detected, then a new class node
+ * is created representing the list of trait interfaces.
+ *
+ * @param directMethodCallCandidate a method selected by the type checker
+ * @param receiver the receiver of the method call
+ *@param args the arguments of the method call
+ * @param returnType the original return type, as inferred by the type checker @return fixed return type if the selected method is {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#withTraits(Object, Class[]) withTraits}
+ */
+ private ClassNode adjustWithTraits(final MethodNode directMethodCallCandidate, final ClassNode receiver, final ClassNode[] args, final ClassNode returnType) {
+ if (directMethodCallCandidate instanceof ExtensionMethodNode) {
+ ExtensionMethodNode emn = (ExtensionMethodNode) directMethodCallCandidate;
+ if ("withTraits".equals(emn.getName()) && "DefaultGroovyMethods".equals(emn.getExtensionMethodNode().getDeclaringClass().getNameWithoutPackage())) {
+ List<ClassNode> nodes = new LinkedList<ClassNode>();
+ Collections.addAll(nodes, receiver.getInterfaces());
+ for (ClassNode arg : args) {
+ if (isClassClassNodeWrappingConcreteType(arg)) {
+ nodes.add(arg.getGenericsTypes()[0].getType());
+ } else {
+ nodes.add(arg);
+ }
+ }
+ return new LowestUpperBoundClassNode(returnType.getName()+"Composed", OBJECT_TYPE, nodes.toArray(new ClassNode[nodes.size()]));
+ }
+ }
+ return returnType;
+ }
+
+ /**
* add various getAt and setAt methods for primtive arrays
* @param receiver the receiver class
* @param name the name of the method
View
115 src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -644,6 +644,121 @@ assert MyEnum.X.bar == 123 && MyEnum.Y.bar == 0
'''
}
+ void testRuntimeWithTraitsDGM() {
+ assertScript '''
+ trait Flying {
+ String fly() {
+ "I'm flying!"
+ }
+ }
+ trait Speaking {
+ String speak() {
+ "I'm speaking!"
+ }
+ }
+ class Duck {}
+ def d = new Duck()
+ try {
+ d.fly()
+ d.speak()
+ } catch (MissingMethodException e) {
+ // doesn't implement Flying
+ }
+ d = d.withTraits(Flying, Speaking)
+ assert d instanceof Flying
+ assert d.fly() == "I'm flying!"
+ assert d instanceof Speaking
+ assert d.speak() == "I'm speaking!"
+ '''
+ }
+
+ void testRuntimeWithTraitsDGMAndExplicitOverride() {
+ assertScript '''
+ trait Flying {
+ String fly() {
+ "I'm flying!"
+ }
+ }
+ trait Speaking {
+ String speak() {
+ "I'm speaking!"
+ }
+ }
+ class Duck {
+ String speak() { "I'm a special duck!" }
+ }
+ def d = new Duck()
+ try {
+ d.fly()
+ d.speak()
+ } catch (MissingMethodException e) {
+ // doesn't implement Flying
+ }
+ d = d.withTraits(Flying, Speaking)
+ assert d instanceof Flying
+ assert d.fly() == "I'm flying!"
+ assert d instanceof Speaking
+ assert d.speak() == "I'm a special duck!"
+ '''
+ }
+
+ void testRuntimeWithTraitsDGMAndExplicitOverrideAndCompileStatic() {
+ assertScript '''
+ trait Flying {
+ String fly() {
+ "I'm flying!"
+ }
+ }
+ trait Speaking {
+ String speak() {
+ "I'm speaking!"
+ }
+ }
+ class Duck {
+ String speak() { "I'm a special duck!" }
+ }
+
+ @groovy.transform.CompileStatic
+ void test() {
+ def d = new Duck()
+ d = d.withTraits(Flying, Speaking)
+ assert d.fly() == "I'm flying!"
+ assert d.speak() == "I'm a special duck!"
+ }
+ test()
+ '''
+ }
+
+ void testRuntimeWithTraitsDGMAndExtraMethodCompileStatic() {
+ assertScript '''
+ trait Flying {
+ String fly() {
+ "I'm flying!"
+ }
+ }
+ trait Speaking {
+ String speak() {
+ "I'm speaking!"
+ }
+ }
+ interface Quack { String quack() }
+ class Duck implements Quack {
+ String quack() { 'Quack!' } // requires an interface to be called statically
+ String speak() { "I'm a special duck!"}
+ }
+
+ @groovy.transform.CompileStatic
+ void test() {
+ def d = new Duck()
+ d = d.withTraits(Flying, Speaking)
+ assert d.fly() == "I'm flying!"
+ assert d.speak() == "I'm a special duck!"
+ assert d.quack() == "Quack!"
+ }
+ test()
+ '''
+ }
+
void testRuntimeTraitWithMethodOfTheSameSignature() {
assertScript '''
trait Flying {

0 comments on commit 41e2107

Please sign in to comment.