This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new 444e236fd4 GROOVY-6663: `super` calls and bridge methods 444e236fd4 is described below commit 444e236fd48303b5af73550c8f1b8cce959db574 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Thu Sep 7 11:10:08 2023 -0500 GROOVY-6663: `super` calls and bridge methods --- src/main/java/groovy/lang/MetaClassImpl.java | 12 +- .../apache/groovy/ast/tools/ClassNodeUtils.java | 13 +- .../org/codehaus/groovy/classgen/Verifier.java | 160 ++++--- src/test/gls/invocation/CovariantReturnTest.groovy | 506 +++++++++++++-------- src/test/groovy/OverrideTest.groovy | 99 ++-- src/test/groovy/ThisAndSuperTest.groovy | 34 +- src/test/groovy/bugs/G4410JavaStringProducer.java | 27 -- src/test/groovy/bugs/G4410Producer1.java | 25 - src/test/groovy/bugs/G4410Producer2.java | 25 - src/test/groovy/bugs/Groovy4410Bug.groovy | 65 --- 10 files changed, 507 insertions(+), 459 deletions(-) diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index 43f235bce6..31cea6b0e3 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -508,7 +508,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { int matchedMethod = mopArrayIndex(method, c); if (matchedMethod >= 0) { methods.set(i, mopMethods[matchedMethod]); - } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) { + } else if (!useThis && !isDGM(method) && (isBridge(method) + || c == method.getDeclaringClass().getTheClass())) { methods.remove(i--); // not fit for super usage } } @@ -523,7 +524,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { if (matchedMethod >= 0) { if (useThis) e.methods = mopMethods[matchedMethod]; else e.methodsForSuper = mopMethods[matchedMethod]; - } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) { + } else if (!useThis && !isDGM(method) && (isBridge(method) + || c == method.getDeclaringClass().getTheClass())) { e.methodsForSuper = null; // not fit for super usage } } @@ -536,6 +538,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { // GROOVY-4922: Due to a numbering scheme change, find the super$number$methodName with // the highest value. If we don't, no method may be found, leading to a stack overflow! int distance = ReflectionCache.getCachedClass(c).getSuperClassDistance() - 1; + if (isBridge(method)) // GROOVY-6663 + return mopArrayIndex(method, "super$" + distance + "$" + method.getName()); while (distance > 0) { int index = mopArrayIndex(method, "super$" + distance + "$" + method.getName()); if (index >= 0) return index; @@ -562,6 +566,10 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { return -1; } + private boolean isBridge(final MetaMethod method) { + return (method.getModifiers() & Opcodes.ACC_BRIDGE) != 0; + } + private boolean isDGM(final MetaMethod method) { return method instanceof GeneratedMetaMethod || method instanceof NewMetaMethod; } diff --git a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java index 7b1bc71b3c..065237b9af 100644 --- a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java +++ b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java @@ -211,16 +211,13 @@ public class ClassNodeUtils { * @param methodsMap A map of existing methods to alter */ public static void addDeclaredMethodsFromAllInterfaces(final ClassNode cNode, final Map<String, MethodNode> methodsMap) { - List<?> cnInterfaces = Arrays.asList(cNode.getInterfaces()); - ClassNode parent = cNode.getSuperClass(); - while (parent != null && !isObjectType(parent)) { - ClassNode[] interfaces = parent.getInterfaces(); - for (ClassNode iface : interfaces) { - if (!cnInterfaces.contains(iface)) { - methodsMap.putAll(iface.getDeclaredMethodsMap()); + List<ClassNode> cnInterfaces = Arrays.asList(cNode.getInterfaces()); + for (ClassNode sc = cNode.getSuperClass(); sc != null && !isObjectType(sc); sc = sc.getSuperClass()) { + for (ClassNode i : sc.getInterfaces()) { + if (!cnInterfaces.contains(i)) { + methodsMap.putAll(i.getDeclaredMethodsMap()); } } - parent = parent.getSuperClass(); } } diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java index 2b017ab41b..b6b37a8736 100644 --- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java +++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java @@ -690,7 +690,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes { @Override public void visitMethod(final MethodNode node) { - // GROOVY-3712: if it's an MOP method, it's an error as they aren't supposed to exist before ACG is invoked + // GROOVY-3712: if it's a MOP method, it's an error as they aren't supposed to exist before ACG is invoked if (MopWriter.isMopMethod(node.getName())) { throw new RuntimeParserException("Found unexpected MOP methods in the class node for " + classNode.getName() + "(" + node.getName() + ")", classNode); } @@ -1359,97 +1359,103 @@ public class Verifier implements GroovyClassVisitor, Opcodes { } protected void addCovariantMethods(final ClassNode classNode) { - Map<String, MethodNode> methodsToAdd = new HashMap<>(); - Map<String, ClassNode> genericsSpec = new HashMap<>(); - // unimplemented abstract methods from interfaces - Map<String, MethodNode> abstractMethods = ClassNodeUtils.getDeclaredMethodsFromInterfaces(classNode); - Map<String, MethodNode> allInterfaceMethods = new HashMap<>(abstractMethods); + Map<String, MethodNode> absInterfaceMethods = ClassNodeUtils.getDeclaredMethodsFromInterfaces(classNode); + Map<String, MethodNode> allInterfaceMethods = new HashMap<>(absInterfaceMethods); ClassNodeUtils.addDeclaredMethodsFromAllInterfaces(classNode, allInterfaceMethods); + List<MethodNode> declaredMethods = new ArrayList<>(classNode.getMethods()); - // remove all static, private and package private methods for (Iterator<MethodNode> methodsIterator = declaredMethods.iterator(); methodsIterator.hasNext(); ) { MethodNode m = methodsIterator.next(); - abstractMethods.remove(m.getTypeDescriptor()); + // remove all static, private and package-private methods if (m.isStatic() || !(m.isPublic() || m.isProtected())) { methodsIterator.remove(); } - MethodNode intfMethod = allInterfaceMethods.get(m.getTypeDescriptor()); - if (intfMethod != null && ((m.getModifiers() & ACC_SYNTHETIC) == 0) - && !m.isPublic() && !m.isStaticConstructor()) { - throw new RuntimeParserException("The method " + m.getName() + - " should be public as it implements the corresponding method from interface " + - intfMethod.getDeclaringClass(), sourceOf(m)); - + absInterfaceMethods.remove(m.getTypeDescriptor()); + MethodNode interfaceMethod = allInterfaceMethods.get(m.getTypeDescriptor()); + if (interfaceMethod != null && !m.isPublic() && !m.isStaticConstructor() && ((m.getModifiers() & ACC_SYNTHETIC) == 0)) { + throw new RuntimeParserException("The method " + m.getName() + " should be public as it implements the corresponding method from interface " + interfaceMethod.getDeclaringClass(), sourceOf(m)); } } + // interfaces may have private/static methods in JDK9 + absInterfaceMethods.values().removeIf(interfaceMethod -> interfaceMethod.isPrivate() || interfaceMethod.isStatic()); - addCovariantMethods(classNode, declaredMethods, abstractMethods, methodsToAdd, genericsSpec); + Map<String, MethodNode> methodsToAdd = new HashMap<>(); + Map<String, ClassNode > genericsSpec = Collections.emptyMap(); + addCovariantMethods(classNode, declaredMethods, absInterfaceMethods, methodsToAdd, genericsSpec); - Map<String, MethodNode> declaredMethodsMap = new HashMap<>(); if (!methodsToAdd.isEmpty()) { + Map<String, MethodNode> declaredMethodsMap = new HashMap<>(); for (MethodNode mn : declaredMethods) { declaredMethodsMap.put(mn.getTypeDescriptor(), mn); } - } - for (Map.Entry<String, MethodNode> entry : methodsToAdd.entrySet()) { - // we skip bridge methods implemented in current class already - MethodNode mn = declaredMethodsMap.get(entry.getKey()); - if (mn == null || !mn.getDeclaringClass().equals(classNode)) { - addPropertyMethod(entry.getValue()); + for (Map.Entry<String, MethodNode> entry : methodsToAdd.entrySet()) { + // we skip bridge methods implemented in current class already + MethodNode mn = declaredMethodsMap.get(entry.getKey()); + if (mn == null || !mn.getDeclaringClass().equals(classNode)) { + addPropertyMethod(entry.getValue()); + } } } } - private void addCovariantMethods(final ClassNode classNode, final List<MethodNode> declaredMethods, final Map<String, MethodNode> abstractMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> oldGenericsSpec) { - ClassNode sn = classNode.getUnresolvedSuperClass(false); - if (sn != null) { - Map<String, ClassNode> genericsSpec = createGenericsSpec(sn, oldGenericsSpec); - List<MethodNode> classMethods = sn.getMethods(); - // original class causing bridge methods for methods in super class - storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, classMethods); - // super class causing bridge methods for abstract methods in original class - if (!abstractMethods.isEmpty()) { - for (MethodNode method : classMethods) { - if (method.isStatic()) continue; - storeMissingCovariantMethods(abstractMethods.values(), method, methodsToAdd, Collections.emptyMap(), true); + private void addCovariantMethods(final ClassNode classNode, final List<MethodNode> declaredMethods, final Map<String, MethodNode> interfaceMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> oldGenericsSpec) { + ClassNode sc = classNode.getUnresolvedSuperClass(false); + if (sc != null) { + Map<String, ClassNode> genericsSpec = createGenericsSpec(sc, oldGenericsSpec); + List<MethodNode> scMethods = sc.getMethods(); + // add bridge methods in original class for overridden methods with changed signature + storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, scMethods); + // add bridge methods in original class for any interface methods overridden by super + if (!interfaceMethods.isEmpty()) { + for (MethodNode method : scMethods) { + if (!method.isStatic()) + storeMissingCovariantMethods(interfaceMethods.values(), method, methodsToAdd, Collections.emptyMap(), true); } } - addCovariantMethods(sn.redirect(), declaredMethods, abstractMethods, methodsToAdd, genericsSpec); + addCovariantMethods(sc.redirect(), declaredMethods, interfaceMethods, methodsToAdd, genericsSpec); } for (ClassNode anInterface : classNode.getInterfaces()) { - List<MethodNode> interfacesMethods = anInterface.getMethods(); + List<MethodNode> ifaceMethods = anInterface.getMethods(); Map<String, ClassNode> genericsSpec = createGenericsSpec(anInterface, oldGenericsSpec); - storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, interfacesMethods); - addCovariantMethods(anInterface, declaredMethods, abstractMethods, methodsToAdd, genericsSpec); + storeMissingCovariantMethods(declaredMethods, methodsToAdd, genericsSpec, ifaceMethods); + addCovariantMethods(anInterface, declaredMethods, interfaceMethods, methodsToAdd, genericsSpec); } } - private void storeMissingCovariantMethods(final List<MethodNode> declaredMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> genericsSpec, final List<MethodNode> methodNodeList) { - for (MethodNode method : declaredMethods) { - if (method.isStatic()) continue; - storeMissingCovariantMethods(methodNodeList, method, methodsToAdd, genericsSpec, false); + private void storeMissingCovariantMethods(final List<MethodNode> declaredMethods, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> genericsSpec, final List<MethodNode> superClassMethods) { + for (MethodNode declaredMethod : declaredMethods) { + if (!declaredMethod.isStatic()) + storeMissingCovariantMethods(superClassMethods, declaredMethod, methodsToAdd, genericsSpec, false); } } - private MethodNode getCovariantImplementation(final MethodNode oldMethod, final MethodNode overridingMethod, Map<String, ClassNode> genericsSpec, final boolean ignoreError) { - if (!oldMethod.getName().equals(overridingMethod.getName())) return null; - if ((overridingMethod.getModifiers() & ACC_BRIDGE) != 0) return null; - if ((oldMethod.getModifiers() & ACC_BRIDGE) != 0) return null; - if (oldMethod.isPrivate()) return null; + private void storeMissingCovariantMethods(final Iterable<MethodNode> superClassMethods, final MethodNode overrideCandidate, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> genericsSpec, final boolean ignoreError) { + for (MethodNode method : superClassMethods) { + if (!method.isPrivate() + && (method.getModifiers() & ACC_BRIDGE) == 0 + && (overrideCandidate.getModifiers() & ACC_BRIDGE) == 0 + && method.getName().equals(overrideCandidate.getName())) { + MethodNode bridgeMethod = getCovariantImplementation(method, overrideCandidate, genericsSpec, ignoreError); + if (bridgeMethod != null) { + methodsToAdd.put(bridgeMethod.getTypeDescriptor(), bridgeMethod); + return; + } + } + } + } + private MethodNode getCovariantImplementation(final MethodNode oldMethod, final MethodNode overrideCandidate, Map<String, ClassNode> genericsSpec, final boolean ignoreError) { if (oldMethod.getGenericsTypes() != null) genericsSpec = addMethodGenerics(oldMethod, genericsSpec); - // parameters - boolean equalParameters = equalParametersNormal(overridingMethod, oldMethod); - if (!equalParameters && !equalParametersWithGenerics(overridingMethod, oldMethod, genericsSpec)) return null; + boolean equalParameters = equalParametersNormal(overrideCandidate, oldMethod); + if (!equalParameters && !equalParametersWithGenerics(overrideCandidate, oldMethod, genericsSpec)) return null; - // return type - ClassNode nmr = overridingMethod.getReturnType(); + ClassNode nmr = overrideCandidate.getReturnType(); ClassNode omr = oldMethod.getReturnType(); boolean equalReturnType = nmr.equals(omr); @@ -1459,11 +1465,11 @@ public class Verifier implements GroovyClassVisitor, Opcodes { if (ignoreError) return null; throw new RuntimeParserException( "The return type of " + - overridingMethod.getTypeDescriptor() + - " in " + overridingMethod.getDeclaringClass().getName() + + overrideCandidate.getTypeDescriptor() + + " in " + overrideCandidate.getDeclaringClass().getName() + " is incompatible with " + omrCorrected.getName() + " in " + oldMethod.getDeclaringClass().getName(), - sourceOf(overridingMethod)); + sourceOf(overrideCandidate)); } if (equalReturnType && equalParameters) return null; @@ -1473,15 +1479,15 @@ public class Verifier implements GroovyClassVisitor, Opcodes { "Cannot override final method " + oldMethod.getTypeDescriptor() + " in " + oldMethod.getDeclaringClass().getName(), - sourceOf(overridingMethod)); + sourceOf(overrideCandidate)); } - if (oldMethod.isStatic() != overridingMethod.isStatic()) { + if (oldMethod.isStatic() != overrideCandidate.isStatic()) { throw new RuntimeParserException( "Cannot override method " + oldMethod.getTypeDescriptor() + " in " + oldMethod.getDeclaringClass().getName() + " with disparate static modifier", - sourceOf(overridingMethod)); + sourceOf(overrideCandidate)); } if (!equalReturnType) { boolean oldM = ClassHelper.isPrimitiveType(omr); @@ -1500,16 +1506,34 @@ public class Verifier implements GroovyClassVisitor, Opcodes { oldMethod.getTypeDescriptor() + " in " + oldMethod.getDeclaringClass().getName() + message, - sourceOf(overridingMethod)); + sourceOf(overrideCandidate)); } } + /* + * abstract class A { + * def m(Object o) + * } + * class B extends A { + * def m(String s) { ... } + * //def m(Object o) { this.m((String)o) } -- bridge method + * class C extends B { + * def m(String s) { ... } + * // no bridge necessary; B#m(Object) uses INVOKEVIRTUAL + * } + */ + MethodNode newMethod = ClassNodeUtils.getMethod(overrideCandidate.getDeclaringClass(), overrideCandidate.getName(), + (MethodNode m) -> !m.isAbstract() && m.getReturnType().equals(omr) && equalParametersNormal(m, oldMethod)); + if (newMethod != null && newMethod != oldMethod && newMethod != overrideCandidate) { + return null; // bridge method would be redundant + } + // if we reach this point there is least one parameter or return type // that is different in its specified form, so create a bridge method String owner = BytecodeHelper.getClassInternalName(classNode); return new MethodNode( oldMethod.getName(), - overridingMethod.getModifiers() | ACC_SYNTHETIC | ACC_BRIDGE, + overrideCandidate.getModifiers() | ACC_SYNTHETIC | ACC_BRIDGE, cleanType(omr), cleanParameters(oldMethod.getParameters()), oldMethod.getExceptions(), @@ -1518,7 +1542,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes { public void visit(final MethodVisitor mv) { mv.visitVarInsn(ALOAD, 0); Parameter[] para = oldMethod.getParameters(); - Parameter[] goal = overridingMethod.getParameters(); + Parameter[] goal = overrideCandidate.getParameters(); int doubleSlotOffset = 0; for (int i = 0, n = para.length; i < n; i += 1) { ClassNode type = para[i].getType(); @@ -1531,7 +1555,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes { BytecodeHelper.doCast(mv, goal[i].getType()); } } - mv.visitMethodInsn(INVOKEVIRTUAL, owner, overridingMethod.getName(), BytecodeHelper.getMethodDescriptor(nmr, overridingMethod.getParameters()), false); + mv.visitMethodInsn(INVOKEVIRTUAL, owner, overrideCandidate.getName(), BytecodeHelper.getMethodDescriptor(nmr, overrideCandidate.getParameters()), false); BytecodeHelper.doReturn(mv, oldMethod.getReturnType()); } @@ -1566,16 +1590,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes { return type.getPlainNodeReference(); } - private void storeMissingCovariantMethods(final Iterable<MethodNode> methods, final MethodNode method, final Map<String, MethodNode> methodsToAdd, final Map<String, ClassNode> genericsSpec, final boolean ignoreError) { - for (MethodNode toOverride : methods) { - MethodNode bridgeMethod = getCovariantImplementation(toOverride, method, genericsSpec, ignoreError); - if (bridgeMethod != null) { - methodsToAdd.put(bridgeMethod.getTypeDescriptor(), bridgeMethod); - return; - } - } - } - private static boolean equalParametersNormal(final MethodNode m1, final MethodNode m2) { Parameter[] p1 = m1.getParameters(); Parameter[] p2 = m2.getParameters(); diff --git a/src/test/gls/invocation/CovariantReturnTest.groovy b/src/test/gls/invocation/CovariantReturnTest.groovy index 4ecfeea6d5..cdff82ea6e 100644 --- a/src/test/gls/invocation/CovariantReturnTest.groovy +++ b/src/test/gls/invocation/CovariantReturnTest.groovy @@ -18,316 +18,446 @@ */ package gls.invocation -import gls.CompilableTestSupport +import org.junit.Test -class CovariantReturnTest extends CompilableTestSupport { +import static groovy.test.GroovyAssert.assertScript +import static groovy.test.GroovyAssert.shouldFail +final class CovariantReturnTest { + + @Test void testCovariantReturn() { - assertScript """ + assertScript ''' class A { - Object foo() {1} + Object foo() { 1 } } - class B extends A{ - String foo(){"2"} + class B extends A { + String foo() { "2" } } - def b = new B(); - assert b.foo()=="2" - assert B.declaredMethods.findAll{it.name=="foo"}.size()==2 - """ + + b = new B() + assert b.foo() == "2" + assert B.declaredMethods.count{ it.name=="foo" } == 2 + ''' } + @Test void testCovariantReturnOverwritingAbstractMethod() { - assertScript """ + assertScript ''' abstract class Numeric { - abstract Numeric eval(); + abstract Numeric eval() } class Rational extends Numeric { - Rational eval() {this} + Rational eval() { this } } - assert Rational.declaredMethods.findAll{it.name=="eval"}.size()==2 - """ + + assert Rational.declaredMethods.count{ it.name=="eval" } == 2 + ''' } - void testCovariantReturnOverwritingObjectMethod() { - shouldNotCompile """ - class X { + @Test + void testCovariantReturnOverwritingAnObjectMethod() { + def err = shouldFail ''' + class C { Long toString() { 333L } String hashCode() { "hash" } } - """ + ''' + assert err.message.contains('The return type of java.lang.Long toString() in C is incompatible with java.lang.String in java.lang.Object') } + @Test void testCovariantOverwritingMethodWithPrimitives() { - assertScript """ - class Base { - Object foo(boolean i) {i} + assertScript ''' + class B { + Object foo(boolean b) { b } } - class Child extends Base { - String foo(boolean i) {""+super.foo(i)} + class C extends B { + String foo(boolean b) { ""+super.foo(b) } } - def x = new Child() - assert x.foo(true) == "true" - assert x.foo(false) == "false" - """ + + c = new C() + assert c.foo(true) == "true" + assert c.foo(false) == "false" + ''' } + @Test void testCovariantOverwritingMethodWithInterface() { - assertScript """ - interface Base { + assertScript ''' + interface A { List foo() - Base baz() + A baz() } - interface Child extends Base { + interface B extends A { ArrayList foo() - Child baz() + B baz() } - class GroovyChildImpl implements Child { + class C implements B { ArrayList foo() {} - GroovyChildImpl baz() {} + C baz() {} } - def x = new GroovyChildImpl() - x.foo() - x.baz() - """ + + c = new C() + c.foo() + c.baz() + ''' } + @Test void testCovariantOverwritingMethodWithInterfaceAndInheritance() { - assertScript """ - interface Base { + assertScript ''' + interface A { List foo() List bar() - Base baz() + A baz() } - interface Child extends Base { + interface B extends A { ArrayList foo() } class MyArrayList extends ArrayList { } - class GroovyChildImpl implements Child { + class C implements B { MyArrayList foo() {} MyArrayList bar() {} - GroovyChildImpl baz() {} + C baz() {} } - def x = new GroovyChildImpl() - x.foo() - x.bar() - x.baz() - """ + + c = new C() + c.foo() + c.bar() + c.baz() + ''' } - void testCovariantMethodFromParentOverwritingMethodFromInterfaceInCurrentclass() { - assertScript """ - interface I { + // GROOVY-2582 + @Test + void testCovariantMethodFromParentOverwritingMethodFromInterfaceInCurrentClass() { + assertScript ''' + interface A { def foo() } - class A { - String foo(){""} + class B { + String foo() { "" } } - class B extends A implements I{} - def b = new B() - assert b.foo() == "" - """ + class C extends B implements A { + } + + c = new C() + assert c.foo() == "" + ''' - // basically the same as above, but with an example - // from an error report (GROOVY-2582) + // basically the same as above, but with an example from an error report // Properties has a method "String getProperty(String)", this class // is also a GroovyObject, meaning a "Object getProperty(String)" method // should be implemented. But this method should not be the usual automatically // added getProperty, but a bridge to the getProperty method provided by Properties - assertScript """ - class Configuration extends java.util.Properties {} - assert Configuration.declaredMethods.findAll{it.name=="getProperty"}.size() == 1 + assertScript ''' + class Configuration extends java.util.Properties { + } + + assert Configuration.declaredMethods.count{ it.name=="getProperty" } == 1 def conf = new Configuration() conf.setProperty("a","b") - // the following assert would fail if standard getProperty method was added - // by the compiler + // the following assert would fail if standard getProperty method was added by the compiler assert conf.getProperty("a") == "b" - """ + ''' - assertScript """ + assertScript ''' class A {} class B extends A {} interface Contract { A method() } - abstract class AbstractContract implements Contract {} - class ContractImpl extends AbstractContract { - B method(String foo='default') { new B() } + abstract class C implements Contract { } - assert new ContractImpl().method() instanceof B - """ + class D extends C { + B method(String foo = 'default') { new B() } + } + + assert new D().method() instanceof B + ''' } + // GROOVY-3229 + @Test void testImplementedInterfacesNotInfluencing() { - // in GROOVY-3229 some methods from Appendable were not correctly recognized + // some methods from Appendable were not correctly recognized // as already being overridden (PrintWriter<Writer<Appendable) - shouldCompile """ - class IndentWriter extends java.io.PrintWriter { - public IndentWriter(Writer w) { super(w, true) } + assertScript ''' + class IndentWriter extends PrintWriter { + IndentWriter(Writer w) { + super(w, true) + } } - """ + + new IndentWriter(new StringWriter()) + ''' } - void testCovariantMethodReturnTypeFromGenericsInterface() { - shouldCompile """ - interface MyCallable<T> { - T myCall() throws Exception; - } - class Task implements MyCallable<List> { - List myCall() throws Exception { - return [ 42 ] + @Test + void testCovariantReturnFromGenericsInterface() { + assertScript ''' + class Task implements java.util.concurrent.Callable<List> { + List call() throws Exception { + [ 42 ] } } - """ + + assert new Task().call() == [42] + ''' } - //GROOVY-7495 - void testCovariantMerhodReturnTypeFromParentInterface() { - shouldCompile """ - interface Item {} - interface DerivedItem extends Item {} + // GROOVY-7849 + @Test + void testCovariantArrayReturnType1() { + assertScript ''' + interface Base {} + + interface Derived extends Base {} + + interface I { + Base[] foo() + } + + class C implements I { + Derived[] foo() { null } + } + new C().foo() + ''' + } + + // GROOVY-7185 + @Test + void testCovariantArrayReturnType2() { + assertScript ''' + interface A<T> { + T[] process(); + } + + class B implements A<String> { + @Override + public String[] process() { + ['foo'] + } + } + + class C extends B { + @Override + String[] process() { + super.process() + } + } + assert new C().process()[0] == 'foo' + ''' + } + // GROOVY-7495 + @Test + void testCovariantReturnFromIndirectInterface() { + assertScript ''' + interface Item { + } + interface DerivedItem extends Item { + } interface Base { Item getItem() } class BaseImpl implements Base { Item getItem() { null } } - interface First extends Base { DerivedItem getItem() } - class FirstImpl extends BaseImpl implements First { - DerivedItem getItem() { null } + DerivedItem getItem() { new DerivedItem(){} } } + interface Second extends First { + } + class SecondImpl extends FirstImpl implements Second { + } + + assert new SecondImpl().item instanceof DerivedItem + ''' - interface Second extends First {} - class SecondImpl extends FirstImpl implements Second {} - """ - shouldCompile """ + assertScript ''' interface A { - B foo() + B foo() + } + interface B { } - interface B {} interface A2 extends A { - B2 foo() + B2 foo() + } + interface B2 extends B { } - interface B2 extends B {} - class AA implements A { - BB foo() { return new BB() } + BB foo() { new BB() } + } + class AA_A2 extends AA implements A2 { + BB_B2 foo() { new BB_B2() } + } + class BB implements B { } - class AA2 extends AA implements A2 { - BB2 foo() { return new BB2() } + class BB_B2 extends BB implements B2 { } - class BB implements B {} - class BB2 extends BB implements B2 {} - """ + + assert new AA_A2().foo() instanceof BB_B2 + ''' } - void testCovariantParameterType() { - assertScript """ - class BaseA implements Comparable<BaseA> { - int index - public int compareTo(BaseA a){ - return index <=> a.index + // GROOVY-4410 + @Test + void testCovariantReturnFromMultipleInterfaces() { + def config = new org.codehaus.groovy.control.CompilerConfiguration( + jointCompilationOptions: [memStub: true], + targetDirectory: File.createTempDir() + ) + + def parentDir = File.createTempDir() + try { + def a = new File(parentDir, 'G4410Producer1.java') + a.write ''' + public interface G4410Producer1 { + Object gimme(String[] array); + } + ''' + def b = new File(parentDir, 'G4410Producer2.java') + b.write ''' + public interface G4410Producer2<T> { + T gimme(String[] array); + } + ''' + def c = new File(parentDir, 'G4410JavaStringProducer.java') + c.write ''' + public class G4410JavaStringProducer implements G4410Producer1 { + public String gimme(String[] array) { + return "Hello World"; + } + } + ''' + def d = new File(parentDir, 'G4410GroovyStringProducers.groovy') + d.write ''' + class HackProducer1 implements G4410Producer1 { + Object gimme(String[] array) { + "Hello World" + } } - } - def a = new BaseA(index:1) - def b = new BaseA(index:2) - assert a < b - """ - } + class StringProducer1 implements G4410Producer1 { + String gimme(String[] array) { + "Hello World" + } + } - void testBridgeMethodExistsOnImplementingClass() { - shouldCompile """ - interface IBar<T>{ - int testMethod(T obj) - } - class Bar implements IBar<Date>{ - int testMethod(Date dt){} - int testMethod(Object obj){} - } - """ + class HackProducer2 implements G4410Producer2<Object> { + Object gimme(String[] array) { + "Hello World" + } + } + + class StringProducer2 implements G4410Producer2<String> { + String gimme(String[] array) { + "Hello World" + } + } + ''' + def e = new File(parentDir, 'Main.groovy') + e.write ''' + def sp1 = new StringProducer1() + assert sp1.gimme(null) == "Hello World" + + def jsp = new G4410JavaStringProducer() + assert jsp.gimme(null) == "Hello World" + + def hp1 = new HackProducer1() + assert hp1.gimme(null) == "Hello World" + + def sp2 = new StringProducer2() + assert sp2.gimme(null) == "Hello World" + + def hp2 = new HackProducer2() + assert hp2.gimme(null) == "Hello World" + ''' + + def loader = new GroovyClassLoader(this.class.classLoader) + def cu = new org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit(config, loader) + cu.addSources(a, b, c, d, e) + cu.compile() + + loader.loadClass('Main').main() + } finally { + config.targetDirectory.deleteDir() + parentDir.deleteDir() + } } - void testCovariantVoidReturnTypeOverridingObjectType() { - shouldNotCompile """ - class BaseClass { + @Test + void testCovariantVoidReturnOverridingObjectType() { + def err = shouldFail ''' + class B { def foo() {} } - class DerivedClass extends BaseClass { + class C extends B { void foo() {} } - """ + ''' + assert err.message.contains('The return type of void foo() in C is incompatible with java.lang.Object in B') } + @Test void testPrimitiveObjectMix() { - //Object overridden by primitive - shouldNotCompile """ - class BaseClass{ def foo(){} } - class DerivedClass extends BaseClass { - boolean foo(){} - } - """ - //primitive overridden by Object - shouldNotCompile """ - class BaseClass{ boolean foo(){} } - class DerivedClass extends BaseClass { - Object foo(){} - } - """ - //primitive overriding different primitive - shouldNotCompile """ - class BaseClass{ boolean foo(){} } - class DerivedClass extends BaseClass { - int foo(){} - } - """ + // Object overridden by primitive + shouldFail ''' + class B { def foo() {} } + class C extends B { + boolean foo() {} + } + ''' + // primitive overridden by Object + shouldFail ''' + class B { boolean foo() {} } + class C extends B { + Object foo() {} + } + ''' + // primitive overriding different primitive + shouldFail ''' + class B { boolean foo() {} } + class C extends B { + int foo() {} + } + ''' } - void testCovariantMethodGenerics_groovy6330() { - shouldNotCompile """ + // GROOVY-6330 + @Test + void testCovariantMethodGenerics() { + def err = shouldFail ''' class StringIterator implements Iterator<String> { - void remove() { } boolean hasNext() { false } - def next() { 'dummy' } + def next() { null } + void remove() {} } - """ - shouldNotCompile """ + ''' + assert err.message.contains('The return type of java.lang.Object next() in StringIterator is incompatible with java.lang.String in java.util.Iterator') + + err = shouldFail ''' class StringIterator implements Iterator<String> { - void remove() { } boolean hasNext() { false } CharSequence next() { null } + void remove() {} } - """ - assertScript """ + ''' + assert err.message.contains('The return type of java.lang.CharSequence next() in StringIterator is incompatible with java.lang.String in java.util.Iterator') + + assertScript ''' class StringIterator implements Iterator<CharSequence> { - void remove() { } boolean hasNext() { false } String next() { 'dummy' } + void remove() {} } - assert new StringIterator().next() == 'dummy' - """ - } - void testCovariantParameter() { - assertScript """ - interface Interface<SomeType> { - public int handle(long a, SomeType x); - } - - class InterfaceImpl - implements Interface<String> { - public int handle(long a, String something) { - return 1 - } - } - - InterfaceImpl test = new InterfaceImpl() - assert test.handle(5, "hi") == 1 - """ + assert new StringIterator().next() == 'dummy' + ''' } - } diff --git a/src/test/groovy/OverrideTest.groovy b/src/test/groovy/OverrideTest.groovy index c29618a8d9..d16dbdb70d 100644 --- a/src/test/groovy/OverrideTest.groovy +++ b/src/test/groovy/OverrideTest.groovy @@ -140,79 +140,88 @@ final class OverrideTest { assert err.message.contains("Method 'methodTakesObject' from class 'HasMethodWithBadArgType' does not override method from its superclass or interfaces but is annotated with @Override.") } - @Test // GROOVY-6654 + @Test void testCovariantParameterType1() { assertScript ''' - class C<T> { - void proc(T t) {} - } - - class D extends C<String> { - @Override - void proc(String s) {} + class C implements Comparable<C> { + int index + int compareTo(C c) { + index <=> c.index + } } - def d = new D() + one = new C(index:1) + two = new C(index:2) + assert one < two ''' } - @Test // GROOVY-10675 + @Test void testCovariantParameterType2() { assertScript ''' - @FunctionalInterface - interface A<I, O> { - O apply(I in) - } - interface B<X, Y> extends A<X, Y> { + interface I<T> { + int handle(long n, T t) } - class C implements B<Number, String> { - @Override String apply(Number n) { 'x' } + + class C implements I<String> { + int handle(long n, String something) { + 1 + } } - def result = new C().apply(42) - assert result == 'x' + c = new C() + assert c.handle(5,"hi") == 1 ''' } - @Test // GROOVY-7849 - void testCovariantArrayReturnType1() { + @Test + void testCovariantParameterType3() { assertScript ''' - interface Base {} - - interface Derived extends Base {} - - interface I { - Base[] foo() + interface I<T> { + int testMethod(T t) } - - class C implements I { - Derived[] foo() { null } + class C implements I<Date> { + int testMethod(Date date) {} + int testMethod(Object obj) {} } - new C().foo() + + assert C.declaredMethods.count{ it.name=="testMethod" } == 2 ''' } - @Test // GROOVY-7185 - void testCovariantArrayReturnType2() { + // GROOVY-6654 + @Test + void testCovariantParameterType4() { assertScript ''' - interface A<T> { - T[] process(); + class C<T> { + void proc(T t) {} } - class B implements A<String> { + class D extends C<String> { @Override - public String[] process() { - ['foo'] - } + void proc(String s) {} } - class C extends B { - @Override - String[] process() { - super.process() - } + def d = new D() + ''' + } + + // GROOVY-10675 + @Test + void testCovariantParameterType5() { + assertScript ''' + @FunctionalInterface + interface A<I, O> { + O apply(I in) + } + interface B<X, Y> extends A<X, Y> { } - assert new C().process()[0] == 'foo' + class C implements B<Number, String> { + @Override String apply(Number n) { 'x' } + } + + def result = new C().apply(42) + assert result == 'x' ''' } diff --git a/src/test/groovy/ThisAndSuperTest.groovy b/src/test/groovy/ThisAndSuperTest.groovy index 41b7f36bc0..b6efb04651 100644 --- a/src/test/groovy/ThisAndSuperTest.groovy +++ b/src/test/groovy/ThisAndSuperTest.groovy @@ -390,9 +390,41 @@ final class ThisAndSuperTest { ''' } - // GROOVY-10302 + // GROOVY-6663 @Test void testSuperDotMethod4() { + assertScript ''' + class A<T> { + protected String getText(T t) { + 'A with ' + t + } + } + + class B extends A<String> { + @Override + protected String getText(String s) { + 'B then ' + super.getText(s) // invoke super$2$getText + } + // bridge String getText(Object o) { this.getText((String) o); } + // String super$2$getText(Object o) { super.getText(o); } + } + + class C extends B { + @Override + protected String getText(String s) { + 'C then ' + super.getText(s) // invoke super$3$getText + } + // String super$3$getText(String s) { super.getText(s); } + } + + String result = new C().getText(null) // invoke bridge method + assert result == 'C then B then A with null' + ''' + } + + // GROOVY-10302 + @Test + void testSuperDotMethod5() { shouldFail ClassNotFoundException, ''' void test() { def list = [] diff --git a/src/test/groovy/bugs/G4410JavaStringProducer.java b/src/test/groovy/bugs/G4410JavaStringProducer.java deleted file mode 100644 index 3188eec869..0000000000 --- a/src/test/groovy/bugs/G4410JavaStringProducer.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package groovy.bugs; - -@SuppressWarnings("all") -public class G4410JavaStringProducer implements G4410Producer1 { - - public String gimme(final String[] a1) { - return "Hello World"; - } -} diff --git a/src/test/groovy/bugs/G4410Producer1.java b/src/test/groovy/bugs/G4410Producer1.java deleted file mode 100644 index ff7d99b825..0000000000 --- a/src/test/groovy/bugs/G4410Producer1.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package groovy.bugs; - -@SuppressWarnings("all") -public interface G4410Producer1 { - - Object gimme(String[] a2); -} \ No newline at end of file diff --git a/src/test/groovy/bugs/G4410Producer2.java b/src/test/groovy/bugs/G4410Producer2.java deleted file mode 100644 index 2c1983ceb9..0000000000 --- a/src/test/groovy/bugs/G4410Producer2.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package groovy.bugs; - -@SuppressWarnings("all") -public interface G4410Producer2<T> { - - T gimme(String[] a2); -} diff --git a/src/test/groovy/bugs/Groovy4410Bug.groovy b/src/test/groovy/bugs/Groovy4410Bug.groovy deleted file mode 100644 index 67f6d9df91..0000000000 --- a/src/test/groovy/bugs/Groovy4410Bug.groovy +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package groovy.bugs - -import groovy.test.GroovyTestCase - -class Groovy4410Bug extends GroovyTestCase { - void testBridgeMethodWithArrayTypeParameterV1() { - StringProducer1 sp = new StringProducer1() - assert sp.gimme(null) == 'Hello World' - - G4410JavaStringProducer jsp = new G4410JavaStringProducer() - assert jsp.gimme(null) == 'Hello World' - - HackProducer1 hp = new HackProducer1() - assert hp.gimme(null) == 'Hello World' - } - - void testBridgeMethodWithArrayTypeParameterV2() { - StringProducer2 sp = new StringProducer2() - assert sp.gimme(null) == 'Hello World' - - HackProducer2 hp = new HackProducer2() - assert hp.gimme(null) == 'Hello World' - } -} -class HackProducer1 implements G4410Producer1 { - Object gimme(String[] a) { - "Hello World" - } -} - -class StringProducer1 implements G4410Producer1 { - String gimme(String[] a1) { - "Hello World" - } -} - -class HackProducer2 implements G4410Producer2<Object> { - Object gimme(String[] a1) { - "Hello World" - } -} - -class StringProducer2 implements G4410Producer2<String> { - String gimme(String[] a1) { - "Hello World" - } -} \ No newline at end of file