Merge the null_safety branch into master (#40)

diff --git a/.travis.yml b/.travis.yml
index 18f7413..9061589 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,39 @@
 language: dart
 
 dart:
-  - 2.0.0
-  - dev
+ - dev
 
-dart_task:
-  - test: --platform vm,chrome
-  - dartfmt
-  - dartanalyzer: --fatal-infos --fatal-warnings .
+jobs:
+  include:
+    - stage: analyze_and_format
+      name: "Analyze"
+      dart: dev
+      os: linux
+      script: dartanalyzer --enable-experiment=non-nullable --fatal-warnings --fatal-infos .
+    - 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]
 
 cache:
- directories:
-   - $HOME/.pub-cache
+  directories:
+    - $HOME/.pub-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c680dd2..a413d78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
-## 1.4.1
+## 1.5.0-nullsafety
 
+* Migrate to null safety.
 * `forEach`: Avoid `await null` if the `Stream` is not paused.
   Improves trivial benchmark by 40%. 
 
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 8bef881..986bccc 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -4,6 +4,9 @@
   strong-mode:
     implicit-casts: false
 
+  enable-experiment:
+  - non-nullable
+
 linter:
   rules:
     - avoid_function_literals_in_foreach_calls
@@ -36,6 +39,8 @@
     - prefer_initializing_formals
     - prefer_interpolation_to_compose_strings
     - prefer_typing_uninitialized_variables
+    - recursive_getters
+    - slash_for_doc_comments
     - test_types_in_equals
     - throw_in_finally
     - unnecessary_await_in_return
diff --git a/benchmark/for_each_benchmark.dart b/benchmark/for_each_benchmark.dart
index 7438ace..2973b2a 100644
--- a/benchmark/for_each_benchmark.dart
+++ b/benchmark/for_each_benchmark.dart
@@ -8,22 +8,22 @@
   final watch = Stopwatch()..start();
   final start = DateTime.now();
 
-  DateTime lastLog;
-  Duration fastest;
-  int fastestIteration;
+  DateTime? lastLog;
+  Duration? fastest;
+  late int fastestIteration;
   var i = 1;
 
   void log(bool force) {
     var now = DateTime.now();
     if (force ||
         lastLog == null ||
-        now.difference(lastLog) > const Duration(seconds: 1)) {
+        now.difference(lastLog!) > const Duration(seconds: 1)) {
       lastLog = now;
       print([
         now.difference(start),
         i.toString().padLeft(10),
         fastestIteration.toString().padLeft(7),
-        fastest.inMicroseconds.toString().padLeft(9)
+        fastest!.inMicroseconds.toString().padLeft(9)
       ].join('   '));
     }
   }
diff --git a/lib/pool.dart b/lib/pool.dart
index d856541..20ab8f1 100644
--- a/lib/pool.dart
+++ b/lib/pool.dart
@@ -51,16 +51,16 @@
   /// for additional files to be read before they could be closed.
   ///
   /// This is `null` if this pool shouldn't time out.
-  RestartableTimer _timer;
+  RestartableTimer? _timer;
 
   /// The amount of time to wait before timing out the pending resources.
-  final Duration _timeout;
+  final Duration? _timeout;
 
   /// A [FutureGroup] that tracks all the `onRelease` callbacks for resources
   /// that have been marked releasable.
   ///
   /// This is `null` until [close] is called.
-  FutureGroup _closeGroup;
+  FutureGroup? _closeGroup;
 
   /// Whether [close] has been called.
   bool get isClosed => _closeMemo.hasRun;
@@ -78,7 +78,7 @@
   /// If [timeout] is passed, then if that much time passes without any activity
   /// all pending [request] futures will throw a [TimeoutException]. This is
   /// intended to avoid deadlocks.
-  Pool(this._maxAllocatedResources, {Duration timeout}) : _timeout = timeout {
+  Pool(this._maxAllocatedResources, {Duration? timeout}) : _timeout = timeout {
     if (_maxAllocatedResources <= 0) {
       throw ArgumentError.value(_maxAllocatedResources, 'maxAllocatedResources',
           'Must be greater than zero.');
@@ -152,17 +152,17 @@
   /// to, a [StateError] is thrown.
   Stream<T> forEach<S, T>(
       Iterable<S> elements, FutureOr<T> Function(S source) action,
-      {bool Function(S item, Object error, StackTrace stack) onError}) {
+      {bool Function(S item, Object error, StackTrace stack)? onError}) {
     onError ??= (item, e, s) => true;
 
     var cancelPending = false;
 
-    Completer resumeCompleter;
-    StreamController<T> controller;
+    Completer? resumeCompleter;
+    late StreamController<T> controller;
 
-    Iterator<S> iterator;
+    late Iterator<S> iterator;
 
-    Future<void> run(int i) async {
+    Future<void> run(int _) async {
       while (iterator.moveNext()) {
         // caching `current` is necessary because there are async breaks
         // in this code and `iterator` is shared across many workers
@@ -171,7 +171,7 @@
         _resetTimer();
 
         if (resumeCompleter != null) {
-          await resumeCompleter.future;
+          await resumeCompleter!.future;
         }
 
         if (cancelPending) {
@@ -182,7 +182,7 @@
         try {
           value = await action(current);
         } catch (e, stack) {
-          if (onError(current, e, stack)) {
+          if (onError!(current, e, stack)) {
             controller.addError(e, stack);
           }
           continue;
@@ -191,20 +191,19 @@
       }
     }
 
-    Future doneFuture;
+    Future<void>? doneFuture;
 
     void onListen() {
-      assert(iterator == null);
       iterator = elements.iterator;
 
       assert(doneFuture == null);
-      doneFuture = Future.wait(
-              Iterable<int>.generate(_maxAllocatedResources)
-                  .map((i) => withResource(() => run(i))),
-              eagerError: true)
+      var futures = Iterable<Future<void>>.generate(
+          _maxAllocatedResources, (i) => withResource(() => run(i)));
+      doneFuture = Future.wait(futures, eagerError: true)
+          .then<void>((_) {})
           .catchError(controller.addError);
 
-      doneFuture.whenComplete(controller.close);
+      doneFuture!.whenComplete(controller.close);
     }
 
     controller = StreamController<T>(
@@ -217,11 +216,11 @@
       },
       onPause: () {
         assert(resumeCompleter == null);
-        resumeCompleter = Completer();
+        resumeCompleter = Completer<void>();
       },
       onResume: () {
         assert(resumeCompleter != null);
-        resumeCompleter.complete();
+        resumeCompleter!.complete();
         resumeCompleter = null;
       },
     );
@@ -241,20 +240,20 @@
   ///
   /// This may be called more than once; it returns the same [Future] each time.
   Future close() => _closeMemo.runOnce(() {
-        if (_closeGroup != null) return _closeGroup.future;
+        if (_closeGroup != null) return _closeGroup!.future;
 
         _resetTimer();
 
         _closeGroup = FutureGroup();
         for (var callback in _onReleaseCallbacks) {
-          _closeGroup.add(Future.sync(callback));
+          _closeGroup!.add(Future.sync(callback));
         }
 
         _allocatedResources -= _onReleaseCallbacks.length;
         _onReleaseCallbacks.clear();
 
-        if (_allocatedResources == 0) _closeGroup.close();
-        return _closeGroup.future;
+        if (_allocatedResources == 0) _closeGroup!.close();
+        return _closeGroup!.future;
       });
   final _closeMemo = AsyncMemoizer();
 
@@ -267,7 +266,7 @@
       pending.complete(PoolResource._(this));
     } else {
       _allocatedResources--;
-      if (isClosed && _allocatedResources == 0) _closeGroup.close();
+      if (isClosed && _allocatedResources == 0) _closeGroup!.close();
     }
   }
 
@@ -280,9 +279,9 @@
       var pending = _requestedResources.removeFirst();
       pending.complete(_runOnRelease(onRelease));
     } else if (isClosed) {
-      _closeGroup.add(Future.sync(onRelease));
+      _closeGroup!.add(Future.sync(onRelease));
       _allocatedResources--;
-      if (_allocatedResources == 0) _closeGroup.close();
+      if (_allocatedResources == 0) _closeGroup!.close();
     } else {
       var zone = Zone.current;
       var registered = zone.registerCallback(onRelease);
@@ -298,7 +297,7 @@
   Future<PoolResource> _runOnRelease(Function() onRelease) {
     Future.sync(onRelease).then((value) {
       _onReleaseCompleters.removeFirst().complete(PoolResource._(this));
-    }).catchError((error, StackTrace stackTrace) {
+    }).catchError((Object error, StackTrace stackTrace) {
       _onReleaseCompleters.removeFirst().completeError(error, stackTrace);
     });
 
@@ -312,9 +311,9 @@
     if (_timer == null) return;
 
     if (_requestedResources.isEmpty) {
-      _timer.cancel();
+      _timer!.cancel();
     } else {
-      _timer.reset();
+      _timer!.reset();
     }
   }
 
diff --git a/pubspec.yaml b/pubspec.yaml
index c87617f..8fc0e14 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: pool
-version: 1.4.1-dev
+version: 1.5.0-nullsafety
 
 description: >-
   Manage a finite pool of resources.
@@ -7,13 +7,96 @@
 homepage: https://github.com/dart-lang/pool
 
 environment:
-  sdk: '>=2.0.0 <3.0.0'
+  sdk: '>=2.9.0-18.0 <2.9.0'
 
 dependencies:
-  async: '>=1.4.0 <3.0.0'
-  stack_trace: '>=0.9.2 <2.0.0'
+  async: '>=2.5.0-nullsafety <2.5.0'
+  stack_trace: '>=1.10.0-nullsafety <1.10.0'
 
 dev_dependencies:
   fake_async: ^1.0.0
   pedantic: ^1.4.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
+  clock:
+    git:
+      url: git://github.com/dart-lang/clock.git
+      ref: null_safety
+  collection: 1.15.0-nullsafety
+  js:
+    git:
+      url: git://github.com/dart-lang/sdk.git
+      path: pkg/js
+  fake_async:
+    git:
+      url: git://github.com/dart-lang/fake_async.git
+      ref: null_safety
+  matcher:
+    git:
+      url: git://github.com/dart-lang/matcher.git
+      ref: null_safety
+  meta: 1.3.0-nullsafety
+  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
+  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/pool_test.dart b/test/pool_test.dart
index 091cd55..20e92d5 100644
--- a/test/pool_test.dart
+++ b/test/pool_test.dart
@@ -438,7 +438,7 @@
   });
 
   group('forEach', () {
-    Pool pool;
+    late Pool pool;
 
     tearDown(() async {
       await pool.close();
@@ -609,7 +609,7 @@
           delayedToString);
 
       // ignore: cancel_subscriptions
-      StreamSubscription subscription;
+      late StreamSubscription subscription;
 
       subscription = stream.listen(
         (data) {
@@ -659,9 +659,9 @@
     });
 
     group('errors', () {
-      Future<void> errorInIterator(
-          {bool Function(int item, Object error, StackTrace stack)
-              onError}) async {
+      Future<void> errorInIterator({
+        bool Function(int item, Object error, StackTrace stack)? onError,
+      }) async {
         pool = Pool(20);
 
         var listFuture = pool
@@ -704,7 +704,8 @@
       test('error in action, no onError', () async {
         pool = Pool(20);
 
-        var list = await pool.forEach(Iterable<int>.generate(100), (i) async {
+        var list = await pool.forEach(Iterable<int>.generate(100),
+            (int i) async {
           await Future.delayed(const Duration(milliseconds: 10));
           if (i % 10 == 0) {
             throw UnsupportedError('Multiples of 10 not supported');
@@ -737,9 +738,7 @@
 /// A matcher for Futures that asserts that they don't complete.
 ///
 /// This should only be called within a [FakeAsync.run] zone.
-Matcher get doesNotComplete => predicate((future) {
-      expect(future, const TypeMatcher<Future>());
-
+Matcher get doesNotComplete => predicate((Future future) {
       var stack = Trace.current(1);
       future.then((_) => registerException(
           TestFailure('Expected future not to complete.'), stack));