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]
+ }
+ }
+}