Skip to content

Commit 3e15247

Browse files
authored
[pigeon] Optimize data class equality and hashing in Dart, Kotlin, java, and Swift, adds equality in other languages (#11140)
This PR optimizes the generated equality (==) and hashing (hashCode/hash) logic for data classes across All languages. These have been bothering me for a while, and I wanted to break out a new pr before the NI code is in review.
1 parent 5a51924 commit 3e15247

75 files changed

Lines changed: 10792 additions & 1635 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/pigeon/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 26.3.0
2+
3+
* Optimizes and improves data class equality and hashing.
4+
* Changes hashing and equality methods to behave consistently across platforms.
5+
* Adds equality methods to previously unsupported languages.
6+
17
## 26.2.3
28

39
* Produces a helpful error message when a method return type is missing or an

packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java

Lines changed: 164 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,171 @@
1919
import java.lang.annotation.Target;
2020
import java.nio.ByteBuffer;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.Map;
25-
import java.util.Objects;
2626

2727
/** Generated class from Pigeon. */
2828
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
2929
public class Messages {
30+
static boolean pigeonDoubleEquals(double a, double b) {
31+
// Normalize -0.0 to 0.0 and handle NaN equality.
32+
return (a == 0.0 ? 0.0 : a) == (b == 0.0 ? 0.0 : b) || (Double.isNaN(a) && Double.isNaN(b));
33+
}
34+
35+
static boolean pigeonFloatEquals(float a, float b) {
36+
// Normalize -0.0 to 0.0 and handle NaN equality.
37+
return (a == 0.0f ? 0.0f : a) == (b == 0.0f ? 0.0f : b) || (Float.isNaN(a) && Float.isNaN(b));
38+
}
39+
40+
static int pigeonDoubleHashCode(double d) {
41+
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
42+
if (d == 0.0) {
43+
d = 0.0;
44+
}
45+
long bits = Double.doubleToLongBits(d);
46+
return (int) (bits ^ (bits >>> 32));
47+
}
48+
49+
static int pigeonFloatHashCode(float f) {
50+
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
51+
if (f == 0.0f) {
52+
f = 0.0f;
53+
}
54+
return Float.floatToIntBits(f);
55+
}
56+
57+
static boolean pigeonDeepEquals(Object a, Object b) {
58+
if (a == b) {
59+
return true;
60+
}
61+
if (a == null || b == null) {
62+
return false;
63+
}
64+
if (a instanceof byte[] && b instanceof byte[]) {
65+
return Arrays.equals((byte[]) a, (byte[]) b);
66+
}
67+
if (a instanceof int[] && b instanceof int[]) {
68+
return Arrays.equals((int[]) a, (int[]) b);
69+
}
70+
if (a instanceof long[] && b instanceof long[]) {
71+
return Arrays.equals((long[]) a, (long[]) b);
72+
}
73+
if (a instanceof double[] && b instanceof double[]) {
74+
double[] da = (double[]) a;
75+
double[] db = (double[]) b;
76+
if (da.length != db.length) {
77+
return false;
78+
}
79+
for (int i = 0; i < da.length; i++) {
80+
if (!pigeonDoubleEquals(da[i], db[i])) {
81+
return false;
82+
}
83+
}
84+
return true;
85+
}
86+
if (a instanceof List && b instanceof List) {
87+
List<?> listA = (List<?>) a;
88+
List<?> listB = (List<?>) b;
89+
if (listA.size() != listB.size()) {
90+
return false;
91+
}
92+
for (int i = 0; i < listA.size(); i++) {
93+
if (!pigeonDeepEquals(listA.get(i), listB.get(i))) {
94+
return false;
95+
}
96+
}
97+
return true;
98+
}
99+
if (a instanceof Map && b instanceof Map) {
100+
Map<?, ?> mapA = (Map<?, ?>) a;
101+
Map<?, ?> mapB = (Map<?, ?>) b;
102+
if (mapA.size() != mapB.size()) {
103+
return false;
104+
}
105+
for (Map.Entry<?, ?> entryA : mapA.entrySet()) {
106+
Object keyA = entryA.getKey();
107+
Object valueA = entryA.getValue();
108+
boolean found = false;
109+
for (Map.Entry<?, ?> entryB : mapB.entrySet()) {
110+
Object keyB = entryB.getKey();
111+
if (pigeonDeepEquals(keyA, keyB)) {
112+
Object valueB = entryB.getValue();
113+
if (pigeonDeepEquals(valueA, valueB)) {
114+
found = true;
115+
break;
116+
} else {
117+
return false;
118+
}
119+
}
120+
}
121+
if (!found) {
122+
return false;
123+
}
124+
}
125+
return true;
126+
}
127+
if (a instanceof Double && b instanceof Double) {
128+
return pigeonDoubleEquals((double) a, (double) b);
129+
}
130+
if (a instanceof Float && b instanceof Float) {
131+
return pigeonFloatEquals((float) a, (float) b);
132+
}
133+
return a.equals(b);
134+
}
135+
136+
static int pigeonDeepHashCode(Object value) {
137+
if (value == null) {
138+
return 0;
139+
}
140+
if (value instanceof byte[]) {
141+
return Arrays.hashCode((byte[]) value);
142+
}
143+
if (value instanceof int[]) {
144+
return Arrays.hashCode((int[]) value);
145+
}
146+
if (value instanceof long[]) {
147+
return Arrays.hashCode((long[]) value);
148+
}
149+
if (value instanceof double[]) {
150+
double[] da = (double[]) value;
151+
int result = 1;
152+
for (double d : da) {
153+
result = 31 * result + pigeonDoubleHashCode(d);
154+
}
155+
return result;
156+
}
157+
if (value instanceof List) {
158+
int result = 1;
159+
for (Object item : (List<?>) value) {
160+
result = 31 * result + pigeonDeepHashCode(item);
161+
}
162+
return result;
163+
}
164+
if (value instanceof Map) {
165+
int result = 0;
166+
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
167+
result +=
168+
((pigeonDeepHashCode(entry.getKey()) * 31) ^ pigeonDeepHashCode(entry.getValue()));
169+
}
170+
return result;
171+
}
172+
if (value instanceof Object[]) {
173+
int result = 1;
174+
for (Object item : (Object[]) value) {
175+
result = 31 * result + pigeonDeepHashCode(item);
176+
}
177+
return result;
178+
}
179+
if (value instanceof Double) {
180+
return pigeonDoubleHashCode((double) value);
181+
}
182+
if (value instanceof Float) {
183+
return pigeonFloatHashCode((float) value);
184+
}
185+
return value.hashCode();
186+
}
30187

31188
/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
32189
public static class FlutterError extends RuntimeException {
@@ -142,15 +299,16 @@ public boolean equals(Object o) {
142299
return false;
143300
}
144301
MessageData that = (MessageData) o;
145-
return Objects.equals(name, that.name)
146-
&& Objects.equals(description, that.description)
147-
&& code.equals(that.code)
148-
&& data.equals(that.data);
302+
return pigeonDeepEquals(name, that.name)
303+
&& pigeonDeepEquals(description, that.description)
304+
&& pigeonDeepEquals(code, that.code)
305+
&& pigeonDeepEquals(data, that.data);
149306
}
150307

151308
@Override
152309
public int hashCode() {
153-
return Objects.hash(name, description, code, data);
310+
Object[] fields = new Object[] {getClass(), name, description, code, data};
311+
return pigeonDeepHashCode(fields);
154312
}
155313

156314
public static final class Builder {

0 commit comments

Comments
 (0)