Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
82065a8
Improve efficiency in some methods
tarrinneal Feb 28, 2026
9861c00
analyze this
tarrinneal Feb 28, 2026
11daedd
format
tarrinneal Mar 2, 2026
e197fc0
less repeating, and less listing
tarrinneal Mar 3, 2026
cbf4096
allow individual class types to not have colliding hashes
tarrinneal Mar 3, 2026
935aa47
improve deep equals for non-collections
tarrinneal Mar 3, 2026
4e9541f
allowing for NaN or other cases where identical is better
tarrinneal Mar 3, 2026
f30b44c
more tests, add other languages, unify behavior across platforms
tarrinneal Mar 3, 2026
08a79f3
first pass at gobject
tarrinneal Mar 3, 2026
8cb51ac
Merge branch 'main' of https://github.com/flutter/packages into effic
tarrinneal Mar 3, 2026
ddc41c2
analyze
tarrinneal Mar 3, 2026
4ad3636
delete random extra files
tarrinneal Mar 3, 2026
0a08ef5
fix kotlin generator change that broke unit test
tarrinneal Mar 3, 2026
5855b23
fix broken gobject and cpp code
tarrinneal Mar 4, 2026
f99e64a
more bugs on languages I can't compile locally
tarrinneal Mar 4, 2026
101fb0f
last few issues I hope
tarrinneal Mar 4, 2026
7309d17
not being able to run things locally sucks
tarrinneal Mar 4, 2026
24d0f23
bigobj
tarrinneal Mar 4, 2026
21ad393
consolidate tests
tarrinneal Mar 4, 2026
94a58c8
revert project files
tarrinneal Mar 4, 2026
3fdd987
More consistency across languages
tarrinneal Mar 4, 2026
b920f50
revert -0.0 changes
tarrinneal Mar 4, 2026
7df481e
gen example
tarrinneal Mar 5, 2026
25021e7
Merge branch 'main' of https://github.com/flutter/packages into effic
tarrinneal Mar 5, 2026
4cbfdcd
memecmp
tarrinneal Mar 5, 2026
8c75a36
re-normalize -0.0 in hash methods to create consistent behavior acros…
tarrinneal Mar 5, 2026
655229c
finish tests
tarrinneal Mar 5, 2026
80d8bbc
lints and reverting of accidentally pushed files
tarrinneal Mar 5, 2026
a57dd6f
hopeful test fix
tarrinneal Mar 5, 2026
4ded545
delete file that shouldn't ever have existed
tarrinneal Mar 5, 2026
8698cb5
more tests and windows method rework
tarrinneal Mar 5, 2026
7355a96
bug fixes and more robust map==
tarrinneal Mar 5, 2026
45d8207
fix linix
tarrinneal Mar 5, 2026
97b8e8c
one last linux fix
tarrinneal Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 26.2.0

* Optimizes and improves data class equality and hashing.
* Changes hashing and equality methods to behave consistently across platforms.
* Adds equality methods to previously unsupported languages.

## 26.1.10

* Dramatically reduces the number of File write operations sent to the operating
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,146 @@
import java.lang.annotation.Target;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/** Generated class from Pigeon. */
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
public class Messages {
static boolean pigeonDeepEquals(Object a, Object b) {
if (a == b) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a instanceof byte[] && b instanceof byte[]) {
return Arrays.equals((byte[]) a, (byte[]) b);
}
if (a instanceof int[] && b instanceof int[]) {
return Arrays.equals((int[]) a, (int[]) b);
}
if (a instanceof long[] && b instanceof long[]) {
return Arrays.equals((long[]) a, (long[]) b);
}
if (a instanceof double[] && b instanceof double[]) {
double[] da = (double[]) a;
double[] db = (double[]) b;
if (da.length != db.length) return false;
for (int i = 0; i < da.length; i++) {
if (!pigeonDeepEquals(da[i], db[i])) {
return false;
}
}
return true;
}
if (a instanceof List && b instanceof List) {
List<?> listA = (List<?>) a;
List<?> listB = (List<?>) b;
if (listA.size() != listB.size()) {
return false;
}
for (int i = 0; i < listA.size(); i++) {
if (!pigeonDeepEquals(listA.get(i), listB.get(i))) {
return false;
}
}
return true;
}
if (a instanceof Map && b instanceof Map) {
Map<?, ?> mapA = (Map<?, ?>) a;
Map<?, ?> mapB = (Map<?, ?>) b;
if (mapA.size() != mapB.size()) {
return false;
}
for (Map.Entry<?, ?> entryA : mapA.entrySet()) {
Object keyA = entryA.getKey();
Object valueA = entryA.getValue();
boolean found = false;
for (Map.Entry<?, ?> entryB : mapB.entrySet()) {
Object keyB = entryB.getKey();
if (pigeonDeepEquals(keyA, keyB)) {
Object valueB = entryB.getValue();
if (pigeonDeepEquals(valueA, valueB)) {
found = true;
break;
}
}
}
if (!found) {
return false;
}
}
return true;
}
if (a instanceof Double && b instanceof Double) {
return ((double) a == 0.0 ? 0.0 : (double) a) == ((double) b == 0.0 ? 0.0 : (double) b)
|| (Double.isNaN((double) a) && Double.isNaN((double) b));
}
if (a instanceof Float && b instanceof Float) {
return ((float) a == 0.0f ? 0.0f : (float) a) == ((float) b == 0.0f ? 0.0f : (float) b)
|| (Float.isNaN((float) a) && Float.isNaN((float) b));
}
return a.equals(b);
}

static int pigeonDeepHashCode(Object value) {
if (value == null) {
return 0;
}
if (value instanceof byte[]) {
return Arrays.hashCode((byte[]) value);
}
if (value instanceof int[]) {
return Arrays.hashCode((int[]) value);
}
if (value instanceof long[]) {
return Arrays.hashCode((long[]) value);
}
if (value instanceof double[]) {
double[] da = (double[]) value;
int result = 1;
for (double d : da) {
result = 31 * result + pigeonDeepHashCode(d);
}
return result;
}
if (value instanceof List) {
int result = 1;
for (Object item : (List<?>) value) {
result = 31 * result + pigeonDeepHashCode(item);
}
return result;
}
if (value instanceof Map) {
int result = 0;
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
result += (pigeonDeepHashCode(entry.getKey()) ^ pigeonDeepHashCode(entry.getValue()));
}
return result;
}
if (value instanceof Object[]) {
int result = 1;
for (Object item : (Object[]) value) {
result = 31 * result + pigeonDeepHashCode(item);
}
return result;
}
if (value instanceof Double) {
double d = (double) value;
if (d == 0.0) d = 0.0;
long bits = Double.doubleToLongBits(d);
return (int) (bits ^ (bits >>> 32));
}
if (value instanceof Float) {
float f = (float) value;
if (f == 0.0f) f = 0.0f;
return Float.floatToIntBits(f);
}
return value.hashCode();
}

/** Error class for passing custom error details to Flutter via a thrown PlatformException. */
public static class FlutterError extends RuntimeException {
Expand Down Expand Up @@ -142,15 +274,16 @@ public boolean equals(Object o) {
return false;
}
MessageData that = (MessageData) o;
return Objects.equals(name, that.name)
&& Objects.equals(description, that.description)
&& code.equals(that.code)
&& data.equals(that.data);
return pigeonDeepEquals(name, that.name)
&& pigeonDeepEquals(description, that.description)
&& pigeonDeepEquals(code, that.code)
&& pigeonDeepEquals(data, that.data);
}

@Override
public int hashCode() {
return Objects.hash(name, description, code, data);
Object[] fields = new Object[] {getClass(), name, description, code, data};
return pigeonDeepHashCode(fields);
}

public static final class Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import java.nio.ByteBuffer

private object EventChannelMessagesPigeonUtils {
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
Expand All @@ -24,20 +27,101 @@ private object EventChannelMessagesPigeonUtils {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
return a.contentEquals(b)
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
return a.contentDeepEquals(b)
}
if (a is List<*> && b is List<*>) {
return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
}
if (a is Map<*, *> && b is Map<*, *>) {
return a.size == b.size &&
a.all { (b as Map<Any?, Any?>).contains(it.key) && deepEquals(it.value, b[it.key]) }
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
if (a is Float && b is Float) {
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) ||
(a.isNaN() && b.isNaN())
}
return a == b
}

fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Array<*> -> value.contentDeepHashCode()
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += (deepHash(entry.key) xor deepHash(entry.value))
}
result
}
is Double -> {
val d = if (value == 0.0) 0.0 else value
val bits = java.lang.Double.doubleToLongBits(d)
(bits xor (bits ushr 32)).toInt()
}
is Float -> {
val f = if (value == 0.0f) 0.0f else value
java.lang.Float.floatToIntBits(f)
}
else -> value.hashCode()
}
}
}

/**
Expand All @@ -61,16 +145,21 @@ data class IntEvent(val data: Long) : PlatformEvent() {
}

override fun equals(other: Any?): Boolean {
if (other !is IntEvent) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return EventChannelMessagesPigeonUtils.deepEquals(toList(), other.toList())
val other = other as IntEvent
return EventChannelMessagesPigeonUtils.deepEquals(this.data, other.data)
}

override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + EventChannelMessagesPigeonUtils.deepHash(this.data)
return result
}
}

/** Generated class from Pigeon that represents data sent in messages. */
Expand All @@ -89,16 +178,21 @@ data class StringEvent(val data: String) : PlatformEvent() {
}

override fun equals(other: Any?): Boolean {
if (other !is StringEvent) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return EventChannelMessagesPigeonUtils.deepEquals(toList(), other.toList())
val other = other as StringEvent
return EventChannelMessagesPigeonUtils.deepEquals(this.data, other.data)
}

override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + EventChannelMessagesPigeonUtils.deepHash(this.data)
return result
}
}

private open class EventChannelMessagesPigeonCodec : StandardMessageCodec() {
Expand Down
Loading
Loading