Revise is/as parsing for nullable types

Change-Id: I93cd4be70882ee318ea0f08848a23b4acdf35309
Reviewed-on: https://dart-review.googlesource.com/c/88302
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index eb45e33..1938d8c 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -100,6 +100,7 @@
         isLetter,
         isLetterOrDigit,
         isOneOf,
+        isOneOfOrEof,
         isWhitespace,
         optional;
 
@@ -4881,15 +4882,26 @@
     if (optional('!', token.next)) {
       not = token = token.next;
     }
-    TypeInfo typeInfo = computeType(token, true);
-    if (typeInfo.isConditionalExpressionStart(token, this)) {
-      typeInfo = typeInfo.asNonNullable;
-    }
+    TypeInfo typeInfo = computeTypeAfterIsOrAs(token);
     token = typeInfo.ensureTypeNotVoid(token, this);
     listener.handleIsOperator(operator, not);
     return skipChainedAsIsOperators(token);
   }
 
+  TypeInfo computeTypeAfterIsOrAs(Token token) {
+    TypeInfo typeInfo = computeType(token, true);
+    if (typeInfo.isNullable) {
+      Token next = typeInfo.skipType(token).next;
+      if (!isOneOfOrEof(next, const [')', '?', ';', 'is', 'as'])) {
+        // TODO(danrubel): investigate other situations
+        // where `?` should be considered part of the type info
+        // rather than the start of a conditional expression.
+        typeInfo = typeInfo.asNonNullable;
+      }
+    }
+    return typeInfo;
+  }
+
   /// ```
   /// typeCast:
   ///   'as' type
@@ -4898,10 +4910,7 @@
   Token parseAsOperatorRest(Token token) {
     Token operator = token = token.next;
     assert(optional('as', operator));
-    TypeInfo typeInfo = computeType(token, true);
-    if (typeInfo.isConditionalExpressionStart(token, this)) {
-      typeInfo = typeInfo.asNonNullable;
-    }
+    TypeInfo typeInfo = computeTypeAfterIsOrAs(token);
     token = typeInfo.ensureTypeNotVoid(token, this);
     listener.handleAsOperator(operator);
     return skipChainedAsIsOperators(token);
@@ -4920,10 +4929,7 @@
       if (optional('!', next.next)) {
         next = next.next;
       }
-      TypeInfo typeInfo = computeType(next, true);
-      if (typeInfo.isConditionalExpressionStart(next, this)) {
-        typeInfo = typeInfo.asNonNullable;
-      }
+      TypeInfo typeInfo = computeTypeAfterIsOrAs(next);
       token = typeInfo.skipType(next);
       next = token.next;
       value = next.stringValue;
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 c42442a..b6e38f5 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -27,6 +27,9 @@
   /// or expressions, while `A<T>` only looks like a type reference.
   bool get couldBeExpression;
 
+  /// Return true if the receiver has a trailing `?`.
+  bool get isNullable;
+
   /// Call this function when the token after [token] must be a type (not void).
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type, inserting a synthetic type reference if
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 f5042e0..c500e04 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
@@ -99,6 +99,9 @@
   bool get couldBeExpression => false;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) {
     parser.reportRecoverableErrorWithToken(
         token.next, fasta.templateExpectedType);
@@ -140,6 +143,9 @@
   bool get couldBeExpression => true;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -189,6 +195,9 @@
   TypeInfo get asNonNullable => simpleTypeWith1Argument;
 
   @override
+  bool get isNullable => true;
+
+  @override
   bool isConditionalExpressionStart(Token token, Parser parser) =>
       isConditionalThenExpression(skipType(token), parser);
 
@@ -221,6 +230,9 @@
   bool get couldBeExpression => false;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -265,6 +277,9 @@
   TypeInfo get asNonNullable => simpleType;
 
   @override
+  bool get isNullable => true;
+
+  @override
   bool isConditionalExpressionStart(Token token, Parser parser) =>
       isConditionalThenExpression(skipType(token), parser);
 
@@ -293,6 +308,9 @@
   bool get couldBeExpression => true;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);
 
@@ -338,6 +356,9 @@
   bool get couldBeExpression => false;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) {
     // Report an error, then parse `void` as if it were a type name.
     parser.reportRecoverableError(token.next, fasta.messageInvalidVoid);
@@ -423,6 +444,9 @@
   bool get couldBeExpression => false;
 
   @override
+  bool get isNullable => false;
+
+  @override
   Token ensureTypeNotVoid(Token token, Parser parser) =>
       parseType(token, parser);