Add support for parsing simple nullable types

... as part of adding NNBD as outlined in
https://github.com/dart-lang/language/issues/110

This only supports parsing simple nullable types
such as int? and List<int>? while subsequent CLs
will add support for parsing more complex types.

Change-Id: I3cc5c65d20bf3732a39cab0e823b2f7332342866
Reviewed-on: https://dart-review.googlesource.com/c/86961
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
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 5e9f636..4dc7e66 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -12,7 +12,7 @@
 
 import 'type_info_impl.dart';
 
-import 'util.dart' show isOneOf, optional;
+import 'util.dart' show isOneOf, isOneOfOrEof, optional;
 
 /// [TypeInfo] provides information collected by [computeType]
 /// about a particular type reference.
@@ -199,13 +199,19 @@
       // We've seen identifier `<` identifier `>`
       next = typeParamOrArg.skip(next).next;
       if (!isGeneralizedFunctionType(next)) {
-        if (required || looksLikeName(next)) {
-          // identifier `<` identifier `>` identifier
-          return typeParamOrArg.typeInfo;
+        if (optional('?', next) && typeParamOrArg == simpleTypeArgument1) {
+          if (required || looksLikeName(next.next)) {
+            // identifier `<` identifier `>` `?` identifier
+            return simpleNullableTypeWith1Argument;
+          }
         } else {
-          // identifier `<` identifier `>` non-identifier
-          return noType;
+          if (required || looksLikeName(next)) {
+            // identifier `<` identifier `>` identifier
+            return typeParamOrArg.typeInfo;
+          }
         }
+        // identifier `<` identifier `>` non-identifier
+        return noType;
       }
     }
     // TODO(danrubel): Consider adding a const for
@@ -255,7 +261,23 @@
         .computeIdentifierGFT(required);
   }
 
-  if (required || looksLikeName(next)) {
+  if (optional('?', next)) {
+    if (required) {
+      // identifier `?`
+      return simpleNullableType;
+    } else {
+      next = next.next;
+      if (isGeneralizedFunctionType(next)) {
+        // identifier `?` Function `(`
+        return simpleNullableType;
+      } else if (looksLikeName(next) &&
+          isOneOfOrEof(
+              next.next, const [';', ',', '=', '>', '>=', '>>', '>>>'])) {
+        // identifier `?` identifier `=`
+        return simpleNullableType;
+      }
+    }
+  } else if (required || looksLikeName(next)) {
     // identifier identifier
     return simpleType;
   }
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 8a29317..ae7c464 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
@@ -38,6 +38,10 @@
 /// when there is a single identifier as the type reference.
 const TypeInfo simpleType = const SimpleType();
 
+/// [SimpleNullableType] is a specialized [TypeInfo] returned by [computeType]
+/// when there is a single identifier followed by `?` as the type reference.
+const TypeInfo simpleNullableType = const SimpleNullableType();
+
 /// [PrefixedType] is a specialized [TypeInfo] returned by [computeType]
 /// when the type reference is of the form: identifier `.` identifier.
 const TypeInfo prefixedType = const PrefixedType();
@@ -60,6 +64,12 @@
 const TypeInfo simpleTypeWith1ArgumentGtGt =
     const SimpleTypeWith1Argument(simpleTypeArgument1GtGt);
 
+/// [SimpleNullableTypeWith1Argument] is a specialized [TypeInfo] returned by
+/// [computeType] when the type reference is of the form:
+/// identifier `<` identifier `>` `?`.
+const TypeInfo simpleNullableTypeWith1Argument =
+    const SimpleNullableTypeWith1Argument();
+
 /// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
 /// [computeTypeParamOrArg] when the type reference is of the form:
 /// `<` identifier `>`.
@@ -164,6 +174,29 @@
   }
 }
 
+/// See documentation on the [simpleNullableTypeWith1Argument] const.
+class SimpleNullableTypeWith1Argument extends SimpleTypeWith1Argument {
+  const SimpleNullableTypeWith1Argument() : super(simpleTypeArgument1);
+
+  @override
+  TypeInfo asNonNullableType() => simpleTypeWith1Argument;
+
+  @override
+  Token parseTypeRest(Token start, Token token, Parser parser) {
+    token = token.next;
+    assert(optional('?', token));
+    parser.listener.handleType(start, token);
+    return token;
+  }
+
+  @override
+  Token skipType(Token token) {
+    token = super.skipType(token).next;
+    assert(optional('?', token));
+    return token;
+  }
+}
+
 /// See documentation on the [simpleTypeWith1Argument] const.
 class SimpleTypeWith1Argument implements TypeInfo {
   final TypeParamOrArgInfo typeArg;
@@ -210,6 +243,27 @@
   }
 }
 
+/// See documentation on the [simpleNullableType] const.
+class SimpleNullableType extends SimpleType {
+  const SimpleNullableType();
+
+  @override
+  TypeInfo asNonNullableType() => simpleType;
+
+  @override
+  Token parseTypeRest(Token start, Parser parser) {
+    Token token = start.next;
+    assert(optional('?', token));
+    parser.listener.handleType(start, token);
+    return token;
+  }
+
+  @override
+  Token skipType(Token token) {
+    return token.next.next;
+  }
+}
+
 /// See documentation on the [simpleType] const.
 class SimpleType implements TypeInfo {
   const SimpleType();
diff --git a/pkg/front_end/test/fasta/parser/type_info_test.dart b/pkg/front_end/test/fasta/parser/type_info_test.dart
index 84d5231..fedbeca 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -19,7 +19,9 @@
   defineReflectiveSuite(() {
     defineReflectiveTests(NoTypeInfoTest);
     defineReflectiveTests(PrefixedTypeInfoTest);
-    defineReflectiveTests(SimpleTypeInfoTest);
+    defineReflectiveTests(SimpleNullableTypeTest);
+    defineReflectiveTests(SimpleNullableTypeWith1ArgumentTest);
+    defineReflectiveTests(SimpleTypeTest);
     defineReflectiveTests(SimpleTypeWith1ArgumentTest);
     defineReflectiveTests(TypeInfoTest);
     defineReflectiveTests(VoidTypeInfoTest);
@@ -309,7 +311,124 @@
 }
 
 @reflectiveTest
-class SimpleTypeInfoTest {
+class SimpleNullableTypeTest {
+  void test_compute() {
+    expectInfo(simpleNullableType, 'C?', required: true);
+    expectInfo(simpleNullableType, 'C?;', required: true);
+    expectInfo(simpleNullableType, 'C?(', required: true);
+    expectInfo(simpleNullableType, 'C?<', required: true);
+    expectInfo(simpleNullableType, 'C?=', required: true);
+    expectInfo(simpleNullableType, 'C?*', required: true);
+    expectInfo(simpleNullableType, 'C? do', required: true);
+
+    expectInfo(simpleNullableType, 'C? foo');
+    expectInfo(simpleNullableType, 'C? get');
+    expectInfo(simpleNullableType, 'C? set');
+    expectInfo(simpleNullableType, 'C? operator');
+    expectInfo(simpleNullableType, 'C? this');
+    expectInfo(simpleNullableType, 'C? Function');
+
+    expectInfo(simpleNullableType, 'C? Function()', required: false);
+    expectInfo(simpleNullableType, 'C? Function<T>()', required: false);
+    expectInfo(simpleNullableType, 'C? Function(int)', required: false);
+    expectInfo(simpleNullableType, 'C? Function<T>(int)', required: false);
+    expectInfo(simpleNullableType, 'C? Function(int x)', required: false);
+    expectInfo(simpleNullableType, 'C? Function<T>(int x)', required: false);
+  }
+
+  void test_simpleNullableType() {
+    final Token start = scanString('before C? ;').tokens;
+    final Token expectedEnd = start.next.next;
+
+    expect(simpleNullableType.skipType(start), expectedEnd);
+    expect(simpleNullableType.couldBeExpression, isTrue);
+
+    TypeInfoListener listener;
+    assertResult(Token actualEnd) {
+      expect(actualEnd, expectedEnd);
+      expect(listener.calls, [
+        'handleIdentifier C typeReference',
+        'handleNoTypeArguments ?',
+        'handleType C ?',
+      ]);
+      expect(listener.errors, isNull);
+    }
+
+    listener = new TypeInfoListener();
+    assertResult(
+        simpleNullableType.ensureTypeNotVoid(start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(
+        simpleNullableType.ensureTypeOrVoid(start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(
+        simpleNullableType.parseTypeNotVoid(start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(simpleNullableType.parseType(start, new Parser(listener)));
+  }
+}
+
+@reflectiveTest
+class SimpleNullableTypeWith1ArgumentTest {
+  void test_compute() {
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>?', required: true);
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>?;', required: true);
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>?(', required: true);
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? do', required: true);
+
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? foo');
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? get');
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? set');
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? operator');
+    expectInfo(simpleNullableTypeWith1Argument, 'C<T>? Function');
+  }
+
+  void test_gt_questionMark() {
+    final Token start = scanString('before C<T>? ;').tokens;
+    final Token expectedEnd = start.next.next.next.next.next;
+    expect(expectedEnd.lexeme, '?');
+
+    expect(simpleNullableTypeWith1Argument.skipType(start), expectedEnd);
+    expect(simpleNullableTypeWith1Argument.couldBeExpression, isFalse);
+
+    TypeInfoListener listener;
+    assertResult(Token actualEnd) {
+      expect(actualEnd, expectedEnd);
+      expect(listener.calls, [
+        'handleIdentifier C typeReference',
+        'beginTypeArguments <',
+        'handleIdentifier T typeReference',
+        'handleNoTypeArguments >',
+        'handleType T null',
+        'endTypeArguments 1 < >',
+        'handleType C ?',
+      ]);
+      expect(listener.errors, isNull);
+    }
+
+    listener = new TypeInfoListener();
+    assertResult(simpleNullableTypeWith1Argument.ensureTypeNotVoid(
+        start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(simpleNullableTypeWith1Argument.ensureTypeOrVoid(
+        start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(simpleNullableTypeWith1Argument.parseTypeNotVoid(
+        start, new Parser(listener)));
+
+    listener = new TypeInfoListener();
+    assertResult(
+        simpleNullableTypeWith1Argument.parseType(start, new Parser(listener)));
+  }
+}
+
+@reflectiveTest
+class SimpleTypeTest {
   void test_compute() {
     expectInfo(simpleType, 'C', required: true);
     expectInfo(simpleType, 'C;', required: true);
@@ -336,7 +455,7 @@
     expectInfo(simpleType, 'C Function<T>(int x)', required: false);
   }
 
-  void test_simpleTypeInfo() {
+  void test_simpleType() {
     final Token start = scanString('before C ;').tokens;
     final Token expectedEnd = start.next;
 
@@ -818,10 +937,15 @@
         expectedErrors: [
           error(codeExpectedType, 2, 1)
         ]);
+  }
 
+  void test_computeType_statements() {
     // Statements that should not have a type
     expectInfo(noType, 'C<T ; T>U;', required: false);
     expectInfo(noType, 'C<T && T>U;', required: false);
+
+    expectInfo(noType, 'C? D : E;', required: false);
+    expectInfo(noType, 'C? D.foo : E;', required: false);
   }
 
   void test_computeType_nested() {