void_checks: Specify individual types which a FutureOr<void> target can accept (#2274)

* void_checks: Specify individual types which a FutureOr<void> target can accept
diff --git a/lib/src/rules/void_checks.dart b/lib/src/rules/void_checks.dart
index 24b9f14..2e59a69 100644
--- a/lib/src/rules/void_checks.dart
+++ b/lib/src/rules/void_checks.dart
@@ -42,7 +42,6 @@
   void registerNodeProcessors(
       NodeLintRegistry registry, LinterContext context) {
     final visitor = _Visitor(this, context);
-    registry.addCompilationUnit(this, visitor);
     registry.addMethodInvocation(this, visitor);
     registry.addInstanceCreationExpression(this, visitor);
     registry.addAssignmentExpression(this, visitor);
@@ -56,8 +55,6 @@
   final LinterContext context;
   final TypeSystem typeSystem;
 
-  InterfaceType _futureDynamicType;
-
   _Visitor(this.rule, this.context) : typeSystem = context.typeSystem;
 
   bool isTypeAcceptableWhenExpectingVoid(DartType type) {
@@ -65,13 +62,24 @@
     if (type.isDartCoreNull) return true;
     if (type.isDartAsyncFuture &&
         type is InterfaceType &&
-        (type.typeArguments.first.isVoid ||
-            type.typeArguments.first.isDartCoreNull)) {
+        isTypeAcceptableWhenExpectingVoid(type.typeArguments.first)) {
       return true;
     }
     return false;
   }
 
+  bool isTypeAcceptableWhenExpectingFutureOrVoid(DartType type) {
+    if (type.isDynamic) return true;
+    if (isTypeAcceptableWhenExpectingVoid(type)) return true;
+    if (type.isDartAsyncFutureOr &&
+        type is InterfaceType &&
+        isTypeAcceptableWhenExpectingFutureOrVoid(type.typeArguments.first)) {
+      return true;
+    }
+
+    return false;
+  }
+
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     final type = node.writeType;
@@ -80,11 +88,6 @@
   }
 
   @override
-  void visitCompilationUnit(CompilationUnit node) {
-    _futureDynamicType = context.typeProvider.futureDynamicType;
-  }
-
-  @override
   void visitInstanceCreationExpression(InstanceCreationExpression node) {
     final args = node.argumentList.arguments;
     final parameters = node.constructorName.staticElement?.parameters;
@@ -131,11 +134,12 @@
     checkedNode ??= node;
     if (expectedType == null || type == null) {
       return;
-    } else if (expectedType.isVoid &&
-            !isTypeAcceptableWhenExpectingVoid(type) ||
-        expectedType.isDartAsyncFutureOr &&
-            (expectedType as InterfaceType).typeArguments.first.isVoid &&
-            !typeSystem.isAssignableTo(type, _futureDynamicType)) {
+    }
+    if (expectedType.isVoid && !isTypeAcceptableWhenExpectingVoid(type)) {
+      rule.reportLint(node);
+    } else if (expectedType.isDartAsyncFutureOr &&
+        (expectedType as InterfaceType).typeArguments.first.isVoid &&
+        !isTypeAcceptableWhenExpectingFutureOrVoid(type)) {
       rule.reportLint(node);
     } else if (checkedNode is FunctionExpression &&
         checkedNode.body is! ExpressionFunctionBody &&
diff --git a/test/rules/experiments/nnbd/rules/void_checks.dart b/test/rules/experiments/nnbd/rules/void_checks.dart
new file mode 100644
index 0000000..99ce214
--- /dev/null
+++ b/test/rules/experiments/nnbd/rules/void_checks.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// test w/ `pub run test -N void_checks`
+
+import 'dart:async';
+
+void emptyFunctionExpressionReturningFutureOrVoid(FutureOr<void> Function() f) {
+  f = () {}; // OK
+}
diff --git a/test/rules/void_checks.dart b/test/rules/void_checks.dart
index 0fa4f7e..97feb86 100644
--- a/test/rules/void_checks.dart
+++ b/test/rules/void_checks.dart
@@ -207,3 +207,7 @@
   void foo() {}
   foo(0);
 }
+
+void emptyFunctionExpressionReturningFutureOrVoid(FutureOr<void> Function() f) {
+  f = () {}; // OK
+}