Add summary support for spread elements.

Change-Id: I5c306225aacca997ef104e7a2b18001dab38201d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96966
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 5d1c4d6..2cd3ae4 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -817,7 +817,7 @@
 
   static ListLiteral listLiteral2(
           Keyword keyword, TypeArgumentList typeArguments,
-          [List<Expression> elements]) =>
+          [List<CollectionElement> elements]) =>
       astFactory.listLiteral(
           keyword == null ? null : TokenFactory.tokenFromKeyword(keyword),
           typeArguments,
@@ -1126,6 +1126,12 @@
           TypeAnnotation type, String parameterName) =>
       simpleFormalParameter2(null, type, parameterName);
 
+  static SpreadElement spreadElement(
+          TokenType operator, Expression expression) =>
+      astFactory.spreadElement(
+          spreadOperator: TokenFactory.tokenFromType(operator),
+          expression: expression);
+
   static StringInterpolation string([List<InterpolationElement> elements]) =>
       astFactory.stringInterpolation(elements);
 
diff --git a/pkg/analyzer/lib/src/summary/expr_builder.dart b/pkg/analyzer/lib/src/summary/expr_builder.dart
index 8ef62ae..629798e 100644
--- a/pkg/analyzer/lib/src/summary/expr_builder.dart
+++ b/pkg/analyzer/lib/src/summary/expr_builder.dart
@@ -17,7 +17,7 @@
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary/resynthesize.dart';
 
-bool _isSetOrMapEnabled(ExperimentStatus experimentStatus) =>
+bool _isSpreadOrControlFlowEnabled(ExperimentStatus experimentStatus) =>
     experimentStatus.spread_collections ||
     experimentStatus.control_flow_collections;
 
@@ -31,7 +31,7 @@
   final ElementImpl context;
   final UnlinkedExpr _uc;
   final bool requireValidConst;
-  final bool useSetOrMap;
+  final bool isSpreadOrControlFlowEnabled;
   final bool becomeSetOrMap;
 
   int intPtr = 0;
@@ -58,9 +58,10 @@
     this.becomeSetOrMap: true,
   })  : this.parametersInScope =
             parametersInScope ?? _parametersInScope(context),
-        this.useSetOrMap = _isSetOrMapEnabled((resynthesizer
-                .library.context.analysisOptions as AnalysisOptionsImpl)
-            .experimentStatus);
+        this.isSpreadOrControlFlowEnabled = _isSpreadOrControlFlowEnabled(
+            (resynthesizer.library.context.analysisOptions
+                    as AnalysisOptionsImpl)
+                .experimentStatus);
 
   bool get hasNonEmptyExpr => _uc != null && _uc.operations.isNotEmpty;
 
@@ -239,7 +240,7 @@
           break;
         case UnlinkedExprOperation.makeTypedSet:
           TypeAnnotation itemType = _newTypeName();
-          if (useSetOrMap) {
+          if (isSpreadOrControlFlowEnabled) {
             _pushSetOrMap(
                 AstTestFactory.typeArgumentList(<TypeAnnotation>[itemType]));
           } else {
@@ -316,6 +317,12 @@
         case UnlinkedExprOperation.pushThis:
           _push(AstTestFactory.thisExpression());
           break;
+        case UnlinkedExprOperation.spreadElement:
+          _pushSpread(TokenType.PERIOD_PERIOD_PERIOD);
+          break;
+        case UnlinkedExprOperation.nullAwareSpreadElement:
+          _pushSpread(TokenType.PERIOD_PERIOD_PERIOD_QUESTION);
+          break;
         case UnlinkedExprOperation.cascadeSectionBegin:
         case UnlinkedExprOperation.cascadeSectionEnd:
         case UnlinkedExprOperation.pushLocalFunctionReference:
@@ -618,7 +625,7 @@
 
   CollectionElement _popCollectionElement() => stack.removeLast();
 
-  void _push(Expression expr) {
+  void _push(CollectionElement expr) {
     stack.add(expr);
   }
 
@@ -755,9 +762,10 @@
 
   void _pushList(TypeArgumentList typeArguments) {
     int count = _uc.ints[intPtr++];
-    List<Expression> elements = <Expression>[];
+    List<CollectionElement> elements =
+        isSpreadOrControlFlowEnabled ? <CollectionElement>[] : <Expression>[];
     for (int i = 0; i < count; i++) {
-      elements.insert(0, _pop());
+      elements.insert(0, _popCollectionElement());
     }
     var typeArg = typeArguments == null
         ? resynthesizer.typeProvider.dynamicType
@@ -830,7 +838,7 @@
         : typeArguments.arguments[1].type;
     var staticType =
         resynthesizer.typeProvider.mapType.instantiate([keyType, valueType]);
-    if (useSetOrMap) {
+    if (isSpreadOrControlFlowEnabled) {
       _push(
           AstTestFactory.setOrMapLiteral(Keyword.CONST, typeArguments, entries)
             ..staticType = staticType);
@@ -915,6 +923,11 @@
     _push(setOrMapLiteral..staticType = staticType);
   }
 
+  void _pushSpread(TokenType operator) {
+    Expression operand = _pop();
+    _push(AstTestFactory.spreadElement(operator, operand));
+  }
+
   List<Expression> _removeTopExpressions(int count) {
     int start = stack.length - count;
     int end = stack.length;
diff --git a/pkg/analyzer/lib/src/summary/format.fbs b/pkg/analyzer/lib/src/summary/format.fbs
index 29ee198..6745d48 100644
--- a/pkg/analyzer/lib/src/summary/format.fbs
+++ b/pkg/analyzer/lib/src/summary/format.fbs
@@ -972,7 +972,15 @@
 
   /// Pop the top 2 values from the stack, place them in a [MapLiteralEntry],
   /// and push the result back onto the stack.
-  makeMapLiteralEntry
+  makeMapLiteralEntry,
+
+  /// Pop the top value from the stack, convert it to a spread element of type
+  /// `...`, and push the result back onto the stack.
+  spreadElement,
+
+  /// Pop the top value from the stack, convert it to a spread element of type
+  /// `...?`, and push the result back onto the stack.
+  nullAwareSpreadElement
 }
 
 /// 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 c97cf91..f000abb 100644
--- a/pkg/analyzer/lib/src/summary/idl.dart
+++ b/pkg/analyzer/lib/src/summary/idl.dart
@@ -3868,6 +3868,14 @@
   /// Pop the top 2 values from the stack, place them in a [MapLiteralEntry],
   /// and push the result back onto the stack.
   makeMapLiteralEntry,
+
+  /// Pop the top value from the stack, convert it to a spread element of type
+  /// `...`, and push the result back onto the stack.
+  spreadElement,
+
+  /// Pop the top value from the stack, convert it to a spread element of type
+  /// `...?`, and push the result back onto the stack.
+  nullAwareSpreadElement,
 }
 
 /// 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 056a4bf..71af96b 100644
--- a/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
+++ b/pkg/analyzer/lib/src/summary/summarize_const_expr.dart
@@ -541,6 +541,13 @@
       _serialize(element.key);
       _serialize(element.value);
       operations.add(UnlinkedExprOperation.makeMapLiteralEntry);
+    } else if (element is SpreadElement) {
+      _serialize(element.expression);
+      bool isNullAware = element.spreadOperator.type ==
+          TokenType.PERIOD_PERIOD_PERIOD_QUESTION;
+      operations.add(isNullAware
+          ? UnlinkedExprOperation.nullAwareSpreadElement
+          : UnlinkedExprOperation.spreadElement);
     } else {
       // TODO(paulberry): Implement serialization for spread and control flow
       //  elements.
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
index b7a7b5c..6813ec5 100644
--- a/pkg/analyzer/test/src/summary/element_text.dart
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -276,7 +276,7 @@
 
     if (e is ConstructorElementImpl) {
       if (e.constantInitializers != null) {
-        writeList(' : ', '', e.constantInitializers, ', ', writeExpression);
+        writeList(' : ', '', e.constantInitializers, ', ', writeNode);
       }
     }
 
@@ -307,209 +307,6 @@
     buffer.writeln(';');
   }
 
-  void writeExpression(AstNode e, [Expression enclosing]) {
-    bool needsParenthesis = e is Expression &&
-        enclosing != null &&
-        e.precedence2 < enclosing.precedence2;
-
-    if (needsParenthesis) {
-      buffer.write('(');
-    }
-
-    if (e == null) {
-      buffer.write('<null>');
-    } else if (e is SimpleIdentifier && e.name == '#invalidConst') {
-      buffer.write('#invalidConst');
-    } else if (e is AdjacentStrings) {
-      writeList("'", "'", e.strings, '',
-          (StringLiteral s) => buffer.write(s.stringValue),
-          includeEmpty: true);
-    } else if (e is Annotation) {
-      buffer.write('@');
-      writeExpression(e.name);
-      if (e.constructorName != null) {
-        buffer.write('.');
-        writeExpression(e.constructorName);
-      }
-      if (e.arguments != null) {
-        writeList('(', ')', e.arguments.arguments, ', ', writeExpression,
-            includeEmpty: true);
-      }
-    } else if (e is AssertInitializer) {
-      buffer.write('assert(');
-      writeExpression(e.condition);
-      if (e.message != null) {
-        buffer.write(', ');
-        writeExpression(e.message);
-      }
-      buffer.write(')');
-    } else if (e is BinaryExpression) {
-      writeExpression(e.leftOperand, e);
-      buffer.write(' ');
-      buffer.write(e.operator.lexeme);
-      buffer.write(' ');
-      writeExpression(e.rightOperand, e);
-    } else if (e is BooleanLiteral) {
-      buffer.write(e.value);
-    } else if (e is ConditionalExpression) {
-      writeExpression(e.condition);
-      buffer.write(' ? ');
-      writeExpression(e.thenExpression);
-      buffer.write(' : ');
-      writeExpression(e.elseExpression);
-    } else if (e is ConstructorFieldInitializer) {
-      writeExpression(e.fieldName);
-      buffer.write(' = ');
-      writeExpression(e.expression);
-    } else if (e is ConstructorName) {
-      writeExpression(e.type);
-      if (e.name != null) {
-        buffer.write('.');
-        writeExpression(e.name);
-      }
-    } else if (e is DoubleLiteral) {
-      buffer.write(e.value);
-    } else if (e is InstanceCreationExpression) {
-      if (e.keyword != null) {
-        buffer.write(e.keyword.lexeme);
-        buffer.write(' ');
-      }
-      writeExpression(e.constructorName);
-      writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
-          includeEmpty: true);
-    } else if (e is IntegerLiteral) {
-      buffer.write(e.value);
-    } else if (e is InterpolationExpression) {
-      buffer.write(r'${');
-      writeExpression(e.expression);
-      buffer.write(r'}');
-    } else if (e is InterpolationString) {
-      buffer.write(e.value.replaceAll("'", r"\'"));
-    } else if (e is ListLiteral) {
-      if (e.constKeyword != null) {
-        buffer.write('const ');
-      }
-      if (e.typeArguments != null) {
-        writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
-      } else if (withTypes) {
-        writeInterfaceTypeArgsComment(e);
-      }
-      writeList('[', ']', e.elements2, ', ', writeExpression,
-          includeEmpty: true);
-    } else if (e is Label) {
-      writeExpression(e.label);
-      buffer.write(': ');
-    } else if (e is SetOrMapLiteral) {
-      if (e.constKeyword != null) {
-        buffer.write('const ');
-      }
-      if (e.typeArguments != null) {
-        writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
-      } else if (withTypes) {
-        writeInterfaceTypeArgsComment(e);
-      }
-      writeList('{', '}', e.elements2, ', ', writeExpression,
-          includeEmpty: true);
-      if (e.isMap) {
-        buffer.write('/*isMap*/');
-      }
-      if (e.isSet) {
-        buffer.write('/*isSet*/');
-      }
-    } else if (e is MapLiteralEntry) {
-      writeExpression(e.key);
-      buffer.write(': ');
-      writeExpression(e.value);
-    } else if (e is MethodInvocation) {
-      if (e.target != null) {
-        writeExpression(e.target);
-        buffer.write(e.operator);
-      }
-      writeExpression(e.methodName);
-      if (e.typeArguments != null) {
-        writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
-      }
-      writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
-          includeEmpty: true);
-    } else if (e is NamedExpression) {
-      writeExpression(e.name);
-      buffer.write(e.expression);
-    } else if (e is NullLiteral) {
-      buffer.write('null');
-    } else if (e is ParenthesizedExpression) {
-      writeExpression(e.expression, e);
-    } else if (e is PrefixExpression) {
-      buffer.write(e.operator.lexeme);
-      writeExpression(e.operand, e);
-    } else if (e is PrefixedIdentifier) {
-      writeExpression(e.prefix);
-      buffer.write('.');
-      writeExpression(e.identifier);
-    } else if (e is PropertyAccess) {
-      writeExpression(e.target, e);
-      buffer.write('.');
-      writeExpression(e.propertyName);
-    } else if (e is RedirectingConstructorInvocation) {
-      buffer.write('this');
-      if (e.constructorName != null) {
-        buffer.write('.');
-        writeExpression(e.constructorName);
-      }
-      writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
-          includeEmpty: true);
-    } else if (e is SimpleIdentifier) {
-      if (withConstElements) {
-        buffer.writeln();
-        buffer.write('  ' * 4);
-        buffer.write(e.name);
-        buffer.write('/*');
-        buffer.write('location: ');
-        buffer.write(_getElementLocationString(e.staticElement));
-        buffer.write('*/');
-      } else {
-        buffer.write(e.name);
-      }
-    } else if (e is SimpleStringLiteral) {
-      buffer.write("'");
-      buffer.write(e.value.replaceAll("'", r"\'"));
-      buffer.write("'");
-    } else if (e is StringInterpolation) {
-      buffer.write("'");
-      e.elements.forEach(writeExpression);
-      buffer.write("'");
-    } else if (e is SuperConstructorInvocation) {
-      buffer.write('super');
-      if (e.constructorName != null) {
-        buffer.write('.');
-        writeExpression(e.constructorName);
-      }
-      writeList('(', ')', e.argumentList.arguments, ', ', writeExpression,
-          includeEmpty: true);
-    } else if (e is SuperExpression) {
-      buffer.write('super');
-    } else if (e is SymbolLiteral) {
-      buffer.write('#');
-      writeList('', '', e.components, '.',
-          (Token token) => buffer.write(token.lexeme));
-    } else if (e is ThisExpression) {
-      buffer.write('this');
-    } else if (e is ThrowExpression) {
-      buffer.write('throw ');
-      writeExpression(e.expression);
-    } else if (e is TypeName) {
-      writeExpression(e.name);
-      if (e.typeArguments != null) {
-        writeList('<', '>', e.typeArguments.arguments, ', ', writeExpression);
-      }
-    } else {
-      fail('Unsupported expression type: ${e.runtimeType}');
-    }
-
-    if (needsParenthesis) {
-      buffer.write(')');
-    }
-  }
-
   void writeFunctionElement(FunctionElement e) {
     writeDocumentation(e);
     writeMetadata(e, '', '\n');
@@ -631,7 +428,7 @@
   void writeMetadata(Element e, String prefix, String separator) {
     if (e.metadata.isNotEmpty) {
       writeList(prefix, '', e.metadata, '$separator$prefix', (a) {
-        writeExpression((a as ElementAnnotationImpl).annotationAst);
+        writeNode((a as ElementAnnotationImpl).annotationAst);
       });
       buffer.write(separator);
     }
@@ -681,6 +478,210 @@
     }
   }
 
+  void writeNode(AstNode e, [Expression enclosing]) {
+    bool needsParenthesis = e is Expression &&
+        enclosing != null &&
+        e.precedence2 < enclosing.precedence2;
+
+    if (needsParenthesis) {
+      buffer.write('(');
+    }
+
+    if (e == null) {
+      buffer.write('<null>');
+    } else if (e is SimpleIdentifier && e.name == '#invalidConst') {
+      buffer.write('#invalidConst');
+    } else if (e is AdjacentStrings) {
+      writeList("'", "'", e.strings, '',
+          (StringLiteral s) => buffer.write(s.stringValue),
+          includeEmpty: true);
+    } else if (e is Annotation) {
+      buffer.write('@');
+      writeNode(e.name);
+      if (e.constructorName != null) {
+        buffer.write('.');
+        writeNode(e.constructorName);
+      }
+      if (e.arguments != null) {
+        writeList('(', ')', e.arguments.arguments, ', ', writeNode,
+            includeEmpty: true);
+      }
+    } else if (e is AssertInitializer) {
+      buffer.write('assert(');
+      writeNode(e.condition);
+      if (e.message != null) {
+        buffer.write(', ');
+        writeNode(e.message);
+      }
+      buffer.write(')');
+    } else if (e is BinaryExpression) {
+      writeNode(e.leftOperand, e);
+      buffer.write(' ');
+      buffer.write(e.operator.lexeme);
+      buffer.write(' ');
+      writeNode(e.rightOperand, e);
+    } else if (e is BooleanLiteral) {
+      buffer.write(e.value);
+    } else if (e is ConditionalExpression) {
+      writeNode(e.condition);
+      buffer.write(' ? ');
+      writeNode(e.thenExpression);
+      buffer.write(' : ');
+      writeNode(e.elseExpression);
+    } else if (e is ConstructorFieldInitializer) {
+      writeNode(e.fieldName);
+      buffer.write(' = ');
+      writeNode(e.expression);
+    } else if (e is ConstructorName) {
+      writeNode(e.type);
+      if (e.name != null) {
+        buffer.write('.');
+        writeNode(e.name);
+      }
+    } else if (e is DoubleLiteral) {
+      buffer.write(e.value);
+    } else if (e is InstanceCreationExpression) {
+      if (e.keyword != null) {
+        buffer.write(e.keyword.lexeme);
+        buffer.write(' ');
+      }
+      writeNode(e.constructorName);
+      writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
+          includeEmpty: true);
+    } else if (e is IntegerLiteral) {
+      buffer.write(e.value);
+    } else if (e is InterpolationExpression) {
+      buffer.write(r'${');
+      writeNode(e.expression);
+      buffer.write(r'}');
+    } else if (e is InterpolationString) {
+      buffer.write(e.value.replaceAll("'", r"\'"));
+    } else if (e is ListLiteral) {
+      if (e.constKeyword != null) {
+        buffer.write('const ');
+      }
+      if (e.typeArguments != null) {
+        writeList('<', '>', e.typeArguments.arguments, ', ', writeNode);
+      } else if (withTypes) {
+        writeInterfaceTypeArgsComment(e);
+      }
+      writeList('[', ']', e.elements2, ', ', writeNode, includeEmpty: true);
+    } else if (e is Label) {
+      writeNode(e.label);
+      buffer.write(': ');
+    } else if (e is SetOrMapLiteral) {
+      if (e.constKeyword != null) {
+        buffer.write('const ');
+      }
+      if (e.typeArguments != null) {
+        writeList('<', '>', e.typeArguments.arguments, ', ', writeNode);
+      } else if (withTypes) {
+        writeInterfaceTypeArgsComment(e);
+      }
+      writeList('{', '}', e.elements2, ', ', writeNode, includeEmpty: true);
+      if (e.isMap) {
+        buffer.write('/*isMap*/');
+      }
+      if (e.isSet) {
+        buffer.write('/*isSet*/');
+      }
+    } else if (e is MapLiteralEntry) {
+      writeNode(e.key);
+      buffer.write(': ');
+      writeNode(e.value);
+    } else if (e is MethodInvocation) {
+      if (e.target != null) {
+        writeNode(e.target);
+        buffer.write(e.operator);
+      }
+      writeNode(e.methodName);
+      if (e.typeArguments != null) {
+        writeList('<', '>', e.typeArguments.arguments, ', ', writeNode);
+      }
+      writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
+          includeEmpty: true);
+    } else if (e is NamedExpression) {
+      writeNode(e.name);
+      buffer.write(e.expression);
+    } else if (e is NullLiteral) {
+      buffer.write('null');
+    } else if (e is ParenthesizedExpression) {
+      writeNode(e.expression, e);
+    } else if (e is PrefixExpression) {
+      buffer.write(e.operator.lexeme);
+      writeNode(e.operand, e);
+    } else if (e is PrefixedIdentifier) {
+      writeNode(e.prefix);
+      buffer.write('.');
+      writeNode(e.identifier);
+    } else if (e is PropertyAccess) {
+      writeNode(e.target, e);
+      buffer.write('.');
+      writeNode(e.propertyName);
+    } else if (e is RedirectingConstructorInvocation) {
+      buffer.write('this');
+      if (e.constructorName != null) {
+        buffer.write('.');
+        writeNode(e.constructorName);
+      }
+      writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
+          includeEmpty: true);
+    } else if (e is SimpleIdentifier) {
+      if (withConstElements) {
+        buffer.writeln();
+        buffer.write('  ' * 4);
+        buffer.write(e.name);
+        buffer.write('/*');
+        buffer.write('location: ');
+        buffer.write(_getElementLocationString(e.staticElement));
+        buffer.write('*/');
+      } else {
+        buffer.write(e.name);
+      }
+    } else if (e is SimpleStringLiteral) {
+      buffer.write("'");
+      buffer.write(e.value.replaceAll("'", r"\'"));
+      buffer.write("'");
+    } else if (e is StringInterpolation) {
+      buffer.write("'");
+      e.elements.forEach(writeNode);
+      buffer.write("'");
+    } else if (e is SuperConstructorInvocation) {
+      buffer.write('super');
+      if (e.constructorName != null) {
+        buffer.write('.');
+        writeNode(e.constructorName);
+      }
+      writeList('(', ')', e.argumentList.arguments, ', ', writeNode,
+          includeEmpty: true);
+    } else if (e is SuperExpression) {
+      buffer.write('super');
+    } else if (e is SymbolLiteral) {
+      buffer.write('#');
+      writeList('', '', e.components, '.',
+          (Token token) => buffer.write(token.lexeme));
+    } else if (e is ThisExpression) {
+      buffer.write('this');
+    } else if (e is ThrowExpression) {
+      buffer.write('throw ');
+      writeNode(e.expression);
+    } else if (e is TypeName) {
+      writeNode(e.name);
+      if (e.typeArguments != null) {
+        writeList('<', '>', e.typeArguments.arguments, ', ', writeNode);
+      }
+    } else if (e is SpreadElement) {
+      buffer.write(e.spreadOperator.lexeme);
+      writeNode(e.expression);
+    } else {
+      fail('Unsupported expression type: ${e.runtimeType}');
+    }
+
+    if (needsParenthesis) {
+      buffer.write(')');
+    }
+  }
+
   void writeParameterElement(ParameterElement e) {
     String defaultValueSeparator;
     Expression defaultValue;
@@ -729,7 +730,7 @@
 
     if (defaultValue != null) {
       buffer.write(defaultValueSeparator);
-      writeExpression(defaultValue);
+      writeNode(defaultValue);
     }
 
     buffer.write(closeString);
@@ -867,7 +868,7 @@
       Expression initializer = (e as ConstVariableElement).constantInitializer;
       if (initializer != null) {
         buffer.write(' = ');
-        writeExpression(initializer);
+        writeNode(initializer);
       }
     }
 
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 139ad19..d90e62a 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -9,8 +9,8 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/context/context.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/dart/element/element.dart';
-import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/summary/idl.dart';
 import 'package:analyzer/src/summary/resynthesize.dart';
@@ -30,7 +30,6 @@
  */
 abstract class AbstractResynthesizeTest with ResourceProviderMixin {
   DeclaredVariables declaredVariables = new DeclaredVariables();
-  AnalysisOptionsImpl analysisOptions = AnalysisOptionsImpl();
   SourceFactory sourceFactory;
   MockSdk sdk;
 
@@ -2579,6 +2578,52 @@
     }
   }
 
+  test_const_list_spread() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>[...<int>[1]];
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/>[...<
+        int/*location: dart:core;int*/>[1]];
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/>[...const <
+        int/*location: dart:core;int*/>[1]];
+''');
+    }
+  }
+
+  test_const_list_spread_null_aware() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>[...?<int>[1]];
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/>[...?<
+        int/*location: dart:core;int*/>[1]];
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/>[...?const <
+        int/*location: dart:core;int*/>[1]];
+''');
+    }
+  }
+
   test_const_map_inferredType() async {
     // The summary needs to contain enough information so that when the constant
     // is resynthesized, the constant value can get the type that was computed
@@ -2602,6 +2647,60 @@
     }
   }
 
+  test_const_map_spread() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int, int>{...<int, int>{1: 2}};
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{...<
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{1: 2}/*isMap*/}/*isMap*/;
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{...const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{1: 2}/*isMap*/}/*isMap*/;
+''');
+    }
+  }
+
+  test_const_map_spread_null_aware() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int, int>{...?<int, int>{1: 2}};
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{...?<
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{1: 2}/*isMap*/}/*isMap*/;
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{...?const <
+        int/*location: dart:core;int*/,
+        int/*location: dart:core;int*/>{1: 2}/*isMap*/}/*isMap*/;
+''');
+    }
+  }
+
   test_const_parameterDefaultValue_initializingFormal_functionTyped() async {
     var library = await checkLibrary(r'''
 class C {
@@ -3064,6 +3163,52 @@
     }
   }
 
+  test_const_set_spread() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>{...<int>{1}};
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/>{...<
+        int/*location: dart:core;int*/>{1}/*isSet*/}/*isSet*/;
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/>{...const <
+        int/*location: dart:core;int*/>{1}/*isSet*/}/*isSet*/;
+''');
+    }
+  }
+
+  test_const_set_spread_null_aware() async {
+    experimentStatus = ExperimentStatus(spread_collections: true);
+    var library = await checkLibrary('''
+const Object x = const <int>{...?<int>{1}};
+''');
+    if (isAstBasedSummary) {
+      checkElementText(
+          library,
+          '''
+const Object x = const <
+        int/*location: dart:core;int*/>{...?<
+        int/*location: dart:core;int*/>{1}/*isSet*/}/*isSet*/;
+''',
+          withTypes: true);
+    } else {
+      checkElementText(library, '''
+const Object x = const <
+        int/*location: dart:core;int*/>{...?const <
+        int/*location: dart:core;int*/>{1}/*isSet*/}/*isSet*/;
+''');
+    }
+  }
+
   test_const_topLevel_binary() async {
     var library = await checkLibrary(r'''
 const vEqual = 1 == 2;
diff --git a/pkg/analyzer/test/src/summary/summary_common.dart b/pkg/analyzer/test/src/summary/summary_common.dart
index b45006a..c69533d 100644
--- a/pkg/analyzer/test/src/summary/summary_common.dart
+++ b/pkg/analyzer/test/src/summary/summary_common.dart
@@ -2754,6 +2754,34 @@
         ]);
   }
 
+  test_constExpr_list_spread() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable = serializeVariableText('const v = [...[]];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[...[]]', operators: [
+      UnlinkedExprOperation.makeUntypedList,
+      UnlinkedExprOperation.spreadElement,
+      UnlinkedExprOperation.makeUntypedList
+    ], ints: [
+      0,
+      1
+    ]);
+  }
+
+  test_constExpr_list_spread_null_aware() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable = serializeVariableText('const v = [...?[]];');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '[...?[]]', operators: [
+      UnlinkedExprOperation.makeUntypedList,
+      UnlinkedExprOperation.nullAwareSpreadElement,
+      UnlinkedExprOperation.makeUntypedList
+    ], ints: [
+      0,
+      1
+    ]);
+  }
+
   test_constExpr_makeSymbol() {
     UnlinkedVariable variable = serializeVariableText('const v = #a.bb.ccc;');
     assertUnlinkedConst(variable.initializer.bodyExpr, '#a.bb.ccc',
@@ -3123,6 +3151,62 @@
     );
   }
 
+  test_constExpr_map_spread() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int, String>{...<int, String>{}};');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '<int, String>{...<int, String>{}}',
+        operators: [
+          UnlinkedExprOperation.makeTypedMap2,
+          UnlinkedExprOperation.spreadElement,
+          UnlinkedExprOperation.makeTypedMap2
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'String',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'String',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
+  test_constExpr_map_spread_null_aware() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int, String>{...?<int, String>{}};');
+    assertUnlinkedConst(
+        variable.initializer.bodyExpr, '<int, String>{...?<int, String>{}}',
+        operators: [
+          UnlinkedExprOperation.makeTypedMap2,
+          UnlinkedExprOperation.nullAwareSpreadElement,
+          UnlinkedExprOperation.makeTypedMap2
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'String',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'String',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
   test_constExpr_parenthesized() {
     UnlinkedVariable variable = serializeVariableText('const v = (1 + 2) * 3;');
     assertUnlinkedConst(variable.initializer.bodyExpr, '(1 + 2) * 3',
@@ -3841,6 +3925,52 @@
         operators: [UnlinkedExprOperation.pushTrue]);
   }
 
+  test_constExpr_set_spread() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int>{...<int>{}};');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '<int>{...<int>{}}',
+        operators: [
+          UnlinkedExprOperation.makeTypedSet,
+          UnlinkedExprOperation.spreadElement,
+          UnlinkedExprOperation.makeTypedSet
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
+  test_constExpr_set_spread_null_aware() {
+    experimentStatus = ExperimentStatus(
+        control_flow_collections: true, spread_collections: true);
+    UnlinkedVariable variable =
+        serializeVariableText('const v = <int>{...?<int>{}};');
+    assertUnlinkedConst(variable.initializer.bodyExpr, '<int>{...?<int>{}}',
+        operators: [
+          UnlinkedExprOperation.makeTypedSet,
+          UnlinkedExprOperation.nullAwareSpreadElement,
+          UnlinkedExprOperation.makeTypedSet
+        ],
+        ints: [
+          0,
+          1
+        ],
+        referenceValidators: [
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum),
+          (EntityRef r) => checkTypeRef(r, 'dart:core', 'int',
+              expectedKind: ReferenceKind.classOrEnum)
+        ]);
+  }
+
   test_constructor() {
     String text = 'class C { C(); }';
     UnlinkedExecutable executable =
diff --git a/pkg/analyzer/test/src/summary/test_strategies.dart b/pkg/analyzer/test/src/summary/test_strategies.dart
index bfbe9fe..eed8b58 100644
--- a/pkg/analyzer/test/src/summary/test_strategies.dart
+++ b/pkg/analyzer/test/src/summary/test_strategies.dart
@@ -43,9 +43,11 @@
   Scanner scanner =
       new Scanner(null, reader, AnalysisErrorListener.NULL_LISTENER);
   Token token = scanner.tokenize();
-  Parser parser =
-      new Parser(NonExistingSource.unknown, AnalysisErrorListener.NULL_LISTENER)
-        ..enableNonNullable = experimentStatus.non_nullable;
+  Parser parser = new Parser(
+      NonExistingSource.unknown, AnalysisErrorListener.NULL_LISTENER)
+    ..enableNonNullable = experimentStatus.non_nullable
+    ..enableSpreadCollections = experimentStatus.spread_collections
+    ..enableControlFlowCollections = experimentStatus.control_flow_collections;
   CompilationUnit unit = parser.parseCompilationUnit(token);
   unit.lineInfo = new LineInfo(scanner.lineStarts);
   return unit;
@@ -175,6 +177,9 @@
         .map((Source source) => source.uri.toString())
         .toSet();
 
+    var analysisOptions = AnalysisOptionsImpl()
+      ..enabledExperiments = experimentStatus.toStringList();
+
     Map<String, LinkedLibrary> linkedSummaries = link(nonSdkLibraryUris,
         getDependency, getUnit, declaredVariables, analysisOptions);
 
@@ -222,7 +227,8 @@
         contents = '';
       }
 
-      CompilationUnit unit = parseText(contents);
+      CompilationUnit unit =
+          parseText(contents, experimentStatus: experimentStatus);
 
       UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit);
       bundleAssembler.addUnlinkedUnit(source, unlinkedUnit);