Add list literal spread collection parsing support

Change-Id: I96098e35baf44660b3f7a49952fde64def9998c7
Reviewed-on: https://dart-review.googlesource.com/c/89680
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 94a0fbe..2ffe897 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -46,7 +46,8 @@
         messageStaticConstructor,
         messageTypedefNotFunction,
         templateDuplicateLabelInSwitchStatement,
-        templateExpectedIdentifier;
+        templateExpectedIdentifier,
+        templateUnexpectedToken;
 import 'package:front_end/src/fasta/quote.dart';
 import 'package:front_end/src/fasta/scanner/token_constants.dart';
 import 'package:front_end/src/fasta/source/stack_listener.dart'
@@ -270,6 +271,13 @@
     scriptTag = ast.scriptTag(token);
   }
 
+  @override
+  void handleSpreadExpression(Token spreadToken) {
+    // TODO(danrubel): generate new AST structure
+    handleRecoverableError(templateUnexpectedToken.withArguments(spreadToken),
+        spreadToken, spreadToken);
+  }
+
   void handleStringJuxtaposition(int literalCount) {
     debugEvent("StringJuxtaposition");
 
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index b022212..44249b8 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -260,6 +260,30 @@
     super.test_parseUnaryExpression_decrement_super_withComment();
   }
 
+  void test_listLiteral_spread() {
+    // TODO(danrubel): Revise this test once AST supports new syntax
+    ListLiteral list = parseExpression('[1, ...[2]]', errors: [
+      expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 4, 3),
+    ]);
+    expect(list.elements, hasLength(2));
+    IntegerLiteral first = list.elements[0];
+    expect(first.value, 1);
+    ListLiteral second = list.elements[1];
+    expect(second.elements, hasLength(1));
+  }
+
+  void test_listLiteral_spreadQ() {
+    // TODO(danrubel): Revise this test once AST supports new syntax
+    ListLiteral list = parseExpression('[1, ...?[2]]', errors: [
+      expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 4, 4),
+    ]);
+    expect(list.elements, hasLength(2));
+    IntegerLiteral first = list.elements[0];
+    expect(first.value, 1);
+    ListLiteral second = list.elements[1];
+    expect(second.elements, hasLength(1));
+  }
+
   void test_mapLiteral() {
     MapLiteral map = parseExpression('{3: 6}', parseSetLiterals: true);
     expect(map.constKeyword, isNull);
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index d023620..ae4004e 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -3519,6 +3519,16 @@
   }
 
   @override
+  void handleSpreadExpression(Token spreadToken) {
+    debugEvent("SpreadExpression");
+    // TODO(danrubel) implement spread expression support
+    handleRecoverableError(
+        fasta.templateUnexpectedToken.withArguments(spreadToken),
+        spreadToken,
+        spreadToken);
+  }
+
+  @override
   void endTypeArguments(int count, Token beginToken, Token endToken) {
     debugEvent("TypeArguments");
     push(const FixedNullableList<UnresolvedType<KernelTypeBuilder>>()
diff --git a/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart b/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
index 51e3b9e..f447023 100644
--- a/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
@@ -1308,6 +1308,11 @@
   }
 
   @override
+  void handleSpreadExpression(Token spreadToken) {
+    listener?.handleSpreadExpression(spreadToken);
+  }
+
+  @override
   void handleStringJuxtaposition(int literalCount) {
     listener?.handleStringJuxtaposition(literalCount);
   }
diff --git a/pkg/front_end/lib/src/fasta/parser/listener.dart b/pkg/front_end/lib/src/fasta/parser/listener.dart
index d681804..dc31098 100644
--- a/pkg/front_end/lib/src/fasta/parser/listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/listener.dart
@@ -1074,6 +1074,13 @@
     logEvent("ConstExpression");
   }
 
+  /// Called after the parser has parsed an expression that starts with
+  /// one of the spread collection tokens `...` or `...?`.  Substructures:
+  /// - expression
+  void handleSpreadExpression(Token spreadToken) {
+    logEvent("SpreadExpression");
+  }
+
   /// Handle the start of a function typed formal parameter.  Substructures:
   /// - type variables
   void beginFunctionTypedFormalParameter(Token token) {}
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index a3a59c2..6ab177a 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -4186,7 +4186,12 @@
         token = next;
         break;
       }
-      token = parseExpression(token);
+      if (optional('...', next) || optional('...?', next)) {
+        token = parseExpression(token.next);
+        listener.handleSpreadExpression(next);
+      } else {
+        token = parseExpression(token);
+      }
       next = token.next;
       ++count;
       if (!optional(',', next)) {
diff --git a/pkg/front_end/lib/src/fasta/source/type_promotion_look_ahead_listener.dart b/pkg/front_end/lib/src/fasta/source/type_promotion_look_ahead_listener.dart
index 5ba2c51..97611a1 100644
--- a/pkg/front_end/lib/src/fasta/source/type_promotion_look_ahead_listener.dart
+++ b/pkg/front_end/lib/src/fasta/source/type_promotion_look_ahead_listener.dart
@@ -376,6 +376,12 @@
     doConstuctorInvocation(token, true);
   }
 
+  @override
+  void handleSpreadExpression(Token spreadToken) {
+    // TODO(danrubel) add support for spread collections
+    // but for now this is ignored and an error reported in the body builder
+  }
+
   void doConstuctorInvocation(Token token, bool isConst) {
     state.pop(); // Arguments.
     state.popPushNull(token.lexeme, token); // Constructor reference.