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 0a16e3e3b8 GROOVY-12158: @AutoImplement generates methods in hash
order, not the order they are found
0a16e3e3b8 is described below
commit 0a16e3e3b8e02152467c6f5a5c24e63f1d0947d4
Author: Paul King <[email protected]>
AuthorDate: Mon Jul 13 17:35:51 2026 +1000
GROOVY-12158: @AutoImplement generates methods in hash order, not the order
they are found
---
.../transform/AutoImplementASTTransformation.java | 6 +-
.../transform/AutoImplementMethodOrderTest.groovy | 70 ++++++++++++++++++++++
2 files changed, 75 insertions(+), 1 deletion(-)
diff --git
a/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
b/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
index 9f41dda1ae..541ff45536 100644
---
a/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
+++
b/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
@@ -38,6 +38,7 @@ import org.codehaus.groovy.control.SourceUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -155,7 +156,10 @@ public class AutoImplementASTTransformation extends
AbstractASTTransformation {
* if not overridden by a concrete declared/inherited method.
*/
static Map<String, MethodNode> getAllCorrectedMethodsMap(final ClassNode
cNode) {
- Map<String, MethodNode> result = new HashMap<>();
+ // insertion-ordered: callers iterate the values to generate methods,
so hash order
+ // would place the generated methods in an arbitrary order in the
class file (and in
+ // the joint-compilation stubs) rather than the order they are found
+ Map<String, MethodNode> result = new LinkedHashMap<>();
for (MethodNode mn : getMethodsWithGenerated(cNode)) {
result.put(methodDescriptorWithoutReturnType(mn), mn);
}
diff --git
a/src/test/groovy/org/codehaus/groovy/transform/AutoImplementMethodOrderTest.groovy
b/src/test/groovy/org/codehaus/groovy/transform/AutoImplementMethodOrderTest.groovy
new file mode 100644
index 0000000000..fb34e28890
--- /dev/null
+++
b/src/test/groovy/org/codehaus/groovy/transform/AutoImplementMethodOrderTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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 org.codehaus.groovy.transform
+
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.Phases
+import org.junit.jupiter.api.Test
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+
+final class AutoImplementMethodOrderTest {
+
+ /**
+ * The methods {@code @AutoImplement} generates must be emitted in the
order they are
+ * found, not in the hash order of the map that collects them, so that
identical
+ * sources produce identical class files.
+ */
+ @Test
+ void testGeneratedMethodsAreEmittedInDeclarationOrder() {
+ def cu = new CompilationUnit()
+ cu.addSource('Probe.groovy', '''
+ interface Face {
+ void alpha()
+ void beta()
+ void gamma()
+ void delta()
+ void epsilon()
+ void zeta()
+ void eta()
+ void theta()
+ }
+
+ @groovy.transform.AutoImplement
+ class Impl implements Face {}
+ ''')
+ cu.compile(Phases.CLASS_GENERATION)
+
+ byte[] bytes = cu.classes.find { it.name == 'Impl' }.bytes
+
+ def declared = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta',
'eta', 'theta']
+ def generated = []
+ new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ MethodVisitor visitMethod(int access, String name, String
descriptor, String signature, String[] exceptions) {
+ if (name in declared) generated << name
+ return null
+ }
+ }, 0)
+
+ assert generated == declared
+ }
+}