You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/10/22 21:33:13 UTC
(groovy) branch GROOVY_4_0_X updated: GROOVY-12470: SC: super-method calls do not work under static compilation
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
new 7e71802d21 GROOVY-12470: SC: super-method calls do not work under static compilation
7e71802d21 is described below
commit 7e71802d219bf2c6a30914353dc2c4c957049569
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Oct 22 14:54:25 2026 +1000
GROOVY-12470: SC: super-method calls do not work under static compilation
When the best candidate for super.m() is an extension method, select from
the superclass's own non-abstract methods instead, as Java and dynamic
Groovy do. super.toString() previously bound to the DGM toString(Object)
extension, which formats via the receiver's virtual toString(), so an
overriding toString() that called super.toString() recursed until a
StackOverflowError. In List and Map subclasses super.toString() now gives
the JDK superclass output, matching dynamic Groovy.
---
.../transform/stc/StaticTypeCheckingVisitor.java | 8 +++
src/test/groovy/bugs/Groovy12470.groovy | 65 ++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 1a019db841..ff5e7a991d 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -3647,6 +3647,14 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
mn = disambiguateMethods(mn, obj, args, call);
}
}
+ if (mn.size() == 1 && mn.get(0) instanceof ExtensionMethodNode && chosenReceiver != null
+ && isSuperExpression(objectExpression)) { // GROOVY-12470: super.m() invokes the superclass method
+ List<MethodNode> superMethods = new ArrayList<>(findMethodsWithGenerated(chosenReceiver.getType(), name));
+ superMethods.removeIf(MethodNode::isAbstract);
+ superMethods = chooseBestMethod(chosenReceiver.getType(),
+ filterMethodsByVisibility(superMethods, typeCheckingContext.getEnclosingClassNode()), args);
+ if (superMethods.size() == 1) mn = superMethods;
+ }
if (mn.size() == 1) {
MethodNode targetMethodCandidate = mn.get(0);
diff --git a/src/test/groovy/bugs/Groovy12470.groovy b/src/test/groovy/bugs/Groovy12470.groovy
new file mode 100644
index 0000000000..99abd42318
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy12470.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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 bugs
+
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * Under static compilation, {@code super.m()} invokes the superclass method, as
+ * in Java and dynamic Groovy, rather than an extension method of the same name.
+ */
+final class Groovy12470 {
+
+ @Test
+ void testSuperToStringInPlainClass() {
+ assertScript '''
+ @groovy.transform.CompileStatic
+ class Foo {
+ def foo() {
+ super.toString()
+ }
+ @Override
+ String toString() {
+ "FOO: ${super.toString()}"
+ }
+ }
+ assert new Foo().foo() ==~ /Foo@\\p{XDigit}+/
+ assert new Foo().toString() ==~ /FOO: Foo@\\p{XDigit}+/
+ '''
+ }
+
+ @Test
+ void testSuperToStringInCollectionSubclass() {
+ assertScript '''
+ import groovy.transform.CompileStatic
+
+ @CompileStatic class SL extends ArrayList<Object> { String toString() { 'SL:' + super.toString() } }
+ @CompileStatic class SM extends HashMap<String, Object> { String toString() { 'SM:' + super.toString() } }
+ class DL extends ArrayList<Object> { String toString() { 'DL:' + super.toString() } }
+ class DM extends HashMap<String, Object> { String toString() { 'DM:' + super.toString() } }
+
+ assert (new SL() << [k: 1]).toString() == 'SL:[{k=1}]'
+ assert (new DL() << [k: 1]).toString() == 'DL:[{k=1}]'
+ assert new SM(k: [1]).toString() == 'SM:{k=[1]}'
+ assert new DM(k: [1]).toString() == 'DM:{k=[1]}'
+ '''
+ }
+}