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 34fa727ce9 GROOVY-12017: Tuple equals should follow Groovy's List
equals semantics
34fa727ce9 is described below
commit 34fa727ce9c6da7e4de83b30bbfbece91f8172f8
Author: Paul King <[email protected]>
AuthorDate: Mon May 18 11:18:46 2026 +1000
GROOVY-12017: Tuple equals should follow Groovy's List equals semantics
---
src/main/java/groovy/lang/Tuple.java | 2 +-
.../typehandling/DefaultTypeTransformation.java | 8 +++++
src/test/groovy/groovy/lang/TupleTest.java | 37 ++++++++++++++++++++++
3 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/src/main/java/groovy/lang/Tuple.java
b/src/main/java/groovy/lang/Tuple.java
index b6204abdd1..bd37eebfb9 100644
--- a/src/main/java/groovy/lang/Tuple.java
+++ b/src/main/java/groovy/lang/Tuple.java
@@ -127,7 +127,7 @@ public class Tuple<E> extends AbstractList<E> implements
Serializable, Cloneable
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof Tuple that)) return false;
+ if (!(o instanceof List<?> that)) return false;
int size = size();
if (size != that.size()) return false;
diff --git
a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
index aa62aa7344..611e01c07d 100644
---
a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
+++
b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
@@ -1190,6 +1190,14 @@ public class DefaultTypeTransformation {
if (left == right) return true;
if (left == null) return right instanceof NullObject;
if (right == null) return left instanceof NullObject;
+ // An object that is both Comparable and a List (e.g. Tuple) must use
+ // list equality here, consistent with how a plain (non-Comparable)
+ // List is compared, rather than taking the Comparable/compareTo
+ // short-circuit below; otherwise `tuple == [..]` would be asymmetric
+ // with `[..] == tuple` and inconsistent with Tuple.equals(List).
+ if (left instanceof List && right instanceof List) {
+ return DefaultGroovyMethods.equals((List) left, (List) right);
+ }
if (left instanceof Comparable) {
return compareToWithEqualityCheck(left, right, true) == 0;
}
diff --git a/src/test/groovy/groovy/lang/TupleTest.java
b/src/test/groovy/groovy/lang/TupleTest.java
index bfe3ecb3e6..12d39a3d13 100644
--- a/src/test/groovy/groovy/lang/TupleTest.java
+++ b/src/test/groovy/groovy/lang/TupleTest.java
@@ -21,6 +21,7 @@ package groovy.lang;
import groovy.test.GroovyTestCase;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -293,4 +294,40 @@ public class TupleTest extends GroovyTestCase {
assert false : e.getMessage();
}
}
+
+ // Regression coverage for making Tuple equality symmetric with List
+ // (broaden Tuple.equals from `instanceof Tuple` to `instanceof List`,
+ // keeping Groovy's number-aware element comparison consistent with
+ // DefaultGroovyMethods.equals(List, List)).
+ public void testTupleEqualsBehavesLikeList() throws Exception {
+ Tuple2<Integer, Integer> t = new Tuple2<>(1, 2);
+ List<Integer> list = Arrays.asList(1, 2);
+
+ // .equals() symmetric in both operand orders
+ assertEquals("Tuple2.equals(List)", t, list);
+ assertEquals("List.equals(Tuple2)", list, t);
+ // hashCode is already the List formula, so it stays consistent
+ assertEquals("hashCode parity with List", t.hashCode(),
list.hashCode());
+
+ // Groovy == in both operand orders
+ assertScript(
+ "def t = new Tuple2(1, 2)\n" +
+ "assert t == [1, 2]\n" +
+ "assert [1, 2] == t\n" +
+ // number-aware elements, consistent with equals(List, List)
+ "assert t == [1L, 2L]\n" +
+ "assert [1L, 2L] == t\n");
+
+ // number-aware elements via .equals(), consistent with equals(List,
List)
+ assertEquals("number-aware elements", t, Arrays.asList(1L, 2L));
+
+ // unchanged: Tuple vs Tuple
+ assertEquals("Tuple vs Tuple", new Tuple2<>(1, 2), new Tuple2<>(1, 2));
+ // empty tuple vs empty list
+ assertEquals("empty Tuple vs empty List", tuple(), Arrays.asList());
+
+ // guardrails: differing size / content are not equal
+ assertNotEquals(t, Arrays.asList(1, 2, 3), "size mismatch");
+ assertNotEquals(t, Arrays.asList(1, 9), "content mismatch");
+ }
}