rework parsing local variable declaration with nullable type

... and address comment in https://dart-review.googlesource.com/c/sdk/+/88301

Change-Id: I96c9bcf995fad58403ab455e907843a40339ef84
Reviewed-on: https://dart-review.googlesource.com/c/88303
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Dan Rubel <danrubel@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index 9e2ad74..827110a 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -1443,6 +1443,47 @@
     return unit;
   }
 
+  void test_assignment_complex() {
+    parseNNBDCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x + bar(7); }');
+  }
+
+  void test_assignment_simple() {
+    parseNNBDCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
+  }
+
+  void test_conditional() {
+    parseNNBDCompilationUnit('D? foo(X? x) { X ? 7 : y; }');
+  }
+
+  void test_conditional_complex() {
+    parseNNBDCompilationUnit('D? foo(X? x) { X ? x2 = x + bar(7) : y; }');
+  }
+
+  void test_conditional_simple() {
+    parseNNBDCompilationUnit('D? foo(X? x) { X ? x2 = x : y; }');
+  }
+
+  void test_for() {
+    parseNNBDCompilationUnit('main() { for(int x = 0; x < 7; ++x) { } }');
+  }
+
+  void test_for_conditional() {
+    parseNNBDCompilationUnit(
+        'main() { for(x ? y = 7 : y = 8; y < 10; ++y) { } }');
+  }
+
+  void test_for_nullable() {
+    parseNNBDCompilationUnit('main() { for(int? x = 0; x < 7; ++x) { } }');
+  }
+
+  void test_foreach() {
+    parseNNBDCompilationUnit('main() { for(int x in [7]) { } }');
+  }
+
+  void test_foreach_nullable() {
+    parseNNBDCompilationUnit('main() { for(int? x in [7, null]) { } }');
+  }
+
   void test_is_nullable() {
     CompilationUnit unit =
         parseNNBDCompilationUnit('main() { x is String? ? (x + y) : z; }');
@@ -1476,10 +1517,6 @@
     expect(elseExpression, isSimpleIdentifier);
   }
 
-  void test_simple_assignment() {
-    parseNNBDCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
-  }
-
   void test_pragma_missing() {
     createParser("library foo;");
     _parserProxy.astBuilder.enableNonNullable = true;
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 ef520f0..51e3b9e 100644
--- a/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
@@ -541,11 +541,6 @@
   }
 
   @override
-  void handleExpressionStatement(Token token) {
-    listener?.handleExpressionStatement(token);
-  }
-
-  @override
   void endFactoryMethod(
       Token beginToken, Token factoryKeyword, Token endToken) {
     listener?.endFactoryMethod(beginToken, factoryKeyword, endToken);
@@ -997,6 +992,11 @@
   }
 
   @override
+  void handleExpressionStatement(Token token) {
+    listener?.handleExpressionStatement(token);
+  }
+
+  @override
   void handleExtraneousExpression(Token token, Message message) {
     listener?.handleExtraneousExpression(token, message);
   }
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 1938d8c..a3a59c2 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -5051,9 +5051,6 @@
       TypeInfo typeInfo,
       bool onlyParseVariableDeclarationStart = false]) {
     typeInfo ??= computeType(beforeType, false);
-    if (typeInfo.isConditionalExpressionStart(beforeType, this)) {
-      typeInfo = noType;
-    }
 
     Token token = typeInfo.skipType(beforeType);
     Token next = token.next;
@@ -5073,11 +5070,49 @@
             computeTypeParamOrArg(next).parseVariables(next, this);
         listener.beginLocalFunctionDeclaration(start.next);
         token = typeInfo.parseType(beforeType, this);
-        next = token.next;
         return parseNamedFunctionRest(token, start.next, beforeFormals, false);
       }
     }
 
+    if (beforeType == start &&
+        typeInfo.isNullable &&
+        typeInfo.couldBeExpression) {
+      assert(optional('?', token));
+      assert(next.isIdentifier);
+      Token afterIdentifier = next.next;
+      //
+      // found <typeref> `?` <identifier>
+      // with no annotations or modifiers preceeding it
+      //
+      if (optional('=', afterIdentifier)) {
+        //
+        // look past the next expression
+        // to determine if this is part of a conditional expression
+        //
+        final originalListener = listener;
+        listener = new ForwardingListener();
+        // TODO(danrubel): consider using TokenStreamGhostWriter here
+        Token afterExpression =
+            parseExpressionWithoutCascade(afterIdentifier).next;
+        listener = originalListener;
+
+        if (optional(':', afterExpression)) {
+          // Looks like part of a conditional expression.
+          // Drop the type information and reset the last consumed token.
+          typeInfo = noType;
+          token = start;
+          next = token.next;
+        }
+      } else if (!afterIdentifier.isKeyword &&
+          !isOneOfOrEof(afterIdentifier, const [';', ',', ')'])) {
+        // Looks like part of a conditional expression.
+        // Drop the type information and reset the last consumed token.
+        typeInfo = noType;
+        token = start;
+        next = token.next;
+      }
+    }
+
     if (token == start) {
       // If no annotation, modifier, or type, and this is not a local function
       // then this must be an expression statement.
@@ -5087,6 +5122,7 @@
         return parseExpressionStatement(start);
       }
     }
+
     if (next.type.isBuiltIn &&
         beforeType == start &&
         typeInfo.couldBeExpression) {
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info.dart b/pkg/front_end/lib/src/fasta/parser/type_info.dart
index b6e38f5..0871621 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -44,13 +44,6 @@
   /// in valid code or during recovery.
   Token ensureTypeOrVoid(Token token, Parser parser);
 
-  /// Return `true` if the tokens comprising the type represented by the
-  /// receiver are the start of a conditional expression.
-  /// For example, `A?` or `A.b?` could be the start of a conditional expression
-  /// and require arbitrary look ahead to determine if it is,
-  /// while `A<T>?` cannot be the start of a conditional expression.
-  bool isConditionalExpressionStart(Token token, Parser parser);
-
   /// Call this function to parse an optional type (not void) after [token].
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type. This may modify the token stream
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
index c500e04..de844a0 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
@@ -26,7 +26,6 @@
 
 import 'util.dart'
     show
-        isOneOfOrEof,
         optional,
         skipMetadata,
         splitGtEq,
@@ -114,9 +113,6 @@
       ensureTypeNotVoid(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) => false;
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -154,9 +150,6 @@
       parseType(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) => false;
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -198,10 +191,6 @@
   bool get isNullable => true;
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) =>
-      isConditionalThenExpression(skipType(token), parser);
-
-  @override
   Token parseTypeRest(Token start, Token token, Parser parser) {
     token = token.next;
     assert(optional('?', token));
@@ -241,9 +230,6 @@
       parseType(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) => false;
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -280,10 +266,6 @@
   bool get isNullable => true;
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) =>
-      isConditionalThenExpression(skipType(token), parser);
-
-  @override
   Token parseTypeRest(Token start, Parser parser) {
     Token token = start.next;
     assert(optional('?', token));
@@ -319,9 +301,6 @@
       parseType(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) => false;
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -370,9 +349,6 @@
       parseType(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) => false;
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       ensureTypeNotVoid(token, parser);
 
@@ -455,12 +431,6 @@
       parseType(token, parser);
 
   @override
-  bool isConditionalExpressionStart(Token token, Parser parser) {
-    //return isConditionalThenExpression(token.next.next, parser);
-    return false;
-  }
-
-  @override
   Token parseTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -1169,40 +1139,3 @@
   }
   return null;
 }
-
-/// Return `true` if the tokens after [token]
-/// represent a valid expression followed by a `:`.
-bool isConditionalThenExpression(Token token, Parser parser) {
-  // Quick checks for simple situations
-  Token next = token.next;
-  if (isOneOfOrEof(next, const ['?', ')', ';'])) {
-    return false;
-  } else if (next.isIdentifier) {
-    next = next.next;
-    if (optional(';', next)) {
-      // <identifier> `;`
-      return false;
-    } else if (isOneOfOrEof(next, const [
-      // Assignment operators
-      // TODO(danrubel): Consider replacing this with Token.isAssignmentOperator
-      '=', '&=', '&&=', '|=', '||=', '^=', '>>=', '<<=', '-=', '%=',
-      '+=', '??=', '/=', '*=', '~/='
-    ])) {
-      if (optional(';', next.next.next)) {
-        // <identifier> = <token> `;`
-        return false;
-      }
-    }
-  }
-
-  // TODO(danrubel): Enable more complex heavy weight lookahead
-  // once the parser can do it without modifying the token stream.
-  /*
-  final originalListener = parser.listener;
-  parser.listener = new ForwardingListener();
-  token = parser.parseExpression(token);
-  parser.listener = originalListener;
-  return optional(':', token.next);
-  */
-  return true;
-}