[cfe] Allow (inferred) void in for loop variables

Closes https://github.com/dart-lang/sdk/issues/48347

Change-Id: Idc8c4c81dac66574c5d24d41c4a81b66fcf34597
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/232601
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 2e8000f..7c41343 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -1111,6 +1111,7 @@
     TreeNode parent = variable.parent!;
     Expression implicitDowncast = inferrer.ensureAssignable(
         variable.type, inferredType, variableGet,
+        isVoidAllowed: true,
         fileOffset: parent.fileOffset,
         errorTemplate: templateForInLoopElementTypeNotAssignable,
         nullabilityErrorTemplate:
diff --git a/pkg/front_end/testcases/general/inferred_void.dart b/pkg/front_end/testcases/general/inferred_void.dart
new file mode 100644
index 0000000..5a39d07
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, 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.
+
+void method() {}
+
+void v1 = method();
+var v2 = method();
+List<void> l1 = [method()];
+var l2 = [method()];
+
+test(Iterable<void> iterable, Stream<void> stream) async {
+  void v1 = method();
+  var v2 = method();
+  for (var v3 in iterable) {}
+  for (void v4 in iterable) {}
+  await for (var v5 in stream) {}
+  await for (void v6 in stream) {}
+  List<void> l1 = [method()];
+  var l2 = [method()];
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.textual_outline.expect b/pkg/front_end/testcases/general/inferred_void.dart.textual_outline.expect
new file mode 100644
index 0000000..258ee94
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+void method() {}
+void v1 = method();
+var v2 = method();
+List<void> l1 = [method()];
+var l2 = [method()];
+test(Iterable<void> iterable, Stream<void> stream) async {}
+main() {}
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/inferred_void.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..f8f634b
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+List<void> l1 = [method()];
+main() {}
+test(Iterable<void> iterable, Stream<void> stream) async {}
+var l2 = [method()];
+var v2 = method();
+void method() {}
+void v1 = method();
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.weak.expect b/pkg/front_end/testcases/general/inferred_void.dart.weak.expect
new file mode 100644
index 0000000..c8d8b7d
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.weak.expect
@@ -0,0 +1,25 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+static field void v1 = self::method();
+static field void v2 = self::method();
+static field core::List<void> l1 = <void>[self::method()];
+static field core::List<void> l2 = <void>[self::method()];
+static method method() → void {}
+static method test(core::Iterable<void> iterable, asy::Stream<void> stream) → dynamic async {
+  void v1 = self::method();
+  void v2 = self::method();
+  for (void v3 in iterable) {
+  }
+  for (void v4 in iterable) {
+  }
+  await for (void v5 in stream) {
+  }
+  await for (void v6 in stream) {
+  }
+  core::List<void> l1 = <void>[self::method()];
+  core::List<void> l2 = <void>[self::method()];
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.weak.modular.expect b/pkg/front_end/testcases/general/inferred_void.dart.weak.modular.expect
new file mode 100644
index 0000000..c8d8b7d
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.weak.modular.expect
@@ -0,0 +1,25 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+static field void v1 = self::method();
+static field void v2 = self::method();
+static field core::List<void> l1 = <void>[self::method()];
+static field core::List<void> l2 = <void>[self::method()];
+static method method() → void {}
+static method test(core::Iterable<void> iterable, asy::Stream<void> stream) → dynamic async {
+  void v1 = self::method();
+  void v2 = self::method();
+  for (void v3 in iterable) {
+  }
+  for (void v4 in iterable) {
+  }
+  await for (void v5 in stream) {
+  }
+  await for (void v6 in stream) {
+  }
+  core::List<void> l1 = <void>[self::method()];
+  core::List<void> l2 = <void>[self::method()];
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.weak.outline.expect b/pkg/front_end/testcases/general/inferred_void.dart.weak.outline.expect
new file mode 100644
index 0000000..c0d467d
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.weak.outline.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+static field void v1;
+static field void v2;
+static field core::List<void> l1;
+static field core::List<void> l2;
+static method method() → void
+  ;
+static method test(core::Iterable<void> iterable, asy::Stream<void> stream) → dynamic async 
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/general/inferred_void.dart.weak.transformed.expect b/pkg/front_end/testcases/general/inferred_void.dart.weak.transformed.expect
new file mode 100644
index 0000000..6528bab
--- /dev/null
+++ b/pkg/front_end/testcases/general/inferred_void.dart.weak.transformed.expect
@@ -0,0 +1,101 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+import "dart:_internal" as _in;
+
+static field void v1 = self::method();
+static field void v2 = self::method();
+static field core::List<void> l1 = core::_GrowableList::_literal1<void>(self::method());
+static field core::List<void> l2 = core::_GrowableList::_literal1<void>(self::method());
+static method method() → void {}
+static method test(core::Iterable<void> iterable, asy::Stream<void> stream) → dynamic /* originally async */ {
+  final asy::_Future<dynamic> :async_future = new asy::_Future::•<dynamic>();
+  core::bool* :is_sync = false;
+  dynamic :return_value;
+  (dynamic) → dynamic :async_op_then;
+  (core::Object, core::StackTrace) → dynamic :async_op_error;
+  core::int :await_jump_var = 0;
+  dynamic :await_ctx_var;
+  dynamic :saved_try_context_var0;
+  dynamic :saved_try_context_var1;
+  dynamic :exception0;
+  dynamic :stack_trace0;
+  function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding 
+    try {
+      #L1:
+      {
+        void v1 = self::method();
+        void v2 = self::method();
+        {
+          core::Iterator<void> :sync-for-iterator = iterable.{core::Iterable::iterator}{core::Iterator<void>};
+          for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
+            void v3 = :sync-for-iterator.{core::Iterator::current}{void};
+            {}
+          }
+        }
+        {
+          core::Iterator<void> :sync-for-iterator = iterable.{core::Iterable::iterator}{core::Iterator<void>};
+          for (; :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
+            void v4 = :sync-for-iterator.{core::Iterator::current}{void};
+            {}
+          }
+        }
+        {
+          asy::Stream<void> :stream = stream;
+          asy::_StreamIterator<void>? :for-iterator = new asy::_StreamIterator::•<void>(:stream);
+          try
+            #L2:
+            while (true) {
+              dynamic #t1 = asy::_asyncStarMoveNextHelper(:stream);
+              [yield] let dynamic #t2 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}, :async_op_then, :async_op_error, :async_op) in null;
+              if(_in::unsafeCast<core::bool>(:result)) {
+                void v5 = :for-iterator.{asy::_StreamIterator::current}{void};
+                {}
+              }
+              else
+                break #L2;
+            }
+          finally
+            if(!(:for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<void>?} == null)) {
+              [yield] let dynamic #t3 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>}, :async_op_then, :async_op_error, :async_op) in null;
+              :result;
+            }
+        }
+        {
+          asy::Stream<void> :stream = stream;
+          asy::_StreamIterator<void>? :for-iterator = new asy::_StreamIterator::•<void>(:stream);
+          try
+            #L3:
+            while (true) {
+              dynamic #t4 = asy::_asyncStarMoveNextHelper(:stream);
+              [yield] let dynamic #t5 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}, :async_op_then, :async_op_error, :async_op) in null;
+              if(_in::unsafeCast<core::bool>(:result)) {
+                void v6 = :for-iterator.{asy::_StreamIterator::current}{void};
+                {}
+              }
+              else
+                break #L3;
+            }
+          finally
+            if(!(:for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<void>?} == null)) {
+              [yield] let dynamic #t6 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>}, :async_op_then, :async_op_error, :async_op) in null;
+              :result;
+            }
+        }
+        core::List<void> l1 = core::_GrowableList::_literal1<void>(self::method());
+        core::List<void> l2 = core::_GrowableList::_literal1<void>(self::method());
+      }
+      asy::_completeWithNoFutureOnAsyncReturn(:async_future, :return_value, :is_sync);
+      return;
+    }
+    on dynamic catch(dynamic exception, core::StackTrace stack_trace) {
+      asy::_completeOnAsyncError(:async_future, exception, stack_trace, :is_sync);
+    }
+  :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+  :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+  :async_op(){() → dynamic};
+  :is_sync = true;
+  return :async_future;
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart b/pkg/front_end/testcases/general/issue48347.dart
new file mode 100644
index 0000000..8b9e1ab
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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.
+
+import 'dart:async';
+
+test(StreamController<void> _eventStreamController) async {
+  await for (final _ in _eventStreamController.stream) {}
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue48347.dart.textual_outline.expect
new file mode 100644
index 0000000..0278a37
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.textual_outline.expect
@@ -0,0 +1,4 @@
+import 'dart:async';
+
+test(StreamController<void> _eventStreamController) async {}
+main() {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue48347.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..50c55af
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.textual_outline_modelled.expect
@@ -0,0 +1,4 @@
+import 'dart:async';
+
+main() {}
+test(StreamController<void> _eventStreamController) async {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart.weak.expect b/pkg/front_end/testcases/general/issue48347.dart.weak.expect
new file mode 100644
index 0000000..555e190
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.weak.expect
@@ -0,0 +1,11 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+
+import "dart:async";
+
+static method test(asy::StreamController<void> _eventStreamController) → dynamic async {
+  await for (final void _ in _eventStreamController.{asy::StreamController::stream}{asy::Stream<void>}) {
+  }
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart.weak.modular.expect b/pkg/front_end/testcases/general/issue48347.dart.weak.modular.expect
new file mode 100644
index 0000000..555e190
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.weak.modular.expect
@@ -0,0 +1,11 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+
+import "dart:async";
+
+static method test(asy::StreamController<void> _eventStreamController) → dynamic async {
+  await for (final void _ in _eventStreamController.{asy::StreamController::stream}{asy::Stream<void>}) {
+  }
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/general/issue48347.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue48347.dart.weak.outline.expect
new file mode 100644
index 0000000..3f670be
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.weak.outline.expect
@@ -0,0 +1,10 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+
+import "dart:async";
+
+static method test(asy::StreamController<void> _eventStreamController) → dynamic async 
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/general/issue48347.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue48347.dart.weak.transformed.expect
new file mode 100644
index 0000000..b10bc16
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue48347.dart.weak.transformed.expect
@@ -0,0 +1,59 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+import "dart:async";
+
+static method test(asy::StreamController<void> _eventStreamController) → dynamic /* originally async */ {
+  final asy::_Future<dynamic> :async_future = new asy::_Future::•<dynamic>();
+  core::bool* :is_sync = false;
+  dynamic :return_value;
+  (dynamic) → dynamic :async_op_then;
+  (core::Object, core::StackTrace) → dynamic :async_op_error;
+  core::int :await_jump_var = 0;
+  dynamic :await_ctx_var;
+  dynamic :saved_try_context_var0;
+  dynamic :saved_try_context_var1;
+  dynamic :exception0;
+  dynamic :stack_trace0;
+  function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding 
+    try {
+      #L1:
+      {
+        {
+          asy::Stream<void> :stream = _eventStreamController.{asy::StreamController::stream}{asy::Stream<void>};
+          asy::_StreamIterator<void>? :for-iterator = new asy::_StreamIterator::•<void>(:stream);
+          try
+            #L2:
+            while (true) {
+              dynamic #t1 = asy::_asyncStarMoveNextHelper(:stream);
+              [yield] let dynamic #t2 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}, :async_op_then, :async_op_error, :async_op) in null;
+              if(_in::unsafeCast<core::bool>(:result)) {
+                final void _ = :for-iterator.{asy::_StreamIterator::current}{void};
+                {}
+              }
+              else
+                break #L2;
+            }
+          finally
+            if(!(:for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<void>?} == null)) {
+              [yield] let dynamic #t3 = asy::_awaitHelper(:for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>}, :async_op_then, :async_op_error, :async_op) in null;
+              :result;
+            }
+        }
+      }
+      asy::_completeWithNoFutureOnAsyncReturn(:async_future, :return_value, :is_sync);
+      return;
+    }
+    on dynamic catch(dynamic exception, core::StackTrace stack_trace) {
+      asy::_completeOnAsyncError(:async_future, exception, stack_trace, :is_sync);
+    }
+  :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+  :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+  :async_op(){() → dynamic};
+  :is_sync = true;
+  return :async_future;
+}
+static method main() → dynamic {}