Copilot commented on code in PR #2670:
URL: https://github.com/apache/groovy/pull/2670#discussion_r3527599451


##########
src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java:
##########
@@ -424,7 +424,10 @@ private void addDelegateMethod(final MethodNode candidate, 
final DelegateDescrip
                 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())) {

Review Comment:
   This condition will also prevent generating delegate methods that are 
intended to implement *abstract* methods inherited from non-primary (e.g., 
precompiled) super types, since abstract methods also commonly have `getCode() 
== null` and `isPrimaryClassNode() == false`. Consider explicitly allowing 
overwrite when `existingNode.isAbstract()` (or equivalent check) so delegation 
can still provide concrete implementations for abstract inherited signatures.



##########
src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy:
##########
@@ -69,22 +70,74 @@ 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
             }
         '''
         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 a = new File(parentDir, 'Sup.groovy')
+            a.write '''
+                class Sup {
+                    final String describe() { 'sup' }
+                }
+            '''
+            def b = new File(parentDir, 'Base.groovy')
+            b.write '''
+                class Base extends Sup {
+                    String hello() { 'hello' }
+                }
+            '''
+            def unit = new CompilationUnit(config)
+            unit.addSources(a, b)
+            unit.compile()
+
+            def loader = new GroovyClassLoader(this.class.classLoader)
+            loader.addClasspath(config.targetDirectory.path)
+            def result = new GroovyShell(loader).evaluate '''

Review Comment:
   `GroovyClassLoader` is `Closeable` and can hold onto resources/classes 
across test runs. To avoid test-suite resource leakage (especially in 
long-running/parallel builds), close the classloader in a `finally` block or 
use a try-with-resources pattern with `GroovyClassLoader`.



##########
src/test/groovy/org/codehaus/groovy/transform/DelegateTransformTest.groovy:
##########
@@ -69,22 +70,74 @@ 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
             }
         '''
         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 a = new File(parentDir, 'Sup.groovy')
+            a.write '''
+                class Sup {
+                    final String describe() { 'sup' }
+                }
+            '''
+            def b = new File(parentDir, 'Base.groovy')
+            b.write '''
+                class Base extends Sup {

Review Comment:
   The variables `a` and `b` are not descriptive in this multi-step setup 
(writing sources, compiling, then loading from classpath). Renaming them to 
something like `supSource`/`baseSource` (or similar) would make the test easier 
to follow and maintain.



##########
src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java:
##########
@@ -424,7 +424,10 @@ private void addDelegateMethod(final MethodNode candidate, 
final DelegateDescrip
                 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())) {

Review Comment:
   The new overwrite rule is covered for inherited *final* methods, but it 
doesn’t appear to cover the flip side: inherited *abstract* methods from a 
precompiled superclass that should be implemented via `@Delegate`. Adding a 
regression test for an abstract method inherited from a classpath-loaded 
(precompiled) superclass would help ensure the new condition doesn’t block 
valid delegate method generation.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to