Hello there,
On Feb 4, 2013, at 12:01 PM, Ondřej Čada wrote:
> Just for the record, I've tentatively tried to write my own AST which does the compile-time mix (source for reference below -- if you or anyone see some blunder in there which might bit my behind in future, I'll be very glad if you could warn me), and TypeChecked seems to work all right. Groovy rocks indeed, just as you people who work on it!
Hm, I've bumped into a problem quite soon :(
So as the static type checking works both in the class into which the methods are injected and also in the "donor" classes (which of course can call methods from the main class), I am mixing all the methods back to the donors.
And it does not work in a weird way: compiles all right, the static type checker checks all OK, but at runtime, it fails:
=== the AST (sans boilerplate)
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class StaticMixinASTTransformation implements ASTTransformation {
public void visit(ASTNode[] nodes, SourceUnit source) {
nodes?.each { if (it instanceof ClassNode) visitClass(it) }
// has to add back all methods to all categories for static type checker
catsByClass.each { classNode,categories ->
categories.each { mixinClassToClass(classNode,it,false) }
}
}
private def catsByClass=[:].withDefault {[]}
private void mixinClassToClass(categoryNode,classNode,record=true) {
println "from $categoryNode.name to $classNode.name (record $record)"
if (record) catsByClass[classNode]<<categoryNode
categoryNode.methods?.each {
println " adding $
it.name (${classNode.getMethod(
it.name,it.parameters)?'exists':'new'})"
if (!classNode.getMethod(
it.name,it.parameters))
classNode.addMethod(it) //*
}
categoryNode.properties?.each {
classNode.addProperty(it)
}
categoryNode.fields?.each {
classNode.addField(it)
}
}
private void visitClass(classNode) {
def category=classNode.annotations?.find {
it.classNode.name=="cz.ocs.StaticMixin" }
def expr=category?.getMember('value')
if (expr instanceof ClassExpression) mixinClassToClass(expr.type,classNode)
else if (expr instanceof ListExpression) expr?.expressions?.each { mixinClassToClass(it.type,classNode) }
else println "Unknown annotation value: ${expr.getClass()} -> ${expr}"
}
}
=== the test source
@groovy.transform.TypeChecked class E {
def foo() { println "E's foo $this";bax() }
static def bar() { println "bar $this";baz() }
}
@groovy.transform.TypeChecked @StaticMixin([E]) class C {
def bax() { println "bax $this" }
static def baz() { println "baz $this" }
}
@groovy.transform.TypeChecked class Test {
static void main(av) {
new C().foo()
C.bar()
}
}
=== the log
============= compiling Test ===
from E to C (record true)
adding foo (new)
adding bar (new)
from C to E (record false)
adding bax (new)
adding baz (new)
adding foo (exists)
adding bar (exists)
============= running ../Test.groovy ===
Exception in thread "main" java.lang.VerifyError: (class: C, method: foo signature: ()Ljava/lang/Object;) Incompatible object argument for function call
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
at java.lang.Class.getDeclaredConstructors(Class.java:1836)
at org.codehaus.groovy.reflection.CachedClass$2$1.run(CachedClass.java:69)
at java.security.AccessController.doPrivileged(Native Method)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:66)
at org.codehaus.groovy.reflection.CachedClass$2.initValue(CachedClass.java:64)
at org.codehaus.groovy.util.LazyReference.getLocked(LazyReference.java:46)
at org.codehaus.groovy.util.LazyReference.get(LazyReference.java:33)
at org.codehaus.groovy.reflection.CachedClass.getConstructors(CachedClass.java:258)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:189)
at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:193)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createNormalMetaClass(MetaClassRegistry.java:157)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:147)
at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:130)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:175)
at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:192)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:309)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:806)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallConstructorSite(CallSiteArray.java:84)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186)
at Test.main(Test.groovy:21)
===
Can you perhaps see what's wrong and advice how to fix it? Suspecting the cross-adding methods would be the culprit, I've tried also
classNode.addMethod(record?it:new MethodNode(
it.name,it.modifiers,it.returnType, it.parameters,it.exceptions,EmptyStatement.INSTANCE))
at the //* line, but it did not help either :(
Thanks a big lot,