Make Equality.hash accept null as an argument.
This just makes it easier to use the equality directly on a nullable type.
The `isValidKey` check still say no for `null`.
R=floitsch@google.com
Review-Url: https://codereview.chromium.org//2898693002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99481ca..8ece77f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.14.1
+
+* Make `Equality` implementations accept `null` as argument to `hash`.
+
## 1.14.0
* Add `CombinedListView`, a view of several lists concatenated together.
diff --git a/lib/src/equality.dart b/lib/src/equality.dart
index aa5effa..a970664 100644
--- a/lib/src/equality.dart
+++ b/lib/src/equality.dart
@@ -94,6 +94,10 @@
/// Equality on iterables.
///
/// Two iterables are equal if they have the same elements in the same order.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
class IterableEquality<E> implements Equality<Iterable<E>> {
final Equality<E> _elementEquality;
const IterableEquality(
@@ -114,6 +118,7 @@
}
int hash(Iterable<E> elements) {
+ if (elements == null) return null.hashCode;
// Jenkins's one-at-a-time hash function.
int hash = 0;
for (E element in elements) {
@@ -138,29 +143,34 @@
///
/// This is effectively the same as [IterableEquality] except that it
/// accesses elements by index instead of through iteration.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
class ListEquality<E> implements Equality<List<E>> {
final Equality<E> _elementEquality;
const ListEquality([Equality<E> elementEquality = const DefaultEquality()])
: _elementEquality = elementEquality;
- bool equals(List<E> e1, List<E> e2) {
- if (identical(e1, e2)) return true;
- if (e1 == null || e2 == null) return false;
- int length = e1.length;
- if (length != e2.length) return false;
+ bool equals(List<E> list1, List<E> list2) {
+ if (identical(list1, list2)) return true;
+ if (list1 == null || list2 == null) return false;
+ int length = list1.length;
+ if (length != list2.length) return false;
for (int i = 0; i < length; i++) {
- if (!_elementEquality.equals(e1[i], e2[i])) return false;
+ if (!_elementEquality.equals(list1[i], list2[i])) return false;
}
return true;
}
- int hash(List<E> e) {
+ int hash(List<E> list) {
+ if (elements == null) return null.hashCode;
// Jenkins's one-at-a-time hash function.
// This code is almost identical to the one in IterableEquality, except
// that it uses indexing instead of iterating to get the elements.
int hash = 0;
- for (int i = 0; i < e.length; i++) {
- int c = _elementEquality.hash(e[i]);
+ for (int i = 0; i < list.length; i++) {
+ int c = _elementEquality.hash(list[i]);
hash = (hash + c) & _HASH_MASK;
hash = (hash + (hash << 10)) & _HASH_MASK;
hash ^= (hash >> 6);
@@ -180,21 +190,21 @@
const _UnorderedEquality(this._elementEquality);
- bool equals(T e1, T e2) {
- if (identical(e1, e2)) return true;
- if (e1 == null || e2 == null) return false;
+ bool equals(T elements1, T elements2) {
+ if (identical(elements1, elements2)) return true;
+ if (elements1 == null || elements2 == null) return false;
HashMap<E, int> counts = new HashMap(
equals: _elementEquality.equals,
hashCode: _elementEquality.hash,
isValidKey: _elementEquality.isValidKey);
int length = 0;
- for (var e in e1) {
+ for (var e in elements1) {
int count = counts[e];
if (count == null) count = 0;
counts[e] = count + 1;
length++;
}
- for (var e in e2) {
+ for (var e in elements2) {
int count = counts[e];
if (count == null || count == 0) return false;
counts[e] = count - 1;
@@ -203,9 +213,10 @@
return length == 0;
}
- int hash(T e) {
+ int hash(T elements) {
+ if (elements == null) return null.hashCode;
int hash = 0;
- for (E element in e) {
+ for (E element in elements) {
int c = _elementEquality.hash(element);
hash = (hash + c) & _HASH_MASK;
}
@@ -237,6 +248,10 @@
///
/// This equality behaves the same as [UnorderedIterableEquality] except that
/// it expects sets instead of iterables as arguments.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
const SetEquality([Equality<E> elementEquality = const DefaultEquality()])
: super(elementEquality);
@@ -271,6 +286,10 @@
///
/// Two maps are equal if they have the same number of entries, and if the
/// entries of the two maps are pairwise equal on both key and value.
+///
+/// The [equals] and [hash] methods accepts `null` values,
+/// even if the [isValidKey] returns `false` for `null`.
+/// The [hash] of `null` is `null.hashCode`.
class MapEquality<K, V> implements Equality<Map<K, V>> {
final Equality<K> _keyEquality;
final Equality<V> _valueEquality;
@@ -280,20 +299,20 @@
: _keyEquality = keys,
_valueEquality = values;
- bool equals(Map<K, V> e1, Map<K, V> e2) {
- if (identical(e1, e2)) return true;
- if (e1 == null || e2 == null) return false;
- int length = e1.length;
- if (length != e2.length) return false;
+ bool equals(Map<K, V> map1, Map<K, V> map2) {
+ if (identical(map1, map2)) return true;
+ if (map1 == null || map2 == null) return false;
+ int length = map1.length;
+ if (length != map2.length) return false;
Map<_MapEntry, int> equalElementCounts = new HashMap();
- for (K key in e1.keys) {
- _MapEntry entry = new _MapEntry(this, key, e1[key]);
+ for (K key in map1.keys) {
+ _MapEntry entry = new _MapEntry(this, key, map1[key]);
int count = equalElementCounts[entry];
if (count == null) count = 0;
equalElementCounts[entry] = count + 1;
}
- for (K key in e2.keys) {
- _MapEntry entry = new _MapEntry(this, key, e2[key]);
+ for (K key in map2.keys) {
+ _MapEntry entry = new _MapEntry(this, key, map2[key]);
int count = equalElementCounts[entry];
if (count == null || count == 0) return false;
equalElementCounts[entry] = count - 1;
@@ -302,6 +321,7 @@
}
int hash(Map<K, V> map) {
+ if (map == null) return null.hashCode;
int hash = 0;
for (K key in map.keys) {
int keyHash = _keyEquality.hash(key);
@@ -348,7 +368,7 @@
for (Equality<E> eq in _equalities) {
if (eq.isValidKey(e)) return eq.hash(e);
}
- return -1;
+ return 0;
}
bool isValidKey(Object o) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 75104ff..4ba307d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: collection
-version: 1.14.0+1
+version: 1.14.1
author: Dart Team <misc@dartlang.org>
description: Collections and utilities functions and classes related to collections.
homepage: https://www.github.com/dart-lang/collection
diff --git a/test/equality_test.dart b/test/equality_test.dart
index f407fab..63399c1 100644
--- a/test/equality_test.dart
+++ b/test/equality_test.dart
@@ -202,6 +202,32 @@
expect(firstObjectEquality.isValidKey([{}]), isFalse);
});
});
+
+ test("Equality accepts null", () {
+ var ie = new IterableEquality();
+ var le = new ListEquality();
+ var se = new SetEquality();
+ var me = new MapEquality();
+ expect(ie.equals(null, null), true);
+ expect(ie.equals([], null), false);
+ expect(ie.equals(null, []), false);
+ expect(ie.hash(null), null.hashCode);
+
+ expect(le.equals(null, null), true);
+ expect(le.equals([], null), false);
+ expect(le.equals(null, []), false);
+ expect(le.hash(null), null.hashCode);
+
+ expect(se.equals(null, null), true);
+ expect(se.equals(new Set(), null), false);
+ expect(se.equals(null, new Set()), false);
+ expect(se.hash(null), null.hashCode);
+
+ expect(me.equals(null, null), true);
+ expect(me.equals({}, null), false);
+ expect(me.equals(null, {}), false);
+ expect(me.hash(null), null.hashCode);
+ });
}
/// Wrapper objects for an `id` value.