This is an automated email from the ASF dual-hosted git repository.
paulk-asert 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 07adcb35b9 GROOVY-12134: @Delegate regression: fails to skip a final
method inherited from a precompiled superclass
07adcb35b9 is described below
commit 07adcb35b9d8f47bae7bc5f1b31c2e70f36e31ae
Author: Paul King <[email protected]>
AuthorDate: Mon Jul 6 18:31:55 2026 +1000
GROOVY-12134: @Delegate regression: fails to skip a final method inherited
from a precompiled superclass
---
.../transform/DelegateASTTransformation.java | 5 +-
.../groovy/transform/DelegateTransformTest.groovy | 97 +++++++++++++++++++++-
2 files changed, 98 insertions(+), 4 deletions(-)
diff --git
a/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
b/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
index 0a7ff39702..184be24fab 100644
--- a/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
@@ -424,7 +424,10 @@ public class DelegateASTTransformation extends
AbstractASTTransformation {
break;
}
}
- if (existingNode == null || existingNode.getCode() == null) {
+ // GROOVY-12134: a non-abstract method from a pre-compiled class is an
+ // implementation despite having no code; only a primary class node can
+ // supply a bodiless placeholder that should be overwritten
+ if (existingNode == null || (existingNode.getCode() == null &&
existingNode.getDeclaringClass().isPrimaryClassNode())) {
final ArgumentListExpression args = new ArgumentListExpression();
final Parameter[] params = candidate.getParameters();
final Parameter[] newParams = new Parameter[params.length];
diff --git
a/src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy
b/src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy
index 9c149bbb19..25b4befe1a 100644
--- a/src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy
@@ -18,6 +18,7 @@
*/
package org.codehaus.groovy.transform
+import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
@@ -69,15 +70,14 @@ final class DelegateTransformTest {
// GROOVY-10439
@Test
void testDelegateImplementingInterfaceWithDifferentTypeArgumentThanOwner()
{
- def err = shouldFail '''
+ assertScript '''
class C extends ArrayList<String> {
@Delegate List<Number> numbers // List<String> takes precedence
}
new C(numbers:[1,2,3])
'''
- assert err.message =~ /The return type of java.lang.Number get\(int\)
in C is incompatible with java.lang.String in java.util.ArrayList/
- err = shouldFail '''
+ def err = shouldFail '''
class C extends ArrayList<String> {
@Delegate HashSet<Number> numbers // Set<Number> added;
Verifier checks
}
@@ -85,6 +85,97 @@ final class DelegateTransformTest {
assert err.message =~ /The interface Collection cannot be implemented
more than once with different arguments: java.util.Collection<java.lang.Number>
\(via Set\) and java.util.Collection<java.lang.String> \(via ArrayList\)/
}
+ // GROOVY-12134
+ @Test
+ void testDelegateSkipsMethodsInheritedFromPrecompiledSuperclass() {
+ // final methods format(Object) and
format(Object,StringBuffer,FieldPosition)
+ // are inherited by C from binary (pre-compiled) super types
+ assertScript '''
+ import java.text.SimpleDateFormat
+ class C extends SimpleDateFormat {
+ @Delegate SimpleDateFormat delegate = new
SimpleDateFormat('yyyy')
+ }
+ new C()
+ '''
+
+ // same shape with Groovy-compiled classes loaded from the classpath
+ def config = new CompilerConfiguration(targetDirectory:
File.createTempDir())
+ def parentDir = File.createTempDir()
+ try {
+ def supSource = new File(parentDir, 'Sup.groovy')
+ supSource.write '''
+ class Sup {
+ final String describe() { 'sup' }
+ }
+ '''
+ def baseSource = new File(parentDir, 'Base.groovy')
+ baseSource.write '''
+ class Base extends Sup {
+ String hello() { 'hello' }
+ }
+ '''
+ def unit = new CompilationUnit(config)
+ unit.addSources(supSource, baseSource)
+ unit.compile()
+
+ new GroovyClassLoader(this.class.classLoader).withCloseable {
loader ->
+ loader.addClasspath(config.targetDirectory.path)
+ def result = new GroovyShell(loader).evaluate '''
+ class Base2 extends Base {
+ String hello() { 'delegated' }
+ }
+ class Child extends Base {
+ @Delegate Base b = new Base2()
+ }
+ def c = new Child()
+ c.describe() + ' ' + c.hello()
+ '''
+ // inherited implementations win, as when all classes are
compiled together
+ assert result == 'sup hello'
+ }
+ } finally {
+ config.targetDirectory.deleteDir()
+ parentDir.deleteDir()
+ }
+ }
+
+ // GROOVY-12134
+ @Test
+ void
testDelegateImplementsAbstractMethodInheritedFromPrecompiledSuperclass() {
+ // an abstract method inherited from a binary super type must still be
+ // implemented via @Delegate; the bodiless-placeholder guard must not
skip it
+ def config = new CompilerConfiguration(targetDirectory:
File.createTempDir())
+ def parentDir = File.createTempDir()
+ try {
+ def baseSource = new File(parentDir, 'AbstractBase.groovy')
+ baseSource.write '''
+ abstract class AbstractBase {
+ abstract String greet()
+ }
+ '''
+ def unit = new CompilationUnit(config)
+ unit.addSources(baseSource)
+ unit.compile()
+
+ new GroovyClassLoader(this.class.classLoader).withCloseable {
loader ->
+ loader.addClasspath(config.targetDirectory.path)
+ def result = new GroovyShell(loader).evaluate '''
+ class Impl {
+ String greet() { 'hi' }
+ }
+ class Child extends AbstractBase {
+ @Delegate Impl delegate = new Impl()
+ }
+ new Child().greet()
+ '''
+ assert result == 'hi'
+ }
+ } finally {
+ config.targetDirectory.deleteDir()
+ parentDir.deleteDir()
+ }
+ }
+
// GROOVY-5974
@Test
void testDelegateExcludes() {