Analyzer: Fix representation of an arrow generator (illegal) function

Fixes https://github.com/dart-lang/sdk/issues/43665

A function like `foo() sync* => []` is illegal, but there is no _parse_
error, only a compile-time error. So the analyzer needs to be able to
represent this function accurately.

I think that without this fix, the formatter crashes trying to format
such a function.

Without this fix, a function like `foo() sync* => []` has a
FunctionElement with `isAsynchronous` true (because there _is_ a
modifier) and `isGenerator` false, because an arrow function cannot be
a generator. But these bool values lead to wrong errors being reported.

Change-Id: I1f7bda3696d04b437f6ea12491ca4aa19b893574
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210820
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/dart/ast/ast_factory.dart b/pkg/analyzer/lib/dart/ast/ast_factory.dart
index 15022fc..d8469f2 100644
--- a/pkg/analyzer/lib/dart/ast/ast_factory.dart
+++ b/pkg/analyzer/lib/dart/ast/ast_factory.dart
@@ -323,9 +323,22 @@
   /// Returns a newly created function body consisting of an expression.
   /// The [keyword] can be `null` if the function body is not an async function
   /// body.
+  @Deprecated("Use expressionFunctionBody2, with new 'star' parameter")
   ExpressionFunctionBody expressionFunctionBody(Token? keyword,
       Token functionDefinition, Expression expression, Token? semicolon);
 
+  /// Returns a newly created function body consisting of an expression.
+  /// The [keyword] can be `null` if the function body is not an async function
+  /// body. The [star] can be `null` if there is no star following the keyword
+  /// (and must be `null` if there is no keyword).
+  ExpressionFunctionBody expressionFunctionBody2({
+    Token? keyword,
+    Token? star,
+    required Token functionDefinition,
+    required Expression expression,
+    Token? semicolon,
+  });
+
   /// Returns a newly created expression statement.
   ExpressionStatement expressionStatement(
       Expression expression, Token? semicolon);
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 434b963..0ea9f43 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -3381,6 +3381,14 @@
   @override
   Token? keyword;
 
+  /// The star optionally following the 'async' or 'sync' keyword, or `null` if
+  /// there is wither no such keyword or no star.
+  ///
+  /// It is an error for an expression function body to feature the star, but
+  /// the parser will accept it.
+  @override
+  Token? star;
+
   /// The token introducing the expression that represents the body of the
   /// function.
   @override
@@ -3396,8 +3404,8 @@
   /// Initialize a newly created function body consisting of a block of
   /// statements. The [keyword] can be `null` if the function body is not an
   /// async function body.
-  ExpressionFunctionBodyImpl(
-      this.keyword, this.functionDefinition, this._expression, this.semicolon) {
+  ExpressionFunctionBodyImpl(this.keyword, this.star, this.functionDefinition,
+      this._expression, this.semicolon) {
     _becomeParentOf(_expression);
   }
 
@@ -3412,6 +3420,7 @@
   @override
   Iterable<SyntacticEntity> get childEntities => ChildEntities()
     ..add(keyword)
+    ..add(star)
     ..add(functionDefinition)
     ..add(_expression)
     ..add(semicolon);
@@ -3432,10 +3441,13 @@
   }
 
   @override
-  bool get isAsynchronous => keyword != null;
+  bool get isAsynchronous => keyword?.lexeme == Keyword.ASYNC.lexeme;
 
   @override
-  bool get isSynchronous => keyword == null;
+  bool get isGenerator => star != null;
+
+  @override
+  bool get isSynchronous => keyword?.lexeme != Keyword.ASYNC.lexeme;
 
   @override
   E? accept<E>(AstVisitor<E> visitor) =>
diff --git a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
index cc2e492..bc7287e 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
@@ -413,8 +413,19 @@
   @override
   ExpressionFunctionBodyImpl expressionFunctionBody(Token? keyword,
           Token functionDefinition, Expression expression, Token? semicolon) =>
-      ExpressionFunctionBodyImpl(
-          keyword, functionDefinition, expression as ExpressionImpl, semicolon);
+      ExpressionFunctionBodyImpl(keyword, null, functionDefinition,
+          expression as ExpressionImpl, semicolon);
+
+  @override
+  ExpressionFunctionBodyImpl expressionFunctionBody2({
+    Token? keyword,
+    Token? star,
+    required Token functionDefinition,
+    required Expression expression,
+    Token? semicolon,
+  }) =>
+      ExpressionFunctionBodyImpl(keyword, star, functionDefinition,
+          expression as ExpressionImpl, semicolon);
 
   @override
   ExpressionStatementImpl expressionStatement(
diff --git a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
index d8eaf00..b5929ae 100644
--- a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
@@ -333,6 +333,9 @@
     var keyword = node.keyword;
     if (keyword != null) {
       sink.write(keyword.lexeme);
+      if (node.star != null) {
+        sink.write('*');
+      }
       sink.write(' ');
     }
     sink.write('${node.functionDefinition.lexeme} ');
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 873ca31..44ea8bb 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -2665,11 +2665,16 @@
     debugEvent("ExpressionFunctionBody");
 
     var expression = pop() as Expression;
-    pop(); // star (*)
+    var star = pop() as Token?;
     var asyncKeyword = pop() as Token?;
     if (parseFunctionBodies) {
-      push(ast.expressionFunctionBody(
-          asyncKeyword, arrowToken, expression, semicolon));
+      push(ast.expressionFunctionBody2(
+        keyword: asyncKeyword,
+        star: star,
+        functionDefinition: arrowToken,
+        expression: expression,
+        semicolon: semicolon,
+      ));
     } else {
       push(ast.emptyFunctionBody(semicolon!));
     }
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 70d878c..71aab4d 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -93,11 +93,14 @@
 
   static ExpressionFunctionBodyImpl asyncExpressionFunctionBody(
           Expression expression) =>
-      astFactory.expressionFunctionBody(
-          TokenFactory.tokenFromTypeAndString(TokenType.IDENTIFIER, "async"),
-          TokenFactory.tokenFromType(TokenType.FUNCTION),
-          expression,
-          TokenFactory.tokenFromType(TokenType.SEMICOLON));
+      astFactory.expressionFunctionBody2(
+        keyword:
+            TokenFactory.tokenFromTypeAndString(TokenType.IDENTIFIER, "async"),
+        star: null,
+        functionDefinition: TokenFactory.tokenFromType(TokenType.FUNCTION),
+        expression: expression,
+        semicolon: TokenFactory.tokenFromType(TokenType.SEMICOLON),
+      );
 
   static BlockFunctionBodyImpl asyncGeneratorBlockFunctionBody(
           [List<Statement> statements = const []]) =>
@@ -106,6 +109,17 @@
           TokenFactory.tokenFromType(TokenType.STAR),
           block(statements));
 
+  static ExpressionFunctionBodyImpl asyncGeneratorExpressionFunctionBody(
+          Expression expression) =>
+      astFactory.expressionFunctionBody2(
+        keyword:
+            TokenFactory.tokenFromTypeAndString(TokenType.IDENTIFIER, "async"),
+        star: TokenFactory.tokenFromType(TokenType.STAR),
+        functionDefinition: TokenFactory.tokenFromType(TokenType.FUNCTION),
+        expression: expression,
+        semicolon: TokenFactory.tokenFromType(TokenType.SEMICOLON),
+      );
+
   static AwaitExpressionImpl awaitExpression(Expression expression) =>
       astFactory.awaitExpression(
           TokenFactory.tokenFromTypeAndString(TokenType.IDENTIFIER, "await"),
@@ -485,11 +499,13 @@
 
   static ExpressionFunctionBodyImpl expressionFunctionBody(
           Expression expression) =>
-      astFactory.expressionFunctionBody(
-          null,
-          TokenFactory.tokenFromType(TokenType.FUNCTION),
-          expression,
-          TokenFactory.tokenFromType(TokenType.SEMICOLON));
+      astFactory.expressionFunctionBody2(
+        keyword: null,
+        star: null,
+        functionDefinition: TokenFactory.tokenFromType(TokenType.FUNCTION),
+        expression: expression,
+        semicolon: TokenFactory.tokenFromType(TokenType.SEMICOLON),
+      );
 
   static ExpressionStatementImpl expressionStatement(Expression expression) =>
       astFactory.expressionStatement(
diff --git a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
index 6119dbf..88e81b9 100644
--- a/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
+++ b/pkg/analyzer/test/src/dart/ast/to_source_visitor_test.dart
@@ -826,6 +826,13 @@
             AstTestFactory.identifier3("a")));
   }
 
+  void test_visitExpressionFunctionBody_async_star() {
+    _assertSource(
+        "async* => a;",
+        AstTestFactory.asyncGeneratorExpressionFunctionBody(
+            AstTestFactory.identifier3("a")));
+  }
+
   void test_visitExpressionFunctionBody_simple() {
     _assertSource("=> a;",
         AstTestFactory.expressionFunctionBody(AstTestFactory.identifier3("a")));
diff --git a/pkg/analyzer/test/src/diagnostics/illegal_sync_generator_return_type_test.dart b/pkg/analyzer/test/src/diagnostics/illegal_sync_generator_return_type_test.dart
index 1656f94..d22ab61 100644
--- a/pkg/analyzer/test/src/diagnostics/illegal_sync_generator_return_type_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/illegal_sync_generator_return_type_test.dart
@@ -15,6 +15,14 @@
 
 @reflectiveTest
 class IllegalSyncGeneratorReturnTypeTest extends PubPackageResolutionTest {
+  test_arrowFunction_iterator() async {
+    await assertErrorsInCode('''
+Iterable<void> f() sync* => [];
+''', [
+      error(CompileTimeErrorCode.RETURN_IN_GENERATOR, 25, 2),
+    ]);
+  }
+
   test_function_iterator() async {
     await assertNoErrorsInCode('''
 Iterable<void> f() sync* {}