Treat empty and uninitialized Maps the same in equality checks (#638)
Treat an empty Map the same as an uninitialized Map when performing
equality checks. This ensures that reading a map field does not alter
the hashCode or equality comparison of the message itself.
Co-authored-by: Alex Greaves <[email protected]>
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 5044e86..89bb811 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -636,6 +636,11 @@
// We don't want reading a field to change equality comparisons.
if (val is List && val.isEmpty) return true;
+ // An empty map field is the same as uninitialized.
+ // This is because accessing a map field automatically creates it.
+ // We don't want reading a field to change equality comparisons.
+ if (val is Map && val.isEmpty) return true;
+
// For now, initialized and uninitialized fields are different.
// TODO(skybrian) consider other cases; should we compare with the
// default value or not?
@@ -693,6 +698,10 @@
return hash; // It's either repeated or an empty byte array.
}
+ if (value is Map && value.isEmpty) {
+ return hash;
+ }
+
hash = _HashUtils._combine(hash, fi.tagNumber);
if (_isBytes(fi.type)) {
// Bytes are represented as a List<int> (Usually with byte-data).
diff --git a/protoc_plugin/test/map_field_test.dart b/protoc_plugin/test/map_field_test.dart
index 3bd364f..eae4cfb 100644
--- a/protoc_plugin/test/map_field_test.dart
+++ b/protoc_plugin/test/map_field_test.dart
@@ -301,7 +301,7 @@
expect(value is Map<int, List<int>>, true);
});
- test('named optional arguments in cosntructor', () {
+ test('named optional arguments in constructor', () {
final testMap = TestMap(
int32ToInt32Field: {1: 11, 2: 22, 3: 33},
int32ToStringField: {1: '11', 2: '22', 3: '33'},
@@ -346,4 +346,14 @@
expect(m.stringToInt32Field[''], 11);
expect(m.stringToInt32Field['def'], 42);
});
+
+ test('Map field reads should not affect equality or hash of message', () {
+ final m1 = TestMap.create();
+ final m2 = TestMap.create();
+ expect(m1, equals(m2));
+ expect(m1.hashCode, equals(m2.hashCode));
+ m1.int32ToStringField; // read a map field
+ expect(m1, equals(m2));
+ expect(m1.hashCode, equals(m2.hashCode));
+ });
}