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 293ebc5000 GROOVY-12017: Tuple equals should follow Groovy's List
equals semantics (cont'd)
293ebc5000 is described below
commit 293ebc5000f8875fb61cefa4ea65e6602a5cdf1c
Author: Paul King <[email protected]>
AuthorDate: Mon May 18 14:53:07 2026 +1000
GROOVY-12017: Tuple equals should follow Groovy's List equals semantics
(cont'd)
---
.../groovy/runtime/DefaultGroovyMethods.java | 8 +++++-
src/test/groovy/groovy/lang/TupleTest.java | 31 ++++++++++++++++++++++
2 files changed, 38 insertions(+), 1 deletion(-)
diff --git
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 2de1cca996..5817e8b0dd 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -5141,7 +5141,13 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
}
static boolean coercedEquals(Object o1, Object o2) {
- if (o1 instanceof Comparable) {
+ // An object that is both Comparable and a List (e.g. Tuple) must be
+ // compared as a List here, consistent with how a plain
(non-Comparable)
+ // List is compared by DefaultTypeTransformation.compareEqual, rather
+ // than taking the Comparable/numberAwareCompareTo short-circuit below;
+ // otherwise nested Tuple/List equality (element-wise list equals)
+ // would be inconsistent with `==` and with Tuple.equals(List).
+ if (o1 instanceof Comparable && !(o1 instanceof List && o2 instanceof
List)) {
if (!(o2 instanceof Comparable &&
numberAwareCompareTo((Comparable) o1, (Comparable) o2) == 0)) {
return false;
}
diff --git a/src/test/groovy/groovy/lang/TupleTest.java
b/src/test/groovy/groovy/lang/TupleTest.java
index 12d39a3d13..f87d88301c 100644
--- a/src/test/groovy/groovy/lang/TupleTest.java
+++ b/src/test/groovy/groovy/lang/TupleTest.java
@@ -330,4 +330,35 @@ public class TupleTest extends GroovyTestCase {
assertNotEquals(t, Arrays.asList(1, 2, 3), "size mismatch");
assertNotEquals(t, Arrays.asList(1, 9), "content mismatch");
}
+
+ // Nested Tuple/List equality: the element comparator (coercedEquals) used
+ // by list/set/map equals and unique() must treat a Tuple like a List,
+ // consistent with == / compareEqual / Tuple.equals(List).
+ public void testNestedTupleEquality() throws Exception {
+ assertEquals("[Tuple2].equals([[..]])",
+ Arrays.asList(new Tuple2<>(1, 2), new Tuple2<>(2, 3)),
+ Arrays.asList(Arrays.asList(1, 2), Arrays.asList(2, 3)));
+ assertEquals("[[..]].equals([Tuple2])",
+ Arrays.asList(Arrays.asList(1, 2), Arrays.asList(2, 3)),
+ Arrays.asList(new Tuple2<>(1, 2), new Tuple2<>(2, 3)));
+
+ // Groovy == nested, both operand orders, number-aware
+ assertScript(
+ "assert [new Tuple2(1, 2), new Tuple2(2, 3)] == [[1, 2], [2,
3]]\n" +
+ "assert [[1, 2], [2, 3]] == [new Tuple2(1, 2), new Tuple2(2,
3)]\n" +
+ "assert [new Tuple2(1, 2)] == [[1L, 2L]]\n" +
+ "assert new Tuple2(1, 2) == new Tuple2(1L, 2L)\n" +
+ "assert [new Tuple2(1, 2)] == [new Tuple2(1L, 2L)]\n");
+
+ // number-heterogeneous tuple .equals() (previously risked CCE via
compareTo)
+ assertEquals("Tuple2(1,2).equals(Tuple2(1L,2L))",
+ new Tuple2<>(1, 2), new Tuple2<>(1L, 2L));
+
+ // unique()/toUnique() must dedupe a Tuple and its equivalent List
+ assertScript(
+ "assert [new Tuple2(1, 2), [1, 2]].unique().size() == 1\n" +
+ "assert [new Tuple2(1, 2), new Tuple2(1, 2)].unique().size()
== 1\n" +
+ "assert [[1, 2], [1, 2]].unique().size() == 1\n" +
+ "assert [new Tuple2(1, 2), new Tuple2(3, 4)].unique().size()
== 2\n");
+ }
}