Merge the null_safety branch into master (#143)

Migrates the package to null safety
diff --git a/.travis.yml b/.travis.yml
index 96ba17a..5801d68 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,27 +1,43 @@
 language: dart
 
 dart:
-- 2.3.0
 - dev
 
-dart_task:
-- test
-
-matrix:
+jobs:
   include:
-  # Only validate formatting using the dev release
-  - dart: dev
-    dart_task: dartfmt
-  - dart: dev
-    dart_task:
-      dartanalyzer: --fatal-infos --fatal-warnings .
-  - dart: 2.3.0
-    dart_task:
-      dartanalyzer: --fatal-warnings .
+    - stage: analyze_and_format
+      name: "Analyze lib/"
+      dart: dev
+      os: linux
+      script: dartanalyzer --fatal-warnings --fatal-infos lib/
+    - stage: analyze_and_format
+      name: "Analyze test/"
+      dart: dev
+      os: linux
+      script: dartanalyzer --enable-experiment=non-nullable --fatal-warnings --fatal-infos test/
+    - stage: analyze_and_format
+      name: "Format"
+      dart: dev
+      os: linux
+      script: dartfmt -n --set-exit-if-changed .
+    - stage: test
+      name: "Vm Tests"
+      dart: dev
+      os: linux
+      script: pub run --enable-experiment=non-nullable test -p vm 
+    - stage: test
+      name: "Web Tests"
+      dart: dev
+      os: linux
+      script: pub run --enable-experiment=non-nullable test -p chrome
+
+stages:
+  - analyze_and_format
+  - test
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
-  only: [master]
+  only: [master, null_safety]
 
 # Incremental pub cache and builds.
 cache:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19e4773..3a47241 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 1.15.0-nnbd
+
+Pre-release for the null safety migration of this package.
+
+Note that `1.15.0` may not be the final stable null safety release version,
+we reserve the right to release it as a `2.0.0` breaking change.
+
+This release will be pinned to only allow pre-release sdk versions starting
+from `2.9.0-dev.18.0`, which is the first version where this package will
+appear in the null safety allow list.
+
 ## 1.14.13
 
 * Deprecate `mapMap`. The Map interface has a `map` call and map literals can
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 05bafc1..5adddb1 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -5,6 +5,9 @@
     unused_import: error
     unused_local_variable: error
     dead_code: error
+  enable-experiment:
+    - non-nullable
+
 linter:
   rules:
     # Errors
diff --git a/lib/src/algorithms.dart b/lib/src/algorithms.dart
index cb6a03f..616be85 100644
--- a/lib/src/algorithms.dart
+++ b/lib/src/algorithms.dart
@@ -16,7 +16,8 @@
 /// (`CastError` on some SDK versions).
 ///
 /// Returns -1 if [value] is not in the list by default.
-int binarySearch<T>(List<T> sortedList, T value, {int Function(T, T) compare}) {
+int binarySearch<T>(List<T> sortedList, T value,
+    {int Function(T, T)? compare}) {
   compare ??= defaultCompare<T>();
   var min = 0;
   var max = sortedList.length;
@@ -46,7 +47,7 @@
 ///
 /// Returns [sortedList.length] if all the items in [sortedList] compare less
 /// than [value].
-int lowerBound<T>(List<T> sortedList, T value, {int Function(T, T) compare}) {
+int lowerBound<T>(List<T> sortedList, T value, {int Function(T, T)? compare}) {
   compare ??= defaultCompare<T>();
   var min = 0;
   var max = sortedList.length;
@@ -66,7 +67,7 @@
 /// Shuffles a list randomly.
 ///
 /// A sub-range of a list can be shuffled by providing [start] and [end].
-void shuffle(List list, [int start = 0, int end]) {
+void shuffle(List list, [int start = 0, int? end]) {
   var random = math.Random();
   end ??= list.length;
   var length = end - start;
@@ -80,7 +81,7 @@
 }
 
 /// Reverses a list, or a part of a list, in-place.
-void reverse(List list, [int start = 0, int end]) {
+void reverse(List list, [int start = 0, int? end]) {
   end ??= list.length;
   _reverse(list, start, end);
 }
@@ -111,7 +112,7 @@
 /// This insertion sort is stable: Equal elements end up in the same order
 /// as they started in.
 void insertionSort<T>(List<T> list,
-    {int Function(T, T) compare, int start = 0, int end}) {
+    {int Function(T, T)? compare, int start = 0, int? end}) {
   // If the same method could have both positional and named optional
   // parameters, this should be (list, [start, end], {compare}).
   compare ??= defaultCompare<T>();
@@ -155,8 +156,8 @@
 /// This merge sort is stable: Equal elements end up in the same order
 /// as they started in.
 void mergeSort<T>(List<T> list,
-    {int start = 0, int end, int Function(T, T) compare}) {
-  end ??= list.length;
+    {int start = 0, int? end, int Function(T, T)? compare}) {
+  end = RangeError.checkValidRange(start, end, list.length);
   compare ??= defaultCompare<T>();
 
   var length = end - start;
@@ -175,12 +176,12 @@
   var firstLength = middle - start;
   var secondLength = end - middle;
   // secondLength is always the same as firstLength, or one greater.
-  var scratchSpace = List<T>(secondLength);
-  _mergeSort(list, compare, middle, end, scratchSpace, 0);
+  var scratchSpace = List<T>.filled(secondLength, list[start]);
+  _mergeSort<T>(list, compare, middle, end, scratchSpace, 0);
   var firstTarget = end - firstLength;
-  _mergeSort(list, compare, start, middle, list, firstTarget);
-  _merge(compare, list, firstTarget, end, scratchSpace, 0, secondLength, list,
-      start);
+  _mergeSort<T>(list, compare, start, middle, list, firstTarget);
+  _merge<T>(compare, list, firstTarget, end, scratchSpace, 0, secondLength,
+      list, start);
 }
 
 /// Performs an insertion sort into a potentially different list than the
@@ -198,7 +199,7 @@
     var max = targetOffset + i;
     while (min < max) {
       var mid = min + ((max - min) >> 1);
-      if (compare(element, target[mid]) < 0) {
+      if (compare(element, target[mid]!) < 0) {
         max = mid;
       } else {
         min = mid + 1;
@@ -220,7 +221,7 @@
     List<T> target, int targetOffset) {
   var length = end - start;
   if (length < _MERGE_SORT_LIMIT) {
-    _movingInsertionSort(list, compare, start, end, target, targetOffset);
+    _movingInsertionSort<T>(list, compare, start, end, target, targetOffset);
     return;
   }
   var middle = start + (length >> 1);
@@ -229,11 +230,11 @@
   // Here secondLength >= firstLength (differs by at most one).
   var targetMiddle = targetOffset + firstLength;
   // Sort the second half into the end of the target area.
-  _mergeSort(list, compare, middle, end, target, targetMiddle);
+  _mergeSort<T>(list, compare, middle, end, target, targetMiddle);
   // Sort the first half into the end of the source area.
-  _mergeSort(list, compare, start, middle, list, middle);
+  _mergeSort<T>(list, compare, start, middle, list, middle);
   // Merge the two parts into the target area.
-  _merge(compare, list, middle, middle + firstLength, target, targetMiddle,
+  _merge<T>(compare, list, middle, middle + firstLength, target, targetMiddle,
       targetMiddle + secondLength, target, targetOffset);
 }
 
@@ -261,7 +262,7 @@
   var cursor1 = firstStart;
   var cursor2 = secondStart;
   var firstElement = firstList[cursor1++];
-  var secondElement = secondList[cursor2++];
+  var secondElement = secondList[cursor2++]!;
   while (true) {
     if (compare(firstElement, secondElement) <= 0) {
       target[targetOffset++] = firstElement;
@@ -270,7 +271,7 @@
     } else {
       target[targetOffset++] = secondElement;
       if (cursor2 != secondEnd) {
-        secondElement = secondList[cursor2++];
+        secondElement = secondList[cursor2++]!;
         continue;
       }
       // Second list empties first. Flushing first list here.
diff --git a/lib/src/canonicalized_map.dart b/lib/src/canonicalized_map.dart
index 16e55e4..280050c 100644
--- a/lib/src/canonicalized_map.dart
+++ b/lib/src/canonicalized_map.dart
@@ -12,13 +12,10 @@
 /// more efficient than a [LinkedHashMap] with a custom equality operator
 /// because it only canonicalizes each key once, rather than doing so for each
 /// comparison.
-///
-/// By default, `null` is allowed as a key. It can be forbidden via the
-/// `isValidKey` parameter.
 class CanonicalizedMap<C, K, V> implements Map<K, V> {
   final C Function(K) _canonicalize;
 
-  final bool Function(Object) _isValidKeyFn;
+  final bool Function(K)? _isValidKeyFn;
 
   final _base = <C, Pair<K, V>>{};
 
@@ -31,7 +28,7 @@
   /// methods that take arbitrary objects. It can be used to filter out keys
   /// that can't be canonicalized.
   CanonicalizedMap(C Function(K key) canonicalize,
-      {bool Function(Object key) isValidKey})
+      {bool Function(K key)? isValidKey})
       : _canonicalize = canonicalize,
         _isValidKeyFn = isValidKey;
 
@@ -45,14 +42,14 @@
   /// methods that take arbitrary objects. It can be used to filter out keys
   /// that can't be canonicalized.
   CanonicalizedMap.from(Map<K, V> other, C Function(K key) canonicalize,
-      {bool Function(Object key) isValidKey})
+      {bool Function(K key)? isValidKey})
       : _canonicalize = canonicalize,
         _isValidKeyFn = isValidKey {
     addAll(other);
   }
 
   @override
-  V operator [](Object key) {
+  V? operator [](Object? key) {
     if (!_isValidKey(key)) return null;
     var pair = _base[_canonicalize(key as K)];
     return pair == null ? null : pair.last;
@@ -82,13 +79,13 @@
   }
 
   @override
-  bool containsKey(Object key) {
+  bool containsKey(Object? key) {
     if (!_isValidKey(key)) return false;
     return _base.containsKey(_canonicalize(key as K));
   }
 
   @override
-  bool containsValue(Object value) =>
+  bool containsValue(Object? value) =>
       _base.values.any((pair) => pair.last == value);
 
   @override
@@ -124,7 +121,7 @@
   }
 
   @override
-  V remove(Object key) {
+  V? remove(Object? key) {
     if (!_isValidKey(key)) return null;
     var pair = _base.remove(_canonicalize(key as K));
     return pair == null ? null : pair.last;
@@ -138,7 +135,7 @@
   Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
 
   @override
-  V update(K key, V Function(V) update, {V Function() ifAbsent}) => _base
+  V update(K key, V Function(V) update, {V Function()? ifAbsent}) => _base
       .update(_canonicalize(key), (pair) => Pair(key, update(pair.last)),
           ifAbsent: ifAbsent == null ? null : () => Pair(key, ifAbsent()))
       .last;
@@ -178,9 +175,8 @@
     return result.toString();
   }
 
-  bool _isValidKey(Object key) =>
-      (key == null || key is K) &&
-      (_isValidKeyFn == null || _isValidKeyFn(key));
+  bool _isValidKey(Object? key) =>
+      (key is K) && (_isValidKeyFn == null || _isValidKeyFn!(key));
 }
 
 /// A collection used to identify cyclic maps during toString() calls.
diff --git a/lib/src/combined_wrappers/combined_iterable.dart b/lib/src/combined_wrappers/combined_iterable.dart
index ce19c20..8c5f091 100644
--- a/lib/src/combined_wrappers/combined_iterable.dart
+++ b/lib/src/combined_wrappers/combined_iterable.dart
@@ -26,7 +26,7 @@
   // efficient implementation instead of running through the entire iterator.
 
   @override
-  bool contains(Object element) => _iterables.any((i) => i.contains(element));
+  bool contains(Object? element) => _iterables.any((i) => i.contains(element));
 
   @override
   bool get isEmpty => _iterables.every((i) => i.isEmpty);
@@ -45,17 +45,30 @@
   /// avoid instantiating unnecessary iterators.
   final Iterator<Iterator<T>> _iterators;
 
-  _CombinedIterator(this._iterators);
+  /// The current iterator in [_iterators], or `null` if done iterating.
+  Iterator<T>? _currentItr;
+
+  _CombinedIterator(this._iterators) {
+    _advance();
+  }
 
   @override
-  T get current => _iterators.current?.current;
+  T get current => _iterators.current.current;
 
   @override
   bool moveNext() {
-    var current = _iterators.current;
-    if (current != null && current.moveNext()) {
+    if (_currentItr == null) return false;
+    if (_currentItr!.moveNext()) {
       return true;
+    } else {
+      _advance();
     }
-    return _iterators.moveNext() && moveNext();
+    return moveNext();
+  }
+
+  /// Advances [_currentItr] or sets it to `null` if there are no more entries
+  /// in [_iterators].
+  void _advance() {
+    _currentItr = _iterators.moveNext() ? _iterators.current : null;
   }
 }
diff --git a/lib/src/combined_wrappers/combined_list.dart b/lib/src/combined_wrappers/combined_list.dart
index 961ea42..e341be4 100644
--- a/lib/src/combined_wrappers/combined_list.dart
+++ b/lib/src/combined_wrappers/combined_list.dart
@@ -15,7 +15,7 @@
 /// both `O(lists)` rather than `O(1)`. A [CombinedListView] is unmodifiable.
 class CombinedListView<T> extends ListBase<T>
     implements UnmodifiableListView<T> {
-  static void _throw() {
+  static Never _throw() {
     throw UnsupportedError('Cannot modify an unmodifiable List');
   }
 
@@ -57,9 +57,8 @@
   }
 
   @override
-  bool remove(Object element) {
+  bool remove(Object? element) {
     _throw();
-    return null;
   }
 
   @override
diff --git a/lib/src/combined_wrappers/combined_map.dart b/lib/src/combined_wrappers/combined_map.dart
index 8e2f729..ae72006 100644
--- a/lib/src/combined_wrappers/combined_map.dart
+++ b/lib/src/combined_wrappers/combined_map.dart
@@ -29,7 +29,7 @@
   CombinedMapView(this._maps);
 
   @override
-  V operator [](Object key) {
+  V? operator [](Object? key) {
     for (var map in _maps) {
       // Avoid two hash lookups on a positive hit.
       var value = map[key];
@@ -75,7 +75,7 @@
   // duplicates.
 
   @override
-  bool contains(Object element) => _iterable.contains(element);
+  bool contains(Object? element) => _iterable.contains(element);
 
   @override
   bool get isEmpty => _iterable.isEmpty;
diff --git a/lib/src/empty_unmodifiable_set.dart b/lib/src/empty_unmodifiable_set.dart
index 86cbbb4..6d7f03c 100644
--- a/lib/src/empty_unmodifiable_set.dart
+++ b/lib/src/empty_unmodifiable_set.dart
@@ -25,18 +25,18 @@
   @override
   EmptyUnmodifiableSet<T> cast<T>() => EmptyUnmodifiableSet<T>();
   @override
-  bool contains(Object element) => false;
+  bool contains(Object? element) => false;
   @override
-  bool containsAll(Iterable<Object> other) => other.isEmpty;
+  bool containsAll(Iterable<Object?> other) => other.isEmpty;
   @override
   Iterable<E> followedBy(Iterable<E> other) => Set.from(other);
   @override
-  E lookup(Object element) => null;
+  E? lookup(Object? element) => null;
   @deprecated
   @override
   EmptyUnmodifiableSet<T> retype<T>() => EmptyUnmodifiableSet<T>();
   @override
-  E singleWhere(bool Function(E) test, {E Function() orElse}) =>
+  E singleWhere(bool Function(E) test, {E Function()? orElse}) =>
       super.singleWhere(test);
   @override
   Iterable<T> whereType<T>() => EmptyUnmodifiableSet<T>();
@@ -45,9 +45,9 @@
   @override
   Set<E> union(Set<E> other) => Set.from(other);
   @override
-  Set<E> intersection(Set<Object> other) => {};
+  Set<E> intersection(Set<Object?> other) => {};
   @override
-  Set<E> difference(Set<Object> other) => {};
+  Set<E> difference(Set<Object?> other) => {};
 
   @override
   bool add(E value) => _throw();
@@ -56,13 +56,13 @@
   @override
   void clear() => _throw();
   @override
-  bool remove(Object element) => _throw();
+  bool remove(Object? element) => _throw();
   @override
-  void removeAll(Iterable<Object> elements) => _throw();
+  void removeAll(Iterable<Object?> elements) => _throw();
   @override
   void removeWhere(bool Function(E) test) => _throw();
   @override
   void retainWhere(bool Function(E) test) => _throw();
   @override
-  void retainAll(Iterable<Object> elements) => _throw();
+  void retainAll(Iterable<Object?> elements) => _throw();
 }
diff --git a/lib/src/equality.dart b/lib/src/equality.dart
index b003a06..eba3841 100644
--- a/lib/src/equality.dart
+++ b/lib/src/equality.dart
@@ -27,7 +27,7 @@
   ///
   /// Some implementations may be restricted to only work on specific types
   /// of objects.
-  bool isValidKey(Object o);
+  bool isValidKey(Object? o);
 }
 
 /// Equality of objects based on derived values.
@@ -52,7 +52,7 @@
   final Equality<F> _inner;
 
   EqualityBy(F Function(E) comparisonKey,
-      [Equality<F> inner = const DefaultEquality()])
+      [Equality<F> inner = const DefaultEquality<Never>()])
       : _comparisonKey = comparisonKey,
         _inner = inner;
 
@@ -64,7 +64,7 @@
   int hash(E e) => _inner.hash(_comparisonKey(e));
 
   @override
-  bool isValidKey(Object o) {
+  bool isValidKey(Object? o) {
     if (o is E) {
       final value = _comparisonKey(o);
       return value is F && _inner.isValidKey(value);
@@ -84,11 +84,11 @@
 class DefaultEquality<E> implements Equality<E> {
   const DefaultEquality();
   @override
-  bool equals(Object e1, Object e2) => e1 == e2;
+  bool equals(Object? e1, Object? e2) => e1 == e2;
   @override
-  int hash(Object e) => e.hashCode;
+  int hash(Object? e) => e.hashCode;
   @override
-  bool isValidKey(Object o) => true;
+  bool isValidKey(Object? o) => true;
 }
 
 /// Equality of objects that compares only the identity of the objects.
@@ -99,7 +99,7 @@
   @override
   int hash(E e) => identityHashCode(e);
   @override
-  bool isValidKey(Object o) => true;
+  bool isValidKey(Object? o) => true;
 }
 
 /// Equality on iterables.
@@ -110,13 +110,13 @@
 /// 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;
+  final Equality<E?> _elementEquality;
   const IterableEquality(
-      [Equality<E> elementEquality = const DefaultEquality()])
+      [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : _elementEquality = elementEquality;
 
   @override
-  bool equals(Iterable<E> elements1, Iterable<E> elements2) {
+  bool equals(Iterable<E>? elements1, Iterable<E>? elements2) {
     if (identical(elements1, elements2)) return true;
     if (elements1 == null || elements2 == null) return false;
     var it1 = elements1.iterator;
@@ -130,7 +130,7 @@
   }
 
   @override
-  int hash(Iterable<E> elements) {
+  int hash(Iterable<E>? elements) {
     if (elements == null) return null.hashCode;
     // Jenkins's one-at-a-time hash function.
     var hash = 0;
@@ -147,7 +147,7 @@
   }
 
   @override
-  bool isValidKey(Object o) => o is Iterable<E>;
+  bool isValidKey(Object? o) => o is Iterable<E>;
 }
 
 /// Equality on lists.
@@ -163,11 +163,12 @@
 /// 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()])
+  const ListEquality(
+      [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : _elementEquality = elementEquality;
 
   @override
-  bool equals(List<E> list1, List<E> list2) {
+  bool equals(List<E>? list1, List<E>? list2) {
     if (identical(list1, list2)) return true;
     if (list1 == null || list2 == null) return false;
     var length = list1.length;
@@ -179,7 +180,7 @@
   }
 
   @override
-  int hash(List<E> list) {
+  int hash(List<E>? list) {
     if (list == null) return null.hashCode;
     // Jenkins's one-at-a-time hash function.
     // This code is almost identical to the one in IterableEquality, except
@@ -198,10 +199,10 @@
   }
 
   @override
-  bool isValidKey(Object o) => o is List<E>;
+  bool isValidKey(Object? o) => o is List<E>;
 }
 
-abstract class _UnorderedEquality<E, T extends Iterable<E>>
+abstract class _UnorderedEquality<E, T extends Iterable<E>?>
     implements Equality<T> {
   final Equality<E> _elementEquality;
 
@@ -250,13 +251,13 @@
 /// Two iterables are considered equal if they have the same number of elements,
 /// and the elements of one set can be paired with the elements
 /// of the other iterable, so that each pair are equal.
-class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
+class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>?> {
   const UnorderedIterableEquality(
-      [Equality<E> elementEquality = const DefaultEquality()])
+      [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : super(elementEquality);
 
   @override
-  bool isValidKey(Object o) => o is Iterable<E>;
+  bool isValidKey(Object? o) => o is Iterable<E>;
 }
 
 /// Equality of sets.
@@ -271,12 +272,13 @@
 /// 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()])
+class SetEquality<E> extends _UnorderedEquality<E, Set<E>?> {
+  const SetEquality(
+      [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : super(elementEquality);
 
   @override
-  bool isValidKey(Object o) => o is Set<E>;
+  bool isValidKey(Object? o) => o is Set<E>;
 }
 
 /// Internal class used by [MapEquality].
@@ -314,13 +316,13 @@
   final Equality<K> _keyEquality;
   final Equality<V> _valueEquality;
   const MapEquality(
-      {Equality<K> keys = const DefaultEquality(),
-      Equality<V> values = const DefaultEquality()})
+      {Equality<K> keys = const DefaultEquality<Never>(),
+      Equality<V> values = const DefaultEquality<Never>()})
       : _keyEquality = keys,
         _valueEquality = values;
 
   @override
-  bool equals(Map<K, V> map1, Map<K, V> map2) {
+  bool equals(Map<K, V>? map1, Map<K, V>? map2) {
     if (identical(map1, map2)) return true;
     if (map1 == null || map2 == null) return false;
     var length = map1.length;
@@ -341,12 +343,12 @@
   }
 
   @override
-  int hash(Map<K, V> map) {
+  int hash(Map<K, V>? map) {
     if (map == null) return null.hashCode;
     var hash = 0;
     for (var key in map.keys) {
       var keyHash = _keyEquality.hash(key);
-      var valueHash = _valueEquality.hash(map[key]);
+      var valueHash = _valueEquality.hash(map[key] as V);
       hash = (hash + 3 * keyHash + 7 * valueHash) & _HASH_MASK;
     }
     hash = (hash + (hash << 3)) & _HASH_MASK;
@@ -356,7 +358,7 @@
   }
 
   @override
-  bool isValidKey(Object o) => o is Map<K, V>;
+  bool isValidKey(Object? o) => o is Map<K, V>;
 }
 
 /// Combines several equalities into a single equality.
@@ -396,7 +398,7 @@
   }
 
   @override
-  bool isValidKey(Object o) {
+  bool isValidKey(Object? o) {
     for (var eq in _equalities) {
       if (eq.isValidKey(o)) return true;
     }
@@ -422,7 +424,7 @@
 class DeepCollectionEquality implements Equality {
   final Equality _base;
   final bool _unordered;
-  const DeepCollectionEquality([Equality base = const DefaultEquality()])
+  const DeepCollectionEquality([Equality base = const DefaultEquality<Never>()])
       : _base = base,
         _unordered = false;
 
@@ -430,7 +432,7 @@
   /// iterables are not considered important. That is, lists and iterables are
   /// treated as unordered iterables.
   const DeepCollectionEquality.unordered(
-      [Equality base = const DefaultEquality()])
+      [Equality base = const DefaultEquality<Never>()])
       : _base = base,
         _unordered = true;
 
@@ -457,7 +459,7 @@
   }
 
   @override
-  int hash(Object o) {
+  int hash(Object? o) {
     if (o is Set) return SetEquality(this).hash(o);
     if (o is Map) return MapEquality(keys: this, values: this).hash(o);
     if (!_unordered) {
@@ -470,7 +472,8 @@
   }
 
   @override
-  bool isValidKey(Object o) => o is Iterable || o is Map || _base.isValidKey(o);
+  bool isValidKey(Object? o) =>
+      o is Iterable || o is Map || _base.isValidKey(o);
 }
 
 /// String equality that's insensitive to differences in ASCII case.
@@ -487,5 +490,5 @@
   int hash(String string) => hashIgnoreAsciiCase(string);
 
   @override
-  bool isValidKey(Object object) => object is String;
+  bool isValidKey(Object? object) => object is String;
 }
diff --git a/lib/src/functions.dart b/lib/src/functions.dart
index 5fbef30..8f401d4 100644
--- a/lib/src/functions.dart
+++ b/lib/src/functions.dart
@@ -13,13 +13,13 @@
 /// [value] are used as the values for the new map.
 @Deprecated('Use Map.map or a for loop in a Map literal.')
 Map<K2, V2> mapMap<K1, V1, K2, V2>(Map<K1, V1> map,
-    {K2 Function(K1, V1) key, V2 Function(K1, V1) value}) {
-  key ??= (mapKey, _) => mapKey as K2;
-  value ??= (_, mapValue) => mapValue as V2;
+    {K2 Function(K1, V1)? key, V2 Function(K1, V1)? value}) {
+  var keyFn = key ?? (mapKey, _) => mapKey as K2;
+  var valueFn = value ?? (_, mapValue) => mapValue as V2;
 
   var result = <K2, V2>{};
   map.forEach((mapKey, mapValue) {
-    result[key(mapKey, mapValue)] = value(mapKey, mapValue);
+    result[keyFn(mapKey, mapValue)] = valueFn(mapKey, mapValue);
   });
   return result;
 }
@@ -30,13 +30,13 @@
 /// select the value that goes into the resulting map based on the two original
 /// values. If [value] is omitted, the value from [map2] is used.
 Map<K, V> mergeMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
-    {V Function(V, V) value}) {
+    {V Function(V, V)? value}) {
   var result = Map<K, V>.from(map1);
   if (value == null) return result..addAll(map2);
 
   map2.forEach((key, mapValue) {
     result[key] =
-        result.containsKey(key) ? value(result[key], mapValue) : mapValue;
+        result.containsKey(key) ? value(result[key] as V, mapValue) : mapValue;
   });
   return result;
 }
@@ -60,12 +60,14 @@
 /// The values returned by [orderBy] are compared using the [compare] function.
 /// If [compare] is omitted, values must implement [Comparable<T>] and they are
 /// compared using their [Comparable.compareTo].
-S minBy<S, T>(Iterable<S> values, T Function(S) orderBy,
-    {int Function(T, T) compare}) {
+///
+/// Returns `null` if [values] is empty.
+S? minBy<S, T>(Iterable<S> values, T Function(S) orderBy,
+    {int Function(T, T)? compare}) {
   compare ??= defaultCompare<T>();
 
-  S minValue;
-  T minOrderBy;
+  S? minValue;
+  T? minOrderBy;
   for (var element in values) {
     var elementOrderBy = orderBy(element);
     if (minOrderBy == null || compare(elementOrderBy, minOrderBy) < 0) {
@@ -82,15 +84,17 @@
 /// The values returned by [orderBy] are compared using the [compare] function.
 /// If [compare] is omitted, values must implement [Comparable<T>] and they are
 /// compared using their [Comparable.compareTo].
-S maxBy<S, T>(Iterable<S> values, T Function(S) orderBy,
-    {int Function(T, T) compare}) {
+///
+/// Returns `null` if [values] is empty.
+S? maxBy<S, T>(Iterable<S> values, T Function(S?) orderBy,
+    {int? Function(T, T)? compare}) {
   compare ??= defaultCompare<T>();
 
-  S maxValue;
-  T maxOrderBy;
+  S? maxValue;
+  T? maxOrderBy;
   for (var element in values) {
     var elementOrderBy = orderBy(element);
-    if (maxOrderBy == null || compare(elementOrderBy, maxOrderBy) > 0) {
+    if (maxOrderBy == null || compare(elementOrderBy, maxOrderBy)! > 0) {
       maxValue = element;
       maxOrderBy = elementOrderBy;
     }
@@ -125,9 +129,9 @@
   for (var vertex1 in keys) {
     for (var vertex2 in keys) {
       for (var vertex3 in keys) {
-        if (result[vertex2].contains(vertex1) &&
-            result[vertex1].contains(vertex3)) {
-          result[vertex2].add(vertex3);
+        if (result[vertex2]!.contains(vertex1) &&
+            result[vertex1]!.contains(vertex3)) {
+          result[vertex2]!.add(vertex3);
         }
       }
     }
@@ -153,7 +157,7 @@
   //
   // [Tarjan's algorithm]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
   var index = 0;
-  var stack = <T>[];
+  var stack = <T?>[];
   var result = <Set<T>>[];
 
   // The order of these doesn't matter, so we use un-linked implementations to
@@ -170,22 +174,22 @@
     stack.add(vertex);
     onStack.add(vertex);
 
-    for (var successor in graph[vertex]) {
+    for (var successor in graph[vertex]!) {
       if (!indices.containsKey(successor)) {
         strongConnect(successor);
-        lowLinks[vertex] = math.min(lowLinks[vertex], lowLinks[successor]);
+        lowLinks[vertex] = math.min(lowLinks[vertex]!, lowLinks[successor]!);
       } else if (onStack.contains(successor)) {
-        lowLinks[vertex] = math.min(lowLinks[vertex], lowLinks[successor]);
+        lowLinks[vertex] = math.min(lowLinks[vertex]!, lowLinks[successor]!);
       }
     }
 
     if (lowLinks[vertex] == indices[vertex]) {
       var component = <T>{};
-      T neighbor;
+      T? neighbor;
       do {
         neighbor = stack.removeLast();
         onStack.remove(neighbor);
-        component.add(neighbor);
+        component.add(neighbor as T);
       } while (neighbor != vertex);
       result.add(component);
     }
diff --git a/lib/src/iterable_zip.dart b/lib/src/iterable_zip.dart
index db38253..940b5e7 100644
--- a/lib/src/iterable_zip.dart
+++ b/lib/src/iterable_zip.dart
@@ -30,7 +30,7 @@
 
 class _IteratorZip<T> implements Iterator<List<T>> {
   final List<Iterator<T>> _iterators;
-  List<T> _current;
+  List<T>? _current;
 
   _IteratorZip(List<Iterator<T>> iterators) : _iterators = iterators;
 
@@ -43,13 +43,11 @@
         return false;
       }
     }
-    _current = List(_iterators.length);
-    for (var i = 0; i < _iterators.length; i++) {
-      _current[i] = _iterators[i].current;
-    }
+    _current = List.generate(_iterators.length, (i) => _iterators[i].current,
+        growable: false);
     return true;
   }
 
   @override
-  List<T> get current => _current;
+  List<T> get current => _current!;
 }
diff --git a/lib/src/priority_queue.dart b/lib/src/priority_queue.dart
index 31ff828..d4355b0 100644
--- a/lib/src/priority_queue.dart
+++ b/lib/src/priority_queue.dart
@@ -21,7 +21,8 @@
   /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
   /// is the case, `E` must implement [Comparable], and this is checked at
   /// runtime for every comparison.
-  factory PriorityQueue([int Function(E, E) comparison]) = HeapPriorityQueue<E>;
+  factory PriorityQueue([int Function(E, E)? comparison]) =
+      HeapPriorityQueue<E>;
 
   /// Number of elements in the queue.
   int get length;
@@ -128,7 +129,7 @@
   final Comparator<E> comparison;
 
   /// List implementation of a heap.
-  List<E> _queue = List<E>(_INITIAL_CAPACITY);
+  List<E?> _queue = List<E?>.filled(_INITIAL_CAPACITY, null);
 
   /// Number of elements in queue.
   ///
@@ -144,7 +145,7 @@
   /// If [comparison] is omitted, it defaults to [Comparable.compare]. If this
   /// is the case, `E` must implement [Comparable], and this is checked at
   /// runtime for every comparison.
-  HeapPriorityQueue([int Function(E, E) comparison])
+  HeapPriorityQueue([int Function(E, E)? comparison])
       : comparison = comparison ?? defaultCompare<E>();
 
   @override
@@ -173,7 +174,7 @@
   @override
   E get first {
     if (_length == 0) throw StateError('No such element');
-    return _queue[0];
+    return _queue[0]!;
   }
 
   @override
@@ -207,13 +208,13 @@
     var length = _length;
     _queue = const [];
     _length = 0;
-    return result.take(length);
+    return result.take(length).cast();
   }
 
   @override
   E removeFirst() {
     if (_length == 0) throw StateError('No such element');
-    var result = _queue[0];
+    var result = _queue[0]!;
     var last = _removeLast();
     if (_length > 0) {
       _bubbleDown(last, 0);
@@ -222,19 +223,14 @@
   }
 
   @override
-  List<E> toList() {
-    var list = <E>[]
-      ..length = _length
-      ..setRange(0, _length, _queue)
-      ..sort(comparison);
-    return list;
-  }
+  List<E> toList() =>
+      List<E>.generate(_length, (i) => _queue[i]!)..sort(comparison);
 
   @override
   Set<E> toSet() {
-    Set<E> set = SplayTreeSet<E>(comparison);
+    var set = SplayTreeSet<E>(comparison);
     for (var i = 0; i < _length; i++) {
-      set.add(_queue[i]);
+      set.add(_queue[i]!);
     }
     return set;
   }
@@ -271,7 +267,7 @@
     // in the heap will also have lower priority.
     do {
       var index = position - 1;
-      var element = _queue[index];
+      var element = _queue[index]!;
       var comp = comparison(element, object);
       if (comp == 0) return index;
       if (comp < 0) {
@@ -298,7 +294,7 @@
 
   E _removeLast() {
     var newLength = _length - 1;
-    var last = _queue[newLength];
+    var last = _queue[newLength]!;
     _queue[newLength] = null;
     _length = newLength;
     return last;
@@ -312,7 +308,7 @@
   void _bubbleUp(E element, int index) {
     while (index > 0) {
       var parentIndex = (index - 1) ~/ 2;
-      var parent = _queue[parentIndex];
+      var parent = _queue[parentIndex]!;
       if (comparison(element, parent) > 0) break;
       _queue[index] = parent;
       index = parentIndex;
@@ -329,10 +325,10 @@
     var rightChildIndex = index * 2 + 2;
     while (rightChildIndex < _length) {
       var leftChildIndex = rightChildIndex - 1;
-      var leftChild = _queue[leftChildIndex];
-      var rightChild = _queue[rightChildIndex];
+      var leftChild = _queue[leftChildIndex]!;
+      var rightChild = _queue[rightChildIndex]!;
       var comp = comparison(leftChild, rightChild);
-      var minChildIndex;
+      int minChildIndex;
       E minChild;
       if (comp < 0) {
         minChild = leftChild;
@@ -352,7 +348,7 @@
     }
     var leftChildIndex = rightChildIndex - 1;
     if (leftChildIndex < _length) {
-      var child = _queue[leftChildIndex];
+      var child = _queue[leftChildIndex]!;
       var comp = comparison(element, child);
       if (comp > 0) {
         _queue[index] = child;
@@ -368,7 +364,7 @@
   void _grow() {
     var newCapacity = _queue.length * 2 + 1;
     if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY;
-    var newQueue = List<E>(newCapacity);
+    var newQueue = List<E?>.filled(newCapacity, null);
     newQueue.setRange(0, _length, _queue);
     _queue = newQueue;
   }
diff --git a/lib/src/queue_list.dart b/lib/src/queue_list.dart
index eb80cbe..2bda02b 100644
--- a/lib/src/queue_list.dart
+++ b/lib/src/queue_list.dart
@@ -26,7 +26,7 @@
   }
 
   static const int _INITIAL_CAPACITY = 8;
-  List<E> _table;
+  late List<E?> _table;
   int _head;
   int _tail;
 
@@ -34,7 +34,7 @@
   ///
   /// If [initialCapacity] is given, prepare the queue for at least that many
   /// elements.
-  QueueList([int initialCapacity])
+  QueueList([int? initialCapacity])
       : _head = 0,
         _tail = 0 {
     if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
@@ -43,11 +43,11 @@
       initialCapacity = _nextPowerOf2(initialCapacity);
     }
     assert(_isPowerOf2(initialCapacity));
-    _table = List<E>(initialCapacity);
+    _table = List<E?>.filled(initialCapacity, null);
   }
 
-  // An internal constructor for use by _CastQueueList.
-  QueueList._();
+  /// An internal constructor for use by [_CastQueueList].
+  QueueList._(this._head, this._tail, this._table);
 
   /// Create a queue initially containing the elements of [source].
   factory QueueList.from(Iterable<E> source) {
@@ -127,7 +127,7 @@
   @override
   E removeFirst() {
     if (_head == _tail) throw StateError('No element');
-    var result = _table[_head];
+    var result = _table[_head] as E;
     _table[_head] = null;
     _head = (_head + 1) & (_table.length - 1);
     return result;
@@ -137,7 +137,7 @@
   E removeLast() {
     if (_head == _tail) throw StateError('No element');
     _tail = (_tail - 1) & (_table.length - 1);
-    var result = _table[_tail];
+    var result = _table[_tail]!;
     _table[_tail] = null;
     return result;
   }
@@ -150,6 +150,15 @@
   @override
   set length(int value) {
     if (value < 0) throw RangeError('Length $value may not be negative.');
+    if (value > length) {
+      try {
+        null as E;
+      } on TypeError {
+        throw UnsupportedError(
+            'The length can only be increased when the element type is '
+            'nullable, but the current element type is `$E`.');
+      }
+    }
 
     var delta = value - length;
     if (delta >= 0) {
@@ -177,7 +186,7 @@
       throw RangeError('Index $index must be in the range [0..$length).');
     }
 
-    return _table[(_head + index) & (_table.length - 1)];
+    return _table[(_head + index) & (_table.length - 1)] as E;
   }
 
   @override
@@ -220,7 +229,7 @@
 
   /// Grow the table when full.
   void _grow() {
-    var newTable = List<E>(_table.length * 2);
+    var newTable = List<E?>.filled(_table.length * 2, null);
     var split = _table.length - _head;
     newTable.setRange(0, split, _table, _head);
     newTable.setRange(split, split + _head, _table, 0);
@@ -229,7 +238,7 @@
     _table = newTable;
   }
 
-  int _writeToList(List<E> target) {
+  int _writeToList(List<E?> target) {
     assert(target.length >= length);
     if (_head <= _tail) {
       var length = _tail - _head;
@@ -251,7 +260,7 @@
     // expansion.
     newElementCount += newElementCount >> 1;
     var newCapacity = _nextPowerOf2(newElementCount);
-    var newTable = List<E>(newCapacity);
+    var newTable = List<E?>.filled(newCapacity, null);
     _tail = _writeToList(newTable);
     _table = newTable;
     _head = 0;
@@ -261,9 +270,9 @@
 class _CastQueueList<S, T> extends QueueList<T> {
   final QueueList<S> _delegate;
 
-  _CastQueueList(this._delegate) : super._() {
-    _table = _delegate._table.cast<T>();
-  }
+  // Assigns invalid values for head/tail because it uses the delegate to hold
+  // the real values, but they are non-null fields.
+  _CastQueueList(this._delegate) : super._(-1, -1, _delegate._table.cast<T>());
 
   @override
   int get _head => _delegate._head;
diff --git a/lib/src/union_set.dart b/lib/src/union_set.dart
index 1b76645..208a2a5 100644
--- a/lib/src/union_set.dart
+++ b/lib/src/union_set.dart
@@ -62,15 +62,15 @@
   }
 
   @override
-  bool contains(Object element) => _sets.any((set) => set.contains(element));
+  bool contains(Object? element) => _sets.any((set) => set.contains(element));
 
   @override
-  E lookup(Object element) {
+  E? lookup(Object? element) {
     if (element == null) return null;
-
-    return _sets
-        .map((set) => set.lookup(element))
-        .firstWhere((result) => result != null, orElse: () => null);
+    for (var set in _sets) {
+      var result = set.lookup(element);
+      if (result != null) return result;
+    }
   }
 
   @override
diff --git a/lib/src/union_set_controller.dart b/lib/src/union_set_controller.dart
index 6c04cef..ef9bb8d 100644
--- a/lib/src/union_set_controller.dart
+++ b/lib/src/union_set_controller.dart
@@ -23,8 +23,7 @@
 /// ```
 class UnionSetController<E> {
   /// The [UnionSet] that provides a view of the union of sets in [this].
-  UnionSet<E> get set => _set;
-  UnionSet<E> _set;
+  late final UnionSet<E> set;
 
   /// The sets whose union is exposed through [set].
   final _sets = <Set<E>>{};
@@ -35,7 +34,7 @@
   /// disjoint—that is, that they contain no elements in common. This makes
   /// many operations including [length] more efficient.
   UnionSetController({bool disjoint = false}) {
-    _set = UnionSet<E>(_sets, disjoint: disjoint);
+    set = UnionSet<E>(_sets, disjoint: disjoint);
   }
 
   /// Adds the contents of [component] to [set].
diff --git a/lib/src/unmodifiable_wrappers.dart b/lib/src/unmodifiable_wrappers.dart
index 7a7898e..96551e8 100644
--- a/lib/src/unmodifiable_wrappers.dart
+++ b/lib/src/unmodifiable_wrappers.dart
@@ -58,7 +58,7 @@
   /// Throws an [UnsupportedError];
   /// operations that change the length of the list are disallowed.
   @override
-  bool remove(Object value) => _throw();
+  bool remove(Object? value) => _throw();
 
   /// Throws an [UnsupportedError];
   /// operations that change the length of the list are disallowed.
@@ -134,7 +134,7 @@
   /// Throws an [UnsupportedError];
   /// operations that change the set are disallowed.
   @override
-  bool remove(Object value) => _throw();
+  bool remove(Object? value) => _throw();
 
   /// Throws an [UnsupportedError];
   /// operations that change the set are disallowed.
@@ -187,7 +187,7 @@
   /// Throws an [UnsupportedError];
   /// operations that change the map are disallowed.
   @override
-  V remove(Object key) => _throw();
+  V remove(Object? key) => _throw();
 
   /// Throws an [UnsupportedError];
   /// operations that change the map are disallowed.
diff --git a/lib/src/wrappers.dart b/lib/src/wrappers.dart
index 7734073..14e2d8c 100644
--- a/lib/src/wrappers.dart
+++ b/lib/src/wrappers.dart
@@ -23,7 +23,7 @@
   Iterable<T> cast<T>() => _base.cast<T>();
 
   @override
-  bool contains(Object element) => _base.contains(element);
+  bool contains(Object? element) => _base.contains(element);
 
   @override
   E elementAt(int index) => _base.elementAt(index);
@@ -38,7 +38,7 @@
   E get first => _base.first;
 
   @override
-  E firstWhere(bool Function(E) test, {E Function() orElse}) =>
+  E firstWhere(bool Function(E) test, {E Function()? orElse}) =>
       _base.firstWhere(test, orElse: orElse);
 
   @override
@@ -67,7 +67,7 @@
   E get last => _base.last;
 
   @override
-  E lastWhere(bool Function(E) test, {E Function() orElse}) =>
+  E lastWhere(bool Function(E) test, {E Function()? orElse}) =>
       _base.lastWhere(test, orElse: orElse);
 
   @override
@@ -86,7 +86,7 @@
   E get single => _base.single;
 
   @override
-  E singleWhere(bool Function(E) test, {E Function() orElse}) {
+  E singleWhere(bool Function(E) test, {E Function()? orElse}) {
     return _base.singleWhere(test, orElse: orElse);
   }
 
@@ -165,7 +165,7 @@
   @Deprecated('Use list.cast<E> instead.')
   static List<E> typed<E>(List base) => base.cast<E>();
 
-  List<E> get _listBase => _base;
+  List<E> get _listBase => _base as List<E>;
 
   @override
   E operator [](int index) => _listBase[index];
@@ -200,7 +200,7 @@
   }
 
   @override
-  void fillRange(int start, int end, [E fillValue]) {
+  void fillRange(int start, int end, [E? fillValue]) {
     _listBase.fillRange(start, end, fillValue);
   }
 
@@ -237,11 +237,11 @@
   }
 
   @override
-  int lastIndexOf(E element, [int start]) =>
+  int lastIndexOf(E element, [int? start]) =>
       _listBase.lastIndexOf(element, start);
 
   @override
-  int lastIndexWhere(bool Function(E) test, [int start]) =>
+  int lastIndexWhere(bool Function(E) test, [int? start]) =>
       _listBase.lastIndexWhere(test, start);
 
   @override
@@ -250,7 +250,7 @@
   }
 
   @override
-  bool remove(Object value) => _listBase.remove(value);
+  bool remove(Object? value) => _listBase.remove(value);
 
   @override
   E removeAt(int index) => _listBase.removeAt(index);
@@ -296,17 +296,17 @@
   }
 
   @override
-  void shuffle([math.Random random]) {
+  void shuffle([math.Random? random]) {
     _listBase.shuffle(random);
   }
 
   @override
-  void sort([int Function(E, E) compare]) {
+  void sort([int Function(E, E)? compare]) {
     _listBase.sort(compare);
   }
 
   @override
-  List<E> sublist(int start, [int end]) => _listBase.sublist(start, end);
+  List<E> sublist(int start, [int? end]) => _listBase.sublist(start, end);
 }
 
 /// A [Set] that delegates all operations to a base set.
@@ -330,7 +330,7 @@
   @Deprecated('Use set.cast<E> instead.')
   static Set<E> typed<E>(Set base) => base.cast<E>();
 
-  Set<E> get _setBase => _base;
+  Set<E> get _setBase => _base as Set<E>;
 
   @override
   bool add(E value) => _setBase.add(value);
@@ -349,22 +349,22 @@
   }
 
   @override
-  bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
+  bool containsAll(Iterable<Object?> other) => _setBase.containsAll(other);
 
   @override
-  Set<E> difference(Set<Object> other) => _setBase.difference(other);
+  Set<E> difference(Set<Object?> other) => _setBase.difference(other);
 
   @override
-  Set<E> intersection(Set<Object> other) => _setBase.intersection(other);
+  Set<E> intersection(Set<Object?> other) => _setBase.intersection(other);
 
   @override
-  E lookup(Object element) => _setBase.lookup(element);
+  E? lookup(Object? element) => _setBase.lookup(element);
 
   @override
-  bool remove(Object value) => _setBase.remove(value);
+  bool remove(Object? value) => _setBase.remove(value);
 
   @override
-  void removeAll(Iterable<Object> elements) {
+  void removeAll(Iterable<Object?> elements) {
     _setBase.removeAll(elements);
   }
 
@@ -374,7 +374,7 @@
   }
 
   @override
-  void retainAll(Iterable<Object> elements) {
+  void retainAll(Iterable<Object?> elements) {
     _setBase.retainAll(elements);
   }
 
@@ -416,7 +416,7 @@
   @Deprecated('Use queue.cast<E> instead.')
   static Queue<E> typed<E>(Queue base) => base.cast<E>();
 
-  Queue<E> get _baseQueue => _base;
+  Queue<E> get _baseQueue => _base as Queue<E>;
 
   @override
   void add(E value) {
@@ -447,7 +447,7 @@
   }
 
   @override
-  bool remove(Object object) => _baseQueue.remove(object);
+  bool remove(Object? object) => _baseQueue.remove(object);
 
   @override
   void removeWhere(bool Function(E) test) {
@@ -495,7 +495,7 @@
   static Map<K, V> typed<K, V>(Map base) => base.cast<K, V>();
 
   @override
-  V operator [](Object key) => _base[key];
+  V? operator [](Object? key) => _base[key];
 
   @override
   void operator []=(K key, V value) {
@@ -521,10 +521,10 @@
   Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
 
   @override
-  bool containsKey(Object key) => _base.containsKey(key);
+  bool containsKey(Object? key) => _base.containsKey(key);
 
   @override
-  bool containsValue(Object value) => _base.containsValue(value);
+  bool containsValue(Object? value) => _base.containsValue(value);
 
   @override
   Iterable<MapEntry<K, V>> get entries => _base.entries;
@@ -555,7 +555,7 @@
       _base.putIfAbsent(key, ifAbsent);
 
   @override
-  V remove(Object key) => _base.remove(key);
+  V? remove(Object? key) => _base.remove(key);
 
   @override
   void removeWhere(bool Function(K, V) test) => _base.removeWhere(test);
@@ -570,7 +570,7 @@
   String toString() => _base.toString();
 
   @override
-  V update(K key, V Function(V) update, {V Function() ifAbsent}) =>
+  V update(K key, V Function(V) update, {V Function()? ifAbsent}) =>
       _base.update(key, update, ifAbsent: ifAbsent);
 
   @override
@@ -590,7 +590,7 @@
     with UnmodifiableSetMixin<E> {
   final Map<E, dynamic> _baseMap;
 
-  MapKeySet(Map<E, dynamic> base) : _baseMap = base;
+  MapKeySet(this._baseMap);
 
   @override
   Iterable<E> get _base => _baseMap.keys;
@@ -604,7 +604,7 @@
   }
 
   @override
-  bool contains(Object element) => _baseMap.containsKey(element);
+  bool contains(Object? element) => _baseMap.containsKey(element);
 
   @override
   bool get isEmpty => _baseMap.isEmpty;
@@ -619,7 +619,7 @@
   String toString() => "{${_base.join(', ')}}";
 
   @override
-  bool containsAll(Iterable<Object> other) => other.every(contains);
+  bool containsAll(Iterable<Object?> other) => other.every(contains);
 
   /// Returns a new set with the the elements of [this] that are not in [other].
   ///
@@ -629,7 +629,7 @@
   /// Note that the returned set will use the default equality operation, which
   /// may be different than the equality operation [this] uses.
   @override
-  Set<E> difference(Set<Object> other) =>
+  Set<E> difference(Set<Object?> other) =>
       where((element) => !other.contains(element)).toSet();
 
   /// Returns a new set which is the intersection between [this] and [other].
@@ -640,12 +640,12 @@
   /// Note that the returned set will use the default equality operation, which
   /// may be different than the equality operation [this] uses.
   @override
-  Set<E> intersection(Set<Object> other) => where(other.contains).toSet();
+  Set<E> intersection(Set<Object?> other) => where(other.contains).toSet();
 
   /// Throws an [UnsupportedError] since there's no corresponding method for
   /// [Map]s.
   @override
-  E lookup(Object element) =>
+  E lookup(Object? element) =>
       throw UnsupportedError("MapKeySet doesn't support lookup().");
 
   @deprecated
@@ -688,14 +688,12 @@
   final Map<K, V> _baseMap;
   final K Function(V) _keyForValue;
 
-  /// Creates a new [MapValueSet] based on [base].
+  /// Creates a new [MapValueSet] based on [_baseMap].
   ///
-  /// [keyForValue] returns the key in the map that should be associated with
+  /// [_keyForValue] returns the key in the map that should be associated with
   /// the given value. The set's notion of equality is identical to the equality
-  /// of the return values of [keyForValue].
-  MapValueSet(Map<K, V> base, K Function(V) keyForValue)
-      : _baseMap = base,
-        _keyForValue = keyForValue;
+  /// of the return values of [_keyForValue].
+  MapValueSet(this._baseMap, this._keyForValue);
 
   @override
   Iterable<V> get _base => _baseMap.values;
@@ -709,9 +707,9 @@
   }
 
   @override
-  bool contains(Object element) {
-    if (element != null && element is! V) return false;
-    var key = _keyForValue(element as V);
+  bool contains(Object? element) {
+    if (element is! V) return false;
+    var key = _keyForValue(element);
 
     return _baseMap.containsKey(key);
   }
@@ -746,7 +744,7 @@
   void clear() => _baseMap.clear();
 
   @override
-  bool containsAll(Iterable<Object> other) => other.every(contains);
+  bool containsAll(Iterable<Object?> other) => other.every(contains);
 
   /// Returns a new set with the the elements of [this] that are not in [other].
   ///
@@ -756,7 +754,7 @@
   /// Note that the returned set will use the default equality operation, which
   /// may be different than the equality operation [this] uses.
   @override
-  Set<V> difference(Set<Object> other) =>
+  Set<V> difference(Set<Object?> other) =>
       where((element) => !other.contains(element)).toSet();
 
   /// Returns a new set which is the intersection between [this] and [other].
@@ -767,20 +765,20 @@
   /// Note that the returned set will use the default equality operation, which
   /// may be different than the equality operation [this] uses.
   @override
-  Set<V> intersection(Set<Object> other) => where(other.contains).toSet();
+  Set<V> intersection(Set<Object?> other) => where(other.contains).toSet();
 
   @override
-  V lookup(Object element) {
-    if (element != null && element is! V) return null;
-    var key = _keyForValue(element as V);
+  V? lookup(Object? element) {
+    if (element is! V) return null;
+    var key = _keyForValue(element);
 
     return _baseMap[key];
   }
 
   @override
-  bool remove(Object element) {
-    if (element != null && element is! V) return false;
-    var key = _keyForValue(element as V);
+  bool remove(Object? element) {
+    if (element is! V) return false;
+    var key = _keyForValue(element);
 
     if (!_baseMap.containsKey(key)) return false;
     _baseMap.remove(key);
@@ -788,7 +786,7 @@
   }
 
   @override
-  void removeAll(Iterable<Object> elements) => elements.forEach(remove);
+  void removeAll(Iterable<Object?> elements) => elements.forEach(remove);
 
   @override
   void removeWhere(bool Function(V) test) {
@@ -800,14 +798,14 @@
   }
 
   @override
-  void retainAll(Iterable<Object> elements) {
+  void retainAll(Iterable<Object?> elements) {
     var valuesToRetain = Set<V>.identity();
     for (var element in elements) {
-      if (element != null && element is! V) continue;
-      var key = _keyForValue(element as V);
+      if (element is! V) continue;
+      var key = _keyForValue(element);
 
       if (!_baseMap.containsKey(key)) continue;
-      valuesToRetain.add(_baseMap[key]);
+      valuesToRetain.add(_baseMap[key]!);
     }
 
     var keysToRemove = [];
diff --git a/pubspec.yaml b/pubspec.yaml
index 8949dd9..638740b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,12 +1,94 @@
 name: collection
-version: 1.14.13
+version: 1.15.0-nnbd
 
 description: Collections and utilities functions and classes related to collections.
 homepage: https://www.github.com/dart-lang/collection
 
 environment:
-  sdk: '>=2.3.0 <3.0.0'
+  sdk: '>=2.9.0-18.0 <2.9.0'
 
 dev_dependencies:
   pedantic: ^1.0.0
   test: ^1.0.0
+
+dependency_overrides:
+  async:
+    git:
+      url: git://github.com/dart-lang/async.git
+      ref: null_safety
+  boolean_selector:
+    git:
+      url: git://github.com/dart-lang/boolean_selector.git
+      ref: null_safety
+  charcode:
+    git:
+      url: git://github.com/dart-lang/charcode.git
+      ref: null_safety
+  js:
+    git:
+      url: git://github.com/dart-lang/sdk.git
+      path: pkg/js
+  matcher:
+    git:
+      url: git://github.com/dart-lang/matcher.git
+      ref: null_safety
+  meta:
+    git:
+      url: git://github.com/dart-lang/sdk.git
+      ref: null_safety-pkgs
+      path: pkg/meta
+  path:
+    git:
+      url: git://github.com/dart-lang/path.git
+      ref: null_safety
+  pedantic:
+    git:
+      url: git://github.com/dart-lang/pedantic.git
+      ref: null_safety
+  pool:
+    git:
+      url: git://github.com/dart-lang/pool.git
+      ref: null_safety
+  source_maps:
+    git:
+      url: git://github.com/dart-lang/source_maps.git
+      ref: null_safety
+  source_map_stack_trace:
+    git:
+      url: git://github.com/dart-lang/source_map_stack_trace.git
+      ref: null_safety
+  source_span:
+    git:
+      url: git://github.com/dart-lang/source_span.git
+      ref: null_safety
+  stack_trace:
+    git:
+      url: git://github.com/dart-lang/stack_trace.git
+      ref: null_safety
+  stream_channel:
+    git:
+      url: git://github.com/dart-lang/stream_channel.git
+      ref: null_safety
+  string_scanner:
+    git:
+      url: git://github.com/dart-lang/string_scanner.git
+      ref: null_safety
+  term_glyph:
+    git:
+      url: git://github.com/dart-lang/term_glyph.git
+      ref: null_safety
+  test_api:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test_api
+  test_core:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test_core
+  test:
+    git:
+      url: git://github.com/dart-lang/test.git
+      ref: null_safety
+      path: pkgs/test
diff --git a/test/algorithms_test.dart b/test/algorithms_test.dart
index 0d0d414..7ab096f 100644
--- a/test/algorithms_test.dart
+++ b/test/algorithms_test.dart
@@ -157,10 +157,10 @@
   test('insertionSortRandom', () {
     var random = Random();
     for (var i = 0; i < 25; i++) {
-      var list = List(i);
-      for (var j = 0; j < i; j++) {
-        list[j] = random.nextInt(25); // Expect some equal elements.
-      }
+      var list = [
+        for (var j = 0; j < i; j++)
+          random.nextInt(25) // Expect some equal elements.
+      ];
       insertionSort(list);
       for (var j = 1; j < i; j++) {
         expect(list[j - 1], lessThanOrEqualTo(list[j]));
@@ -195,10 +195,10 @@
   test('MergeSortRandom', () {
     var random = Random();
     for (var i = 0; i < 250; i += 1) {
-      var list = List(i);
-      for (var j = 0; j < i; j++) {
-        list[j] = random.nextInt(i); // Expect some equal elements.
-      }
+      var list = [
+        for (var j = 0; j < i; j++)
+          random.nextInt(i) // Expect some equal elements.
+      ];
       mergeSort(list);
       for (var j = 1; j < i; j++) {
         expect(list[j - 1], lessThanOrEqualTo(list[j]));
@@ -212,14 +212,13 @@
     // larger case where the internal moving insertion sort is used
     // larger cases with multiple splittings, numbers just around a power of 2.
     for (var size in [8, 50, 511, 512, 513]) {
-      var list = List<OC>(size);
       // Class OC compares using id.
       // With size elements with id's in the range 0..size/4, a number of
       // collisions are guaranteed. These should be sorted so that the 'order'
       // part of the objects are still in order.
-      for (var i = 0; i < size; i++) {
-        list[i] = OC(random.nextInt(size >> 2), i);
-      }
+      var list = [
+        for (var i = 0; i < size; i++) OC(random.nextInt(size >> 2), i)
+      ];
       mergeSort(list);
       var prev = list[0];
       for (var i = 1; i < size; i++) {
@@ -255,10 +254,7 @@
   test('MergeSortSpecialCases', () {
     for (var size in [511, 512, 513]) {
       // All equal.
-      var list = List(size);
-      for (var i = 0; i < size; i++) {
-        list[i] = OC(0, i);
-      }
+      var list = [for (var i = 0; i < size; i++) OC(0, i)];
       mergeSort(list);
       for (var i = 0; i < size; i++) {
         expect(list[i].order, equals(i));
diff --git a/test/canonicalized_map_test.dart b/test/canonicalized_map_test.dart
index 2d42b09..e5bd34e 100644
--- a/test/canonicalized_map_test.dart
+++ b/test/canonicalized_map_test.dart
@@ -7,11 +7,10 @@
 
 void main() {
   group('with an empty canonicalized map', () {
-    CanonicalizedMap<int, String, String> map;
+    late CanonicalizedMap<int, String, String> map;
 
     setUp(() {
-      map = CanonicalizedMap(int.parse,
-          isValidKey: (s) => RegExp(r'^\d+$').hasMatch(s as String));
+      map = CanonicalizedMap(int.parse, isValidKey: RegExp(r'^\d+$').hasMatch);
     });
 
     test('canonicalizes keys on set and get', () {
@@ -164,7 +163,7 @@
     var map;
     setUp(() {
       map = CanonicalizedMap<int, String, dynamic>(int.parse,
-          isValidKey: (s) => RegExp(r'^\d+$').hasMatch(s as String));
+          isValidKey: RegExp(r'^\d+$').hasMatch);
     });
 
     test('for an empty map', () {
diff --git a/test/comparators_test.dart b/test/comparators_test.dart
index b8dcb3e..ab48711 100644
--- a/test/comparators_test.dart
+++ b/test/comparators_test.dart
@@ -57,7 +57,7 @@
     '~'
   ];
 
-  List<String> sortedBy(int Function(String, String) compare) =>
+  List<String> sortedBy(int Function(String, String)? compare) =>
       strings.toList()
         ..shuffle()
         ..sort(compare);
@@ -65,6 +65,7 @@
   test('String.compareTo', () {
     expect(sortedBy(null), strings);
   });
+
   test('compareAsciiLowerCase', () {
     expect(sortedBy(compareAsciiLowerCase), sortedBy((a, b) {
       var delta = a.toLowerCase().compareTo(b.toLowerCase());
@@ -73,6 +74,7 @@
       return a.compareTo(b);
     }));
   });
+
   test('compareAsciiUpperCase', () {
     expect(sortedBy(compareAsciiUpperCase), sortedBy((a, b) {
       var delta = a.toUpperCase().compareTo(b.toUpperCase());
@@ -88,7 +90,7 @@
   // compared to non-digits.
   String replaceNumbers(String string) =>
       string.replaceAllMapped(RegExp(r'\d+'), (m) {
-        var digits = m[0];
+        var digits = m[0]!;
         return String.fromCharCodes([0x30, int.parse(digits), digits.length]);
       });
 
diff --git a/test/functions_test.dart b/test/functions_test.dart
index a659c94..e37b978 100644
--- a/test/functions_test.dart
+++ b/test/functions_test.dart
@@ -29,14 +29,18 @@
     });
 
     test("maps the map's keys", () {
-      // ignore: deprecated_member_use_from_same_package
-      expect(mapMap({'foo': 1, 'bar': 2}, key: (key, value) => key[value]),
+      expect(
+          // ignore: deprecated_member_use_from_same_package
+          mapMap({'foo': 1, 'bar': 2},
+              key: (dynamic key, dynamic value) => key[value]),
           equals({'o': 1, 'r': 2}));
     });
 
     test("maps the map's values", () {
-      // ignore: deprecated_member_use_from_same_package
-      expect(mapMap({'foo': 1, 'bar': 2}, value: (key, value) => key[value]),
+      expect(
+          // ignore: deprecated_member_use_from_same_package
+          mapMap({'foo': 1, 'bar': 2},
+              value: (dynamic key, dynamic value) => key[value]),
           equals({'foo': 'o', 'bar': 'r'}));
     });
 
@@ -44,15 +48,17 @@
       expect(
           // ignore: deprecated_member_use_from_same_package
           mapMap({'foo': 1, 'bar': 2},
-              key: (key, value) => '$key$value',
-              value: (key, value) => key[value]),
+              key: (dynamic key, dynamic value) => '$key$value',
+              value: (dynamic key, dynamic value) => key[value]),
           equals({'foo1': 'o', 'bar2': 'r'}));
     });
   });
 
   group('mergeMaps()', () {
     test('with empty maps returns an empty map', () {
-      expect(mergeMaps({}, {}, value: expectAsync2((_, __) {}, count: 0)),
+      expect(
+          mergeMaps({}, {},
+              value: expectAsync2((dynamic _, dynamic __) {}, count: 0)),
           isEmpty);
     });
 
@@ -69,19 +75,20 @@
     test('uses the callback to merge values', () {
       expect(
           mergeMaps({'foo': 1, 'bar': 2}, {'bar': 3, 'baz': 4},
-              value: (value1, value2) => value1 + value2),
+              value: (dynamic value1, dynamic value2) => value1 + value2),
           equals({'foo': 1, 'bar': 5, 'baz': 4}));
     });
   });
 
   group('groupBy()', () {
     test('returns an empty map for an empty iterable', () {
-      expect(groupBy([], expectAsync1((_) {}, count: 0)), isEmpty);
+      expect(groupBy([], expectAsync1((dynamic _) {}, count: 0)), isEmpty);
     });
 
     test("groups elements by the function's return value", () {
       expect(
-          groupBy(['foo', 'bar', 'baz', 'bop', 'qux'], (string) => string[1]),
+          groupBy(['foo', 'bar', 'baz', 'bop', 'qux'],
+              (dynamic string) => string[1]),
           equals({
             'o': ['foo', 'bop'],
             'a': ['bar', 'baz'],
@@ -93,8 +100,8 @@
   group('minBy()', () {
     test('returns null for an empty iterable', () {
       expect(
-          minBy([], expectAsync1((_) {}, count: 0),
-              compare: expectAsync2((_, __) => null, count: 0)),
+          minBy([], expectAsync1((dynamic _) {}, count: 0),
+              compare: expectAsync2((dynamic _, dynamic __) => -1, count: 0)),
           isNull);
     });
 
@@ -108,7 +115,7 @@
             {'foo': 4},
             {'foo': 1},
             {'foo': 2}
-          ], (map) => map['foo']),
+          ], (dynamic map) => map['foo']),
           equals({'foo': 1}));
     });
 
@@ -121,7 +128,7 @@
             {'foo': 1},
             {'foo': 2}
           ], (map) => map,
-              compare: (map1, map2) => map1['foo'].compareTo(map2['foo'])),
+              compare: (map1, map2) => map1['foo']!.compareTo(map2['foo']!)),
           equals({'foo': 1}));
     });
   });
@@ -129,8 +136,8 @@
   group('maxBy()', () {
     test('returns null for an empty iterable', () {
       expect(
-          maxBy([], expectAsync1((_) {}, count: 0),
-              compare: expectAsync2((_, __) => null, count: 0)),
+          maxBy([], expectAsync1((dynamic _) {}, count: 0),
+              compare: expectAsync2((dynamic _, dynamic __) => null, count: 0)),
           isNull);
     });
 
@@ -144,7 +151,7 @@
             {'foo': 4},
             {'foo': 1},
             {'foo': 2}
-          ], (map) => map['foo']),
+          ], (dynamic map) => map['foo']),
           equals({'foo': 5}));
     });
 
@@ -156,8 +163,8 @@
             {'foo': 4},
             {'foo': 1},
             {'foo': 2}
-          ], (map) => map,
-              compare: (map1, map2) => map1['foo'].compareTo(map2['foo'])),
+          ], (map) => map!,
+              compare: (map1, map2) => map1['foo']!.compareTo(map2['foo']!)),
           equals({'foo': 5}));
     });
   });
diff --git a/test/priority_queue_test.dart b/test/priority_queue_test.dart
index 928de6c..b38b3aa 100644
--- a/test/priority_queue_test.dart
+++ b/test/priority_queue_test.dart
@@ -29,7 +29,7 @@
 }
 
 void testCustom(
-    PriorityQueue<C> Function(int Function(C, C) comparator) create) {
+    PriorityQueue<C> Function(int Function(C, C)? comparator) create) {
   for (var count in [1, 5, 127, 128]) {
     testQueue('Custom:$count/null', () => create(null),
         List<C>.generate(count, (x) => C(x)), C(count));
@@ -48,7 +48,8 @@
   test(name, () => testQueueBody(create, elements, notElement));
 }
 
-void testQueueBody(PriorityQueue Function() create, List elements, notElement) {
+void testQueueBody<T>(
+    PriorityQueue<T> Function() create, List<T> elements, notElement) {
   var q = create();
   expect(q.isEmpty, isTrue);
   expect(q, hasLength(0));
diff --git a/test/queue_list_test.dart b/test/queue_list_test.dart
index 4814550..21b6056 100644
--- a/test/queue_list_test.dart
+++ b/test/queue_list_test.dart
@@ -134,7 +134,7 @@
     });
 
     test('grows a smaller queue', () {
-      var queue = QueueList.from([1, 2, 3]);
+      var queue = QueueList<int?>.from([1, 2, 3]);
       queue.length = 5;
       expect(queue, equals([1, 2, 3, null, null]));
     });
@@ -142,6 +142,10 @@
     test('throws a RangeError if length is less than 0', () {
       expect(() => QueueList().length = -1, throwsRangeError);
     });
+
+    test('throws an UnsupportedError if element type is non-nullable', () {
+      expect(() => QueueList<int>().length = 1, throwsUnsupportedError);
+    });
   });
 
   group('[]', () {
diff --git a/test/union_set_controller_test.dart b/test/union_set_controller_test.dart
index 8f5ad83..3604d62 100644
--- a/test/union_set_controller_test.dart
+++ b/test/union_set_controller_test.dart
@@ -7,8 +7,8 @@
 import 'package:collection/collection.dart';
 
 void main() {
-  UnionSetController<int> controller;
-  Set<int> innerSet;
+  late UnionSetController<int> controller;
+  late Set<int> innerSet;
   setUp(() {
     innerSet = {1, 2, 3};
     controller = UnionSetController()..add(innerSet);
diff --git a/test/union_set_test.dart b/test/union_set_test.dart
index 795cf2b..e5da546 100644
--- a/test/union_set_test.dart
+++ b/test/union_set_test.dart
@@ -35,7 +35,7 @@
     });
 
     test("map() doesn't run on any elements", () {
-      expect(set.map(expectAsync1((_) {}, count: 0)), isEmpty);
+      expect(set.map(expectAsync1((dynamic _) {}, count: 0)), isEmpty);
     });
   });
 
diff --git a/test/unmodifiable_collection_test.dart b/test/unmodifiable_collection_test.dart
index a7abd89..ea93e31 100644
--- a/test/unmodifiable_collection_test.dart
+++ b/test/unmodifiable_collection_test.dart
@@ -117,8 +117,8 @@
   });
 
   test('$name - fold', () {
-    expect(wrapped.fold(0, (x, y) => x + y),
-        equals(original.fold(0, (x, y) => x + y)));
+    expect(wrapped.fold(0, (dynamic x, y) => x + y),
+        equals(original.fold(0, (dynamic x, y) => x + y)));
   });
 
   test('$name - forEach', () {
diff --git a/test/wrapper_test.dart b/test/wrapper_test.dart
index 43ff4c2..2612867 100644
--- a/test/wrapper_test.dart
+++ b/test/wrapper_test.dart
@@ -4,7 +4,9 @@
 
 /// Tests wrapper utilities.
 
+@TestOn('vm')
 import 'dart:collection';
+import 'dart:mirrors';
 
 import 'package:collection/collection.dart';
 import 'package:test/test.dart';
@@ -35,10 +37,12 @@
   // the *same* invocation on the wrapped object.
   dynamic equals;
 
+  InstanceMirror get mirror;
+
   @override
-  dynamic noSuchMethod(Invocation i) {
-    equals = wrappedChecker(i);
-    return null;
+  dynamic noSuchMethod(Invocation actual) {
+    equals = wrappedChecker(actual);
+    return mirror.delegate(actual);
   }
 
   @override
@@ -54,11 +58,14 @@
 // member invocation.
 class InvocationChecker {
   final Invocation _expected;
-  InvocationChecker(this._expected);
+  final InstanceMirror _instanceMirror;
+
+  InvocationChecker(this._expected, this._instanceMirror);
+
   @override
   dynamic noSuchMethod(Invocation actual) {
     testInvocations(_expected, actual);
-    return null;
+    return _instanceMirror.delegate(actual);
   }
 
   @override
@@ -77,122 +84,147 @@
 // argument to DelegatingIterable/Set/List/Queue.
 class IterableInvocationChecker<T> extends InvocationChecker
     implements Iterable<T> {
-  IterableInvocationChecker(Invocation expected) : super(expected);
+  IterableInvocationChecker(Invocation expected, InstanceMirror mirror)
+      : super(expected, mirror);
 }
 
 class ListInvocationChecker<T> extends InvocationChecker implements List<T> {
-  ListInvocationChecker(Invocation expected) : super(expected);
+  ListInvocationChecker(Invocation expected, InstanceMirror mirror)
+      : super(expected, mirror);
 }
 
 class SetInvocationChecker<T> extends InvocationChecker implements Set<T> {
-  SetInvocationChecker(Invocation expected) : super(expected);
+  SetInvocationChecker(Invocation expected, InstanceMirror mirror)
+      : super(expected, mirror);
 }
 
 class QueueInvocationChecker<T> extends InvocationChecker implements Queue<T> {
-  QueueInvocationChecker(Invocation expected) : super(expected);
+  QueueInvocationChecker(Invocation expected, InstanceMirror mirror)
+      : super(expected, mirror);
 }
 
 class MapInvocationChecker<K, V> extends InvocationChecker
     implements Map<K, V> {
-  MapInvocationChecker(Invocation expected) : super(expected);
+  MapInvocationChecker(Invocation expected, InstanceMirror mirror)
+      : super(expected, mirror);
 }
 
 // Expector that wraps in DelegatingIterable.
 class IterableExpector<T> extends Expector implements Iterable<T> {
   @override
+  final InstanceMirror mirror;
+
+  IterableExpector(Iterable<T> realInstance) : mirror = reflect(realInstance);
+
+  @override
   dynamic wrappedChecker(Invocation i) =>
-      DelegatingIterable<T>(IterableInvocationChecker<T>(i));
+      DelegatingIterable<T>(IterableInvocationChecker<T>(i, mirror));
 }
 
 // Expector that wraps in DelegatingList.
-class ListExpector<T> extends Expector implements List<T> {
+class ListExpector<T> extends IterableExpector<T> implements List<T> {
+  ListExpector(List<T> realInstance) : super(realInstance);
+
   @override
   dynamic wrappedChecker(Invocation i) =>
-      DelegatingList<T>(ListInvocationChecker<T>(i));
+      DelegatingList<T>(ListInvocationChecker<T>(i, mirror));
 }
 
 // Expector that wraps in DelegatingSet.
-class SetExpector<T> extends Expector implements Set<T> {
+class SetExpector<T> extends IterableExpector<T> implements Set<T> {
+  SetExpector(Set<T> realInstance) : super(realInstance);
+
   @override
   dynamic wrappedChecker(Invocation i) =>
-      DelegatingSet<T>(SetInvocationChecker<T>(i));
+      DelegatingSet<T>(SetInvocationChecker<T>(i, mirror));
 }
 
 // Expector that wraps in DelegatingSet.
-class QueueExpector<T> extends Expector implements Queue<T> {
+class QueueExpector<T> extends IterableExpector<T> implements Queue<T> {
+  QueueExpector(Queue<T> realInstance) : super(realInstance);
+
   @override
   dynamic wrappedChecker(Invocation i) =>
-      DelegatingQueue<T>(QueueInvocationChecker<T>(i));
+      DelegatingQueue<T>(QueueInvocationChecker<T>(i, mirror));
 }
 
 // Expector that wraps in DelegatingMap.
 class MapExpector<K, V> extends Expector implements Map<K, V> {
   @override
+  final InstanceMirror mirror;
+
+  MapExpector(Map<K, V> realInstance) : mirror = reflect(realInstance);
+
+  @override
   dynamic wrappedChecker(Invocation i) =>
-      DelegatingMap<K, V>(MapInvocationChecker<K, V>(i));
+      DelegatingMap<K, V>(MapInvocationChecker<K, V>(i, mirror));
 }
 
 // Utility values to use as arguments in calls.
 Null func0() => null;
-Null func1(Object x) => null;
-Null func2(Object x, Object y) => null;
-var val = Object();
+dynamic func1(dynamic x) => null;
+dynamic func2(dynamic x, dynamic y) => null;
+bool boolFunc(dynamic x) => true;
+Iterable<dynamic> expandFunc(dynamic x) => [x];
+dynamic foldFunc(dynamic previous, dynamic next) => previous;
+int compareFunc(dynamic x, dynamic y) => 0;
+var val = 10;
 
 void main() {
-  void testIterable(var expect) {
-    (expect..any(func1)).equals.any(func1);
+  void testIterable(IterableExpector expect) {
+    (expect..any(boolFunc)).equals.any(boolFunc);
     (expect..contains(val)).equals.contains(val);
     (expect..elementAt(0)).equals.elementAt(0);
-    (expect..every(func1)).equals.every(func1);
-    (expect..expand(func1)).equals.expand(func1);
+    (expect..every(boolFunc)).equals.every(boolFunc);
+    (expect..expand(expandFunc)).equals.expand(expandFunc);
     (expect..first).equals.first;
     // Default values of the Iterable interface will be added in the
     // second call to firstWhere, so we must record them in our
     // expectation (which doesn't have the interface implemented or
     // its default values).
-    (expect..firstWhere(func1, orElse: null)).equals.firstWhere(func1);
-    (expect..firstWhere(func1, orElse: func0))
+    (expect..firstWhere(boolFunc, orElse: null)).equals.firstWhere(boolFunc);
+    (expect..firstWhere(boolFunc, orElse: func0))
         .equals
-        .firstWhere(func1, orElse: func0);
-    (expect..fold(null, func2)).equals.fold(null, func2);
-    (expect..forEach(func1)).equals.forEach(func1);
+        .firstWhere(boolFunc, orElse: func0);
+    (expect..fold(null, foldFunc)).equals.fold(null, foldFunc);
+    (expect..forEach(boolFunc)).equals.forEach(boolFunc);
     (expect..isEmpty).equals.isEmpty;
     (expect..isNotEmpty).equals.isNotEmpty;
     (expect..iterator).equals.iterator;
     (expect..join('')).equals.join();
     (expect..join('X')).equals.join('X');
     (expect..last).equals.last;
-    (expect..lastWhere(func1, orElse: null)).equals.lastWhere(func1);
-    (expect..lastWhere(func1, orElse: func0))
+    (expect..lastWhere(boolFunc, orElse: null)).equals.lastWhere(boolFunc);
+    (expect..lastWhere(boolFunc, orElse: func0))
         .equals
-        .lastWhere(func1, orElse: func0);
+        .lastWhere(boolFunc, orElse: func0);
     (expect..length).equals.length;
     (expect..map(func1)).equals.map(func1);
     (expect..reduce(func2)).equals.reduce(func2);
     (expect..single).equals.single;
-    (expect..singleWhere(func1, orElse: null)).equals.singleWhere(func1);
+    (expect..singleWhere(boolFunc, orElse: null)).equals.singleWhere(boolFunc);
     (expect..skip(5)).equals.skip(5);
-    (expect..skipWhile(func1)).equals.skipWhile(func1);
+    (expect..skipWhile(boolFunc)).equals.skipWhile(boolFunc);
     (expect..take(5)).equals.take(5);
-    (expect..takeWhile(func1)).equals.takeWhile(func1);
+    (expect..takeWhile(boolFunc)).equals.takeWhile(boolFunc);
     (expect..toList(growable: true)).equals.toList();
     (expect..toList(growable: true)).equals.toList(growable: true);
     (expect..toList(growable: false)).equals.toList(growable: false);
     (expect..toSet()).equals.toSet();
     (expect..toString()).equals.toString();
-    (expect..where(func1)).equals.where(func1);
+    (expect..where(boolFunc)).equals.where(boolFunc);
   }
 
-  void testList(var expect) {
+  void testList(ListExpector expect) {
     testIterable(expect);
+    // Later expects require at least 5 items
+    (expect..add(val)).equals.add(val);
+    (expect..addAll([val, val, val, val])).equals.addAll([val, val, val, val]);
 
     (expect..[4]).equals[4];
     (expect..[4] = 5).equals[4] = 5;
 
-    (expect..add(val)).equals.add(val);
-    (expect..addAll([val])).equals.addAll([val]);
     (expect..asMap()).equals.asMap();
-    (expect..clear()).equals.clear();
     (expect..fillRange(4, 5, null)).equals.fillRange(4, 5);
     (expect..fillRange(4, 5, val)).equals.fillRange(4, 5, val);
     (expect..getRange(4, 5)).equals.getRange(4, 5);
@@ -202,25 +234,30 @@
     (expect..insertAll(4, [val])).equals.insertAll(4, [val]);
     (expect..lastIndexOf(val, null)).equals.lastIndexOf(val);
     (expect..lastIndexOf(val, 4)).equals.lastIndexOf(val, 4);
-    (expect..length = 4).equals.length = 4;
-    (expect..remove(val)).equals.remove(val);
-    (expect..removeAt(4)).equals.removeAt(4);
-    (expect..removeLast()).equals.removeLast();
-    (expect..removeRange(4, 5)).equals.removeRange(4, 5);
-    (expect..removeWhere(func1)).equals.removeWhere(func1);
     (expect..replaceRange(4, 5, [val])).equals.replaceRange(4, 5, [val]);
-    (expect..retainWhere(func1)).equals.retainWhere(func1);
+    (expect..retainWhere(boolFunc)).equals.retainWhere(boolFunc);
     (expect..reversed).equals.reversed;
     (expect..setAll(4, [val])).equals.setAll(4, [val]);
     (expect..setRange(4, 5, [val], 0)).equals.setRange(4, 5, [val]);
-    (expect..setRange(4, 5, [val], 3)).equals.setRange(4, 5, [val], 3);
-    (expect..sort(null)).equals.sort();
-    (expect..sort(func2)).equals.sort(func2);
+    (expect..setRange(4, 5, [val, val], 1))
+        .equals
+        .setRange(4, 5, [val, val], 1);
+    (expect..sort()).equals.sort();
+    (expect..sort(compareFunc)).equals.sort(compareFunc);
     (expect..sublist(4, null)).equals.sublist(4);
     (expect..sublist(4, 5)).equals.sublist(4, 5);
+
+    // Do destructive apis last so other ones can work properly
+    (expect..removeAt(4)).equals.removeAt(4);
+    (expect..remove(val)).equals.remove(val);
+    (expect..removeLast()).equals.removeLast();
+    (expect..removeRange(4, 5)).equals.removeRange(4, 5);
+    (expect..removeWhere(boolFunc)).equals.removeWhere(boolFunc);
+    (expect..length = 5).equals.length = 5;
+    (expect..clear()).equals.clear();
   }
 
-  void testSet(var expect) {
+  void testSet(SetExpector expect) {
     testIterable(expect);
     var set = <dynamic>{};
     (expect..add(val)).equals.add(val);
@@ -231,25 +268,25 @@
     (expect..intersection(set)).equals.intersection(set);
     (expect..remove(val)).equals.remove(val);
     (expect..removeAll([val])).equals.removeAll([val]);
-    (expect..removeWhere(func1)).equals.removeWhere(func1);
+    (expect..removeWhere(boolFunc)).equals.removeWhere(boolFunc);
     (expect..retainAll([val])).equals.retainAll([val]);
-    (expect..retainWhere(func1)).equals.retainWhere(func1);
+    (expect..retainWhere(boolFunc)).equals.retainWhere(boolFunc);
     (expect..union(set)).equals.union(set);
   }
 
-  void testQueue(var expect) {
+  void testQueue(QueueExpector expect) {
     testIterable(expect);
     (expect..add(val)).equals.add(val);
     (expect..addAll([val])).equals.addAll([val]);
     (expect..addFirst(val)).equals.addFirst(val);
     (expect..addLast(val)).equals.addLast(val);
-    (expect..clear()).equals.clear();
     (expect..remove(val)).equals.remove(val);
     (expect..removeFirst()).equals.removeFirst();
     (expect..removeLast()).equals.removeLast();
+    (expect..clear()).equals.clear();
   }
 
-  void testMap(var expect) {
+  void testMap(MapExpector expect) {
     var map = {};
     (expect..[val]).equals[val];
     (expect..[val] = val).equals[val] = val;
@@ -273,7 +310,7 @@
   // [setUpSet] should return a set with two elements: "foo" and "bar".
   void testTwoElementSet(Set<String> Function() setUpSet) {
     group('with two elements', () {
-      Set<String> set;
+      late Set<String> set;
       setUp(() => set = setUpSet());
 
       test('.any', () {
@@ -313,7 +350,9 @@
       });
 
       test('.fold', () {
-        expect(set.fold('start', (previous, element) => previous + element),
+        expect(
+            set.fold(
+                'start', (dynamic previous, element) => previous + element),
             equals('startfoobar'));
       });
 
@@ -434,28 +473,28 @@
   }
 
   test('Iterable', () {
-    testIterable(IterableExpector());
+    testIterable(IterableExpector([1]));
   });
 
   test('List', () {
-    testList(ListExpector());
+    testList(ListExpector([1]));
   });
 
   test('Set', () {
-    testSet(SetExpector());
+    testSet(SetExpector({1}));
   });
 
   test('Queue', () {
-    testQueue(QueueExpector());
+    testQueue(QueueExpector(Queue.of([1])));
   });
 
   test('Map', () {
-    testMap(MapExpector());
+    testMap(MapExpector({'a': 'b'}));
   });
 
   group('MapKeySet', () {
-    Map<String, dynamic> map;
-    Set<String> set;
+    late Map<String, dynamic> map;
+    late Set<String> set;
 
     setUp(() {
       map = <String, int>{};
@@ -522,8 +561,8 @@
   });
 
   group('MapValueSet', () {
-    Map<String, String> map;
-    Set<String> set;
+    late Map<String, String> map;
+    late Set<String> set;
 
     setUp(() {
       map = <String, String>{};