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 5ca3ac444e GROOVY-12156: chooseBestMethod returns candidates in 
identity-hash order, making static-compilation output path-dependent
5ca3ac444e is described below

commit 5ca3ac444e08513e6936d08c061b603ef148fb47
Author: Paul King <[email protected]>
AuthorDate: Mon Jul 13 16:26:40 2026 +1000

    GROOVY-12156: chooseBestMethod returns candidates in identity-hash order, 
making static-compilation output path-dependent
---
 .../transform/stc/StaticTypeCheckingSupport.java   |  6 ++-
 .../stc/StaticTypeCheckingSupportTest.groovy       | 57 ++++++++++++++++++++++
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index 0e319aebad..b6add39c9e 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1063,7 +1063,11 @@ public abstract class StaticTypeCheckingSupport {
                 return asList(methods); // GROOVY-11683: no covariants or 
equivalents
         }
 
-        Set<MethodNode> bestMethods = new HashSet<>(); // choose best 
method(s) for each possible receiver
+        // GROOVY-12156: MethodNode has identity equals/hashCode, so an 
unordered set would
+        // return the candidates in identity-hash order; that order varies 
with the compiling
+        // thread and its allocation history, and callers that accept more 
than one candidate
+        // (union-type receiver, method reference, macro dispatch) let it 
reach generated code
+        Set<MethodNode> bestMethods = new LinkedHashSet<>(); // choose best 
method(s) for each possible receiver
         for (ClassNode rcvr : duckType ? ((UnionTypeClassNode) 
receiver).getDelegates() : new ClassNode[]{receiver}) {
             var view = methods;
             if (duckType) {
diff --git 
a/src/test/groovy/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupportTest.groovy
 
b/src/test/groovy/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupportTest.groovy
new file mode 100644
index 0000000000..f8e49e68bd
--- /dev/null
+++ 
b/src/test/groovy/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupportTest.groovy
@@ -0,0 +1,57 @@
+/*
+ *  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.stc
+
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.junit.jupiter.api.Test
+
+import java.lang.reflect.Modifier
+
+import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE
+import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE
+import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod
+
+final class StaticTypeCheckingSupportTest {
+
+    // GROOVY-12156
+    @Test
+    void testUnionTypeCandidateOrder() {
+        // MethodNode has identity equals/hashCode, and HotSpot's identity 
hash comes from a
+        // thread-local PRNG, so the hash a node receives depends on how many 
identity hashes
+        // the thread has already handed out; vary that, as a real compile 
would
+        (0..<64).each { int warmup ->
+            warmup.times { new Object().hashCode() }
+
+            ClassNode a = new ClassNode('A', Modifier.PUBLIC, OBJECT_TYPE)
+            ClassNode b = new ClassNode('B', Modifier.PUBLIC, OBJECT_TYPE)
+            MethodNode am = a.addMethod('m', Modifier.PUBLIC, STRING_TYPE, 
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null)
+            MethodNode bm = b.addMethod('m', Modifier.PUBLIC, Integer_TYPE, 
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null)
+
+            // GROOVY-8965: candidates must follow the union's delegate order; 
callers that accept
+            // more than one candidate fold their return types with 
lowestUpperBound, so the order
+            // reaches the inferred type and therefore the generated bytecode
+            def candidates = chooseBestMethod(new UnionTypeClassNode(a, b), 
[am, bm], ClassNode.EMPTY_ARRAY)
+
+            assert candidates == [am, bm]
+        }
+    }
+}

Reply via email to