Add summary support for "for each" elements with identifiers.

Change-Id: Id75fbf13d2d7063ff1e3e0122f801321adf0ef89
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97442
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
index 80779ea..c62b040 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -509,8 +509,11 @@
           iterable: iterable);
 
   static ForElement forElement(
-          ForLoopParts forLoopParts, CollectionElement body) =>
+          ForLoopParts forLoopParts, CollectionElement body,
+          {bool hasAwait: false}) =>
       astFactory.forElement(
+          awaitKeyword:
+              hasAwait ? TokenFactory.tokenFromKeyword(Keyword.AWAIT) : null,
           forKeyword: TokenFactory.tokenFromKeyword(Keyword.FOR),
           leftParenthesis: TokenFactory.tokenFromType(TokenType.OPEN_PAREN),
           forLoopParts: forLoopParts,
diff --git a/pkg/analyzer/lib/src/summary/expr_builder.dart b/pkg/analyzer/lib/src/summary/expr_builder.dart
index 694ca03..0447c55 100644
--- a/pkg/analyzer/lib/src/summary/expr_builder.dart
+++ b/pkg/analyzer/lib/src/summary/expr_builder.dart
@@ -333,7 +333,10 @@
           _pushForParts();
           break;
         case UnlinkedExprOperation.forElement:
-          _pushForElement();
+          _pushForElement(false);
+          break;
+        case UnlinkedExprOperation.forElementWithAwait:
+          _pushForElement(true);
           break;
         case UnlinkedExprOperation.pushEmptyExpression:
           _push(null);
@@ -356,6 +359,9 @@
           identifier.staticElement = variablesInScope[name];
           _push(_createAssignment(identifier));
           break;
+        case UnlinkedExprOperation.forEachPartsWithIdentifier:
+          _forEachPartsWithIdentifier();
+          break;
         case UnlinkedExprOperation.cascadeSectionBegin:
         case UnlinkedExprOperation.cascadeSectionEnd:
         case UnlinkedExprOperation.pushLocalFunctionReference:
@@ -616,6 +622,12 @@
     return _buildIdentifierSequence(info);
   }
 
+  void _forEachPartsWithIdentifier() {
+    var iterable = _pop();
+    SimpleIdentifier identifier = _pop();
+    _pushNode(AstTestFactory.forEachPartsWithIdentifier(identifier, iterable));
+  }
+
   void _forInitializerDeclarations(bool hasType) {
     var count = _uc.ints[intPtr++];
     var variables = List<VariableDeclaration>.filled(count, null);
@@ -698,13 +710,14 @@
     _push(AstTestFactory.propertyAccess(target, propertyNode));
   }
 
-  void _pushForElement() {
+  void _pushForElement(bool hasAwait) {
     var body = _popCollectionElement();
     var forLoopParts = _popNode() as ForLoopParts;
     if (forLoopParts is ForPartsWithDeclarations) {
       variablesInScope.pop(forLoopParts.variables.variables.length);
     }
-    _pushCollectionElement(AstTestFactory.forElement(forLoopParts, body));
+    _pushCollectionElement(
+        AstTestFactory.forElement(forLoopParts, body, hasAwait: hasAwait));
   }
 
   void _pushForParts() {
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index 4cb2a21..554d4b5 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -1041,7 +1041,15 @@
   /// `value` is not present in the stack, so it should not be popped and the
   /// corresponding value of the parameter after/before update is pushed onto
   /// the stack instead.
-  assignToParameter
+  assignToParameter,
+
+  /// Pop from the stack an identifier and an expression, and create for-each
+  /// parts of the form `identifier in expression`.
+  forEachPartsWithIdentifier,
+
+  /// Pop the top 2 values from the stack.  The first is the for loop parts.
+  /// The second is the body.
+  forElementWithAwait
 }
 
 /// Enum used to indicate the kind of a parameter.
diff --git a/pkg/analyzer/lib/src/summary/idl.dart b/pkg/analyzer/lib/src/summary/idl.dart
index fd75a96..03a5457 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -3954,6 +3954,14 @@
   /// corresponding value of the parameter after/before update is pushed onto
   /// the stack instead.
   assignToParameter,
+
+  /// Pop from the stack an identifier and an expression, and create for-each
+  /// parts of the form `identifier in expression`.
+  forEachPartsWithIdentifier,
+
+  /// Pop the top 2 values from the stack.  The first is the for loop parts.
+  /// The second is the body.
+  forElementWithAwait,
 }
 
 /// Unlinked summary information about an import declaration.
diff --git a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
index ec233dd..b6dbe1c 100644
--- a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
+++ b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
@@ -571,6 +571,7 @@
         operations.add(UnlinkedExprOperation.ifElseElement);
       }
     } else if (element is ForElement) {
+      isValidConst = false;
       var parts = element.forLoopParts;
       int numVariablesToPop = 0;
       if (parts is ForParts) {
@@ -606,13 +607,23 @@
         }
         operations.add(UnlinkedExprOperation.forParts);
         ints.add(parts.updaters.length);
+      } else if (parts is ForEachParts) {
+        if (parts is ForEachPartsWithIdentifier) {
+          _serialize(parts.identifier);
+          _serialize(parts.iterable);
+          operations.add(UnlinkedExprOperation.forEachPartsWithIdentifier);
+        } else {
+          // See https://github.com/dart-lang/sdk/issues/35569
+          throw new StateError('TODO(paulberry)');
+        }
       } else {
-        // See https://github.com/dart-lang/sdk/issues/35569
-        throw new StateError('TODO(paulberry)');
+        throw StateError('Unrecognized for parts');
       }
       _serializeCollectionElement(element.body);
       popVariableNames(numVariablesToPop);
-      operations.add(UnlinkedExprOperation.forElement);
+      operations.add(element.awaitKeyword == null
+          ? UnlinkedExprOperation.forElement
+          : UnlinkedExprOperation.forElementWithAwait);
     } else {
       throw new StateError('Unsupported CollectionElement: $element');
     }
diff --git a/pkg/analyzer/test/src/summary/expr_builder_test.dart b/pkg/analyzer/test/src/summary/expr_builder_test.dart
index 5898a3f..ca5e9b4 100644
--- a/pkg/analyzer/test/src/summary/expr_builder_test.dart
+++ b/pkg/analyzer/test/src/summary/expr_builder_test.dart
@@ -303,6 +303,24 @@
         expectedText: expectedText, extraDeclarations: 'int i;');
   }
 
+  void test_list_for_each_with_identifier() {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var sourceText = '[for (i in const []) i]';
+    // Resynthesis inserts synthetic "const" tokens; work around that.
+    var expectedText = 'const $sourceText';
+    checkSimpleExpression(sourceText,
+        expectedText: expectedText, extraDeclarations: 'int i;');
+  }
+
+  void test_list_for_each_with_identifier_await() {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var sourceText = '[await for (i in const []) i]';
+    // Resynthesis inserts synthetic "const" tokens; work around that.
+    var expectedText = 'const $sourceText';
+    checkSimpleExpression(sourceText,
+        expectedText: expectedText, extraDeclarations: 'int i;');
+  }
+
   void test_list_for_empty_condition() {
     experimentStatus = ExperimentStatus(control_flow_collections: true);
     var sourceText = '[for (i = 0;; i++) i]';
diff --git a/pkg/analyzer/test/src/summary/summary_common.dart b/pkg/analyzer/test/src/summary/summary_common.dart
index 91d5349..9a374be 100644
--- a/pkg/analyzer/test/src/summary/summary_common.dart
+++ b/pkg/analyzer/test/src/summary/summary_common.dart
@@ -7692,6 +7692,61 @@
         ]);
   }
 
+  test_expr_list_for_each_with_identifier() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('int i; var v = [for (i in []) i];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[for (i in []) i]',
+        isValidConst: false,
+        operators: [
+          UnlinkedExprOperation.pushReference,
+          UnlinkedExprOperation.makeUntypedList,
+          UnlinkedExprOperation.forEachPartsWithIdentifier,
+          UnlinkedExprOperation.pushReference,
+          UnlinkedExprOperation.forElement,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, null, 'i',
+              expectedKind: ReferenceKind.topLevelPropertyAccessor),
+          (EntityRef r) => checkTypeRef(r, null, 'i',
+              expectedKind: ReferenceKind.topLevelPropertyAccessor)
+        ]);
+  }
+
+  test_expr_list_for_each_with_identifier_await() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('int i; var v = [await for (i in []) i];');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '[await for (i in []) i]',
+        isValidConst: false,
+        operators: [
+          UnlinkedExprOperation.pushReference,
+          UnlinkedExprOperation.makeUntypedList,
+          UnlinkedExprOperation.forEachPartsWithIdentifier,
+          UnlinkedExprOperation.pushReference,
+          UnlinkedExprOperation.forElementWithAwait,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, null, 'i',
+              expectedKind: ReferenceKind.topLevelPropertyAccessor),
+          (EntityRef r) => checkTypeRef(r, null, 'i',
+              expectedKind: ReferenceKind.topLevelPropertyAccessor)
+        ]);
+  }
+
   test_expr_list_for_empty_condition() {
     experimentStatus = ExperimentStatus(
         control_flow_collections: true, spread_collections: true);