I hit this whilst working on integrating Eclipse JDT and the groovy compiler. It makes a difference whether the supertype is from a .class file (like java.util.List in this case) or a user defined interface. In this case we get the right error:
— A.groovy —
class A extends I {}
interface I {}
—
groovyc A.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, A.groovy: 1: You are not allowed to extend the inter
face 'I', use implements instead.
@ line 1, column 1.
class A extends I {}
^
1 error
But as reported by the raiser, if the interface is accessed through a .class file, there is no error, just the NPE.
The reason is that the ClassNode hierarchies are set up differently in the two cases. In the case of a user defined interface (like my I), the ClassNode for I is built specifying that the superclass of the interface I is Object through this ClassNode constructor (that doesn't feel quite right, object isnt the superclass of an interface)
public ClassNode(String name, int modifiers, ClassNode superClass)
{
this(name, modifiers, superClass, EMPTY_ARRAY, MixinNode.EMPTY_ARRAY);
}
In the case of a node created for a .class file (like java.util.List) the class is half created then subject to lazy initialization. The lazy init code in Java5.configureClassNode():
Class sc = clazz.getSuperclass();
if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit,clazz.getGenericSuperclass(),sc));
doesn't give the ClassNode for List a superclass (because clazz.getSuperclass() for an interface returns null).
The ClassNode built for List can then not be handled by the code in Verifier.getMetaClassField()
ClassNode current = node;
while (current!=null && current!=ClassHelper.OBJECT_TYPE)
{
current = current.getSuperClass();
ret = current.getDeclaredField("metaClass");
if (ret==null) continue;
if (Modifier.isPrivate(ret.getModifiers())) continue;
return ret;
}
because on some time through the loop, current will be List - its superclass will be grabbed - it will be null and then getDeclaredField will break with an NPE.
I haven't submitted a patch to fix it because I'm not sure how you'd want to address it. I think the ClassNode hierarchies ought to be the same regardless of the route taken to build them (interfaces not having a superclass).
I'd probably rewrite that superclass navigating method 'getMetaClassField' so it can never dereference a null. This variant works and produces the correct error message (no NPE):
private FieldNode getMetaClassField(ClassNode node) {
FieldNode ret = node.getDeclaredField("metaClass");
if (ret!=null) return ret;
ClassNode current = node.getSuperClass();
while (current!=null && current!=ClassHelper.OBJECT_TYPE)
{
ret = current.getDeclaredField("metaClass");
if (ret!=null && !Modifier.isPrivate(ret.getModifiers())) return ret;
current = current.getSuperClass();
}
return null;
}
I hit this whilst working on integrating Eclipse JDT and the groovy compiler. It makes a difference whether the supertype is from a .class file (like java.util.List in this case) or a user defined interface. In this case we get the right error:
— A.groovy —
class A extends I {}
interface I {}
—
groovyc A.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, A.groovy: 1: You are not allowed to extend the inter
face 'I', use implements instead.
@ line 1, column 1.
class A extends I {}
^
1 error
But as reported by the raiser, if the interface is accessed through a .class file, there is no error, just the NPE.
The reason is that the ClassNode hierarchies are set up differently in the two cases. In the case of a user defined interface (like my I), the ClassNode for I is built specifying that the superclass of the interface I is Object through this ClassNode constructor (that doesn't feel quite right, object isnt the superclass of an interface)
public ClassNode(String name, int modifiers, ClassNode superClass)
{ this(name, modifiers, superClass, EMPTY_ARRAY, MixinNode.EMPTY_ARRAY); }In the case of a node created for a .class file (like java.util.List) the class is half created then subject to lazy initialization. The lazy init code in Java5.configureClassNode():
Class sc = clazz.getSuperclass();
if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit,clazz.getGenericSuperclass(),sc));
doesn't give the ClassNode for List a superclass (because clazz.getSuperclass() for an interface returns null).
The ClassNode built for List can then not be handled by the code in Verifier.getMetaClassField()
ClassNode current = node;
{ current = current.getSuperClass(); ret = current.getDeclaredField("metaClass"); if (ret==null) continue; if (Modifier.isPrivate(ret.getModifiers())) continue; return ret; }while (current!=null && current!=ClassHelper.OBJECT_TYPE)
because on some time through the loop, current will be List - its superclass will be grabbed - it will be null and then getDeclaredField will break with an NPE.
I haven't submitted a patch to fix it because I'm not sure how you'd want to address it. I think the ClassNode hierarchies ought to be the same regardless of the route taken to build them (interfaces not having a superclass).
I'd probably rewrite that superclass navigating method 'getMetaClassField' so it can never dereference a null. This variant works and produces the correct error message (no NPE):
private FieldNode getMetaClassField(ClassNode node) {
{ ret = current.getDeclaredField("metaClass"); if (ret!=null && !Modifier.isPrivate(ret.getModifiers())) return ret; current = current.getSuperClass(); }FieldNode ret = node.getDeclaredField("metaClass");
if (ret!=null) return ret;
ClassNode current = node.getSuperClass();
while (current!=null && current!=ClassHelper.OBJECT_TYPE)
return null;
}