[dart2js] Fix implicitly used free type variable not being registered in closure scope.

Fixes: https://github.com/dart-lang/sdk/issues/54209
Change-Id: Ia042ac4444ebe134107ab5d626a3328d49b38241
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/339381
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/ir/scope_visitor.dart b/pkg/compiler/lib/src/ir/scope_visitor.dart
index 463dc88..0b2385b 100644
--- a/pkg/compiler/lib/src/ir/scope_visitor.dart
+++ b/pkg/compiler/lib/src/ir/scope_visitor.dart
@@ -431,6 +431,14 @@
     }
     enterNewScope(node, () {
       visitNode(node.variable);
+      if (node.isAsync) {
+        // If this is async then the type is explicitly used to instantiate
+        // the underlying StreamIterator.
+        visitInContext(
+            node.variable.type,
+            VariableUse.constructorTypeArgument(
+                _coreTypes.streamIteratorDefaultConstructor));
+      }
       visitInVariableScope(node, () {
         visitNode(node.iterable);
         visitNode(node.body);
diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart
index 8d946d2..6ca042c 100644
--- a/pkg/compiler/lib/src/ssa/locals_handler.dart
+++ b/pkg/compiler/lib/src/ssa/locals_handler.dart
@@ -250,20 +250,18 @@
     // ClosureRepresentationInfos).
     final scopeInfo = _scopeInfo;
     if (scopeInfo is ClosureRepresentationInfo && scopeInfo.isClosure) {
-      ClosureRepresentationInfo closureData = scopeInfo;
       // If the freeVariableMapping is not empty, then this function was a
       // nested closure that captures variables. Redirect the captured
       // variables to fields in the closure.
-      closureData.forEachFreeVariable(_localsMap!,
-          (Local from, FieldEntity to) {
+      scopeInfo.forEachFreeVariable(_localsMap!, (Local from, FieldEntity to) {
         redirectElement(from, to);
       });
       // Inside closure redirect references to itself to [:this:].
-      HThis thisInstruction = HThis(closureData.thisLocal as ThisLocal?,
-          _abstractValueDomain.nonNullType);
+      HThis thisInstruction = HThis(
+          scopeInfo.thisLocal as ThisLocal?, _abstractValueDomain.nonNullType);
       builder.graph.thisInstruction = thisInstruction;
       builder.graph.entry.addAtEntry(thisInstruction);
-      updateLocal(closureData.getClosureEntity(_localsMap!)!, thisInstruction);
+      updateLocal(scopeInfo.getClosureEntity(_localsMap!)!, thisInstruction);
     } else if (element.isInstanceMember) {
       // Once closures have been mapped to classes their instance members might
       // not have any thisElement if the closure was created inside a static
diff --git a/tests/web/regress/issue/54209_test.dart b/tests/web/regress/issue/54209_test.dart
new file mode 100644
index 0000000..27dfc4e
--- /dev/null
+++ b/tests/web/regress/issue/54209_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2023, 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.
+
+// Implicit uses of `foo.T` were not being registered in the local closure's
+// scope. The `await for` was then trying to lookup the `Foo.T` to register it
+// as the type argument to Stream. But the lookup failed because it `Foo.T`
+// wasn't registered.
+
+class A {
+  Future<void> foo<T>(Stream<T> Function() f) async {
+    await (() async {
+      await for (var v in f()) {
+        print(v);
+      }
+    }());
+  }
+}
+
+void main() {
+  A().foo<int>(() => Stream<int>.value(3));
+}