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() {

Reply via email to