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.