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

Change-Id: I9bdc8027ce002e87e6a34b3e306f6ef27bbfac59
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97444
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/summary/expr_builder.dart b/pkg/analyzer/lib/src/summary/expr_builder.dart
index 0447c55..2fe2012 100644
--- a/pkg/analyzer/lib/src/summary/expr_builder.dart
+++ b/pkg/analyzer/lib/src/summary/expr_builder.dart
@@ -362,6 +362,12 @@
         case UnlinkedExprOperation.forEachPartsWithIdentifier:
           _forEachPartsWithIdentifier();
           break;
+        case UnlinkedExprOperation.forEachPartsWithUntypedDeclaration:
+          _forEachPartsWithDeclaration(false);
+          break;
+        case UnlinkedExprOperation.forEachPartsWithTypedDeclaration:
+          _forEachPartsWithDeclaration(true);
+          break;
         case UnlinkedExprOperation.cascadeSectionBegin:
         case UnlinkedExprOperation.cascadeSectionEnd:
         case UnlinkedExprOperation.pushLocalFunctionReference:
@@ -622,6 +628,22 @@
     return _buildIdentifierSequence(info);
   }
 
+  void _forEachPartsWithDeclaration(bool hasType) {
+    var iterable = _pop();
+    var name = _uc.strings[stringPtr++];
+    var element = LocalVariableElementImpl(name, -1);
+    var keyword = hasType ? null : Keyword.VAR;
+    var type = hasType ? _newTypeName() : null;
+    var loopVariable = AstTestFactory.declaredIdentifier2(keyword, type, name);
+    loopVariable.identifier.staticElement = element;
+    if (hasType) {
+      element.type = type.type;
+    }
+    _pushNode(
+        AstTestFactory.forEachPartsWithDeclaration(loopVariable, iterable));
+    variablesInScope.push(element);
+  }
+
   void _forEachPartsWithIdentifier() {
     var iterable = _pop();
     SimpleIdentifier identifier = _pop();
@@ -715,6 +737,8 @@
     var forLoopParts = _popNode() as ForLoopParts;
     if (forLoopParts is ForPartsWithDeclarations) {
       variablesInScope.pop(forLoopParts.variables.variables.length);
+    } else if (forLoopParts is ForEachPartsWithDeclaration) {
+      variablesInScope.pop(1);
     }
     _pushCollectionElement(
         AstTestFactory.forElement(forLoopParts, body, hasAwait: hasAwait));
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index 554d4b5..3d529d9 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -1049,7 +1049,18 @@
 
   /// Pop the top 2 values from the stack.  The first is the for loop parts.
   /// The second is the body.
-  forElementWithAwait
+  forElementWithAwait,
+
+  /// Pop an expression from the stack, and create for-each parts of the form
+  /// `var name in expression`, where `name` is obtained from
+  /// [UnlinkedExpr.strings].
+  forEachPartsWithUntypedDeclaration,
+
+  /// Pop an expression from the stack, and create for-each parts of the form
+  /// `Type name in expression`, where `name` is obtained from
+  /// [UnlinkedExpr.strings], and `Type` is obtained from
+  /// [UnlinkedExpr.references].
+  forEachPartsWithTypedDeclaration
 }
 
 /// 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 03a5457..81731be 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -3962,6 +3962,17 @@
   /// Pop the top 2 values from the stack.  The first is the for loop parts.
   /// The second is the body.
   forElementWithAwait,
+
+  /// Pop an expression from the stack, and create for-each parts of the form
+  /// `var name in expression`, where `name` is obtained from
+  /// [UnlinkedExpr.strings].
+  forEachPartsWithUntypedDeclaration,
+
+  /// Pop an expression from the stack, and create for-each parts of the form
+  /// `Type name in expression`, where `name` is obtained from
+  /// [UnlinkedExpr.strings], and `Type` is obtained from
+  /// [UnlinkedExpr.references].
+  forEachPartsWithTypedDeclaration,
 }
 
 /// Unlinked summary information about an import declaration.
diff --git a/pkg/analyzer/lib/src/summary/link.dart b/pkg/analyzer/lib/src/summary/link.dart
index 6898e4a..df84918 100644
--- a/pkg/analyzer/lib/src/summary/link.dart
+++ b/pkg/analyzer/lib/src/summary/link.dart
@@ -1913,6 +1913,7 @@
           intPtr++;
           break;
         case UnlinkedExprOperation.assignToRef:
+        case UnlinkedExprOperation.forEachPartsWithTypedDeclaration:
           refPtr++;
           break;
         case UnlinkedExprOperation.invokeMethodRef:
@@ -5255,6 +5256,7 @@
           break;
         case UnlinkedExprOperation.typeCast:
         case UnlinkedExprOperation.typeCheck:
+        case UnlinkedExprOperation.forEachPartsWithTypedDeclaration:
           refPtr++;
           break;
         case UnlinkedExprOperation.pushLocalFunctionReference:
diff --git a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
index b6dbe1c..8727ef2 100644
--- a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
+++ b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
@@ -612,9 +612,23 @@
           _serialize(parts.identifier);
           _serialize(parts.iterable);
           operations.add(UnlinkedExprOperation.forEachPartsWithIdentifier);
+        } else if (parts is ForEachPartsWithDeclaration) {
+          _serialize(parts.iterable);
+          var type = parts.loopVariable.type;
+          if (type == null) {
+            operations
+                .add(UnlinkedExprOperation.forEachPartsWithUntypedDeclaration);
+          } else {
+            references.add(serializeType(type));
+            operations
+                .add(UnlinkedExprOperation.forEachPartsWithTypedDeclaration);
+          }
+          var name = parts.loopVariable.identifier.name;
+          strings.add(name);
+          pushVariableName(name);
+          ++numVariablesToPop;
         } else {
-          // See https://github.com/dart-lang/sdk/issues/35569
-          throw new StateError('TODO(paulberry)');
+          throw StateError('Unrecognized for parts');
         }
       } else {
         throw StateError('Unrecognized for parts');
diff --git a/pkg/analyzer/test/src/summary/expr_builder_test.dart b/pkg/analyzer/test/src/summary/expr_builder_test.dart
index ca5e9b4..5a8d608 100644
--- a/pkg/analyzer/test/src/summary/expr_builder_test.dart
+++ b/pkg/analyzer/test/src/summary/expr_builder_test.dart
@@ -303,6 +303,34 @@
         expectedText: expectedText, extraDeclarations: 'int i;');
   }
 
+  void test_list_for_each_with_declaration_typed() {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var sourceText = '[for (int i in const []) i]';
+    // Resynthesis inserts synthetic "const" tokens; work around that.
+    var expectedText = 'const $sourceText';
+    var list = checkSimpleExpression(sourceText, expectedText: expectedText)
+        as ListLiteral;
+    var forElement = list.elements2[0] as ForElement;
+    var loopParts = forElement.forLoopParts as ForEachPartsWithDeclaration;
+    var iElement = loopParts.loopVariable.identifier.staticElement;
+    var iRef = forElement.body as SimpleIdentifier;
+    expect(iRef.staticElement, same(iElement));
+  }
+
+  void test_list_for_each_with_declaration_untyped() {
+    experimentStatus = ExperimentStatus(control_flow_collections: true);
+    var sourceText = '[for (var i in const []) i]';
+    // Resynthesis inserts synthetic "const" tokens; work around that.
+    var expectedText = 'const $sourceText';
+    var list = checkSimpleExpression(sourceText, expectedText: expectedText)
+        as ListLiteral;
+    var forElement = list.elements2[0] as ForElement;
+    var loopParts = forElement.forLoopParts as ForEachPartsWithDeclaration;
+    var iElement = loopParts.loopVariable.identifier.staticElement;
+    var iRef = forElement.body as SimpleIdentifier;
+    expect(iRef.staticElement, same(iElement));
+  }
+
   void test_list_for_each_with_identifier() {
     experimentStatus = ExperimentStatus(control_flow_collections: true);
     var sourceText = '[for (i in const []) i]';
diff --git a/pkg/analyzer/test/src/summary/summary_common.dart b/pkg/analyzer/test/src/summary/summary_common.dart
index 9a374be..f594ee2 100644
--- a/pkg/analyzer/test/src/summary/summary_common.dart
+++ b/pkg/analyzer/test/src/summary/summary_common.dart
@@ -7692,6 +7692,57 @@
         ]);
   }
 
+  test_expr_list_for_each_with_declaration_typed() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('var v = [for (int i in []) i];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[for (int i in []) i]',
+        isValidConst: false,
+        operators: [
+          UnlinkedExprOperation.makeUntypedList,
+          UnlinkedExprOperation.forEachPartsWithTypedDeclaration,
+          UnlinkedExprOperation.pushParameter,
+          UnlinkedExprOperation.forElement,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          0,
+          1
+        ],
+        strings: [
+          'i',
+          'i'
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int')
+        ]);
+  }
+
+  test_expr_list_for_each_with_declaration_untyped() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('var v = [for (var i in []) i];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[for (var i in []) i]',
+        isValidConst: false,
+        operators: [
+          UnlinkedExprOperation.makeUntypedList,
+          UnlinkedExprOperation.forEachPartsWithUntypedDeclaration,
+          UnlinkedExprOperation.pushParameter,
+          UnlinkedExprOperation.forElement,
+          UnlinkedExprOperation.makeUntypedList
+        ],
+        ints: [
+          0,
+          1
+        ],
+        strings: [
+          'i',
+          'i'
+        ]);
+  }
+
   test_expr_list_for_each_with_identifier() {
     experimentStatus = ExperimentStatus(
         control_flow_collections: true, spread_collections: true);