Add support for prefixed nullable type

This adds support for nullable types of the form

<identifier> '.' <identifier> '?'

and

<identifier> '.' <identifier> '?' 'Function' '(' ...

This is an increment CL in the ongoing effort to add nullable type support
as outlined in https://github.com/dart-lang/language/issues/110

Change-Id: I526aecbe64bacbd442cea0b4c52d36ff23b0443b
Reviewed-on: https://dart-review.googlesource.com/c/87083
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index b1dd26d..b3e24df 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -1987,6 +1987,35 @@
         BinaryExpression, expression.condition);
   }
 
+  void test_conditionalExpression_prefixedValue() {
+    ExpressionStatement statement = parseStatement('a.b ? y : z;');
+    ConditionalExpression expression = statement.expression;
+    EngineTestCase.assertInstanceOf((obj) => obj is PrefixedIdentifier,
+        PrefixedIdentifier, expression.condition);
+  }
+
+  void test_conditionalExpression_prefixedValue2() {
+    ExpressionStatement statement = parseStatement('a.b ? x.y : z;');
+    ConditionalExpression expression = statement.expression;
+    EngineTestCase.assertInstanceOf((obj) => obj is PrefixedIdentifier,
+        PrefixedIdentifier, expression.condition);
+    EngineTestCase.assertInstanceOf((obj) => obj is PrefixedIdentifier,
+        PrefixedIdentifier, expression.thenExpression);
+  }
+
+  void test_conditionalExpression_precedence_prefixedNullableType_as() {
+    Expression expression = parseExpression('x as p.A ? (x + y) : z');
+    expect(expression, isNotNull);
+    expect(expression, new TypeMatcher<ConditionalExpression>());
+    ConditionalExpression conditional = expression;
+    Expression condition = conditional.condition;
+    expect(condition, new TypeMatcher<AsExpression>());
+    Expression thenExpression = conditional.thenExpression;
+    expect(thenExpression, new TypeMatcher<ParenthesizedExpression>());
+    Expression elseExpression = conditional.elseExpression;
+    expect(elseExpression, new TypeMatcher<SimpleIdentifier>());
+  }
+
   void test_conditionalExpression_precedence_nullableType_as() {
     Expression expression = parseExpression('x as String ? (x + y) : z');
     expect(expression, isNotNull);
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 5e27b8c..2a5a8f6 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, isOneOfOrEof, optional;
+import 'util.dart' show isOneOf, optional;
 
 /// [TypeInfo] provides information collected by [computeType]
 /// about a particular type reference.
@@ -231,14 +231,23 @@
       // We've seen identifier `.` identifier
       typeParamOrArg = computeTypeParamOrArg(next, inDeclaration);
       next = next.next;
-      if (typeParamOrArg == noTypeParamOrArg &&
-          !isGeneralizedFunctionType(next)) {
-        if (required || looksLikeName(next)) {
-          // identifier `.` identifier identifier
-          return prefixedType;
-        } else {
-          // identifier `.` identifier non-identifier
-          return noType;
+      if (typeParamOrArg == noTypeParamOrArg) {
+        if (optional('?', next)) {
+          next = next.next;
+          // identifier `.` identifier `?`
+          if (!required &&
+              !looksLikeVarName(next) &&
+              !isGeneralizedFunctionType(next)) {
+            return noType;
+          }
+        } else if (!isGeneralizedFunctionType(next)) {
+          if (required || looksLikeName(next)) {
+            // identifier `.` identifier identifier
+            return prefixedType;
+          } else {
+            // identifier `.` identifier non-identifier
+            return noType;
+          }
         }
       }
       // identifier `.` identifier
@@ -267,10 +276,7 @@
       // identifier `?` Function `(`
       return new ComplexTypeInfo(token, noTypeParamOrArg)
           .computeIdentifierQuestionGFT(required);
-    } else if (required ||
-        (looksLikeName(next) &&
-            isOneOfOrEof(
-                next.next, const [';', ',', '=', '>', '>=', '>>', '>>>']))) {
+    } else if (required || looksLikeVarName(next)) {
       // identifier `?`
       return simpleNullableType;
     }
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 41df19b..323b535 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,6 +26,7 @@
 
 import 'util.dart'
     show
+        isOneOfOrEof,
         optional,
         skipMetadata,
         splitGtEq,
@@ -367,6 +368,16 @@
   return false;
 }
 
+bool looksLikeVarName(Token token) {
+  return (looksLikeName(token) &&
+      isOneOfOrEof(token.next, const [
+        ';', ',',
+        // TODO(danrubel): Consider refactoring this into TokenType.isAssignment
+        '=', '&&=', '&=', '||=', '|=', '^=', '>>=', '<<=', '-=', '%=', '+=',
+        '??=', '/=', '*=', '~/=',
+      ]));
+}
+
 /// Instances of [ComplexTypeInfo] are returned by [computeType] to represent
 /// type references that cannot be represented by the constants above.
 class ComplexTypeInfo implements TypeInfo {
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 64675bb..84c5130 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -1013,6 +1013,39 @@
         required: true);
   }
 
+  void test_computeType_prefixedGFT_questionMark() {
+    expectComplexInfo('C.a? Function(', // Scanner inserts synthetic ')'.
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C prefixedTypeReference',
+          'handleIdentifier a typeReferenceContinuation',
+          'handleQualified .',
+          'handleNoTypeArguments ?',
+          'handleType C ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function null',
+        ]);
+    expectComplexInfo('C.a? Function<T>(int x) Function<T>(int x)',
+        required: false, expectedAfter: 'Function');
+    expectComplexInfo('C.a? Function<T>(int x) Function<T>(int x)',
+        required: true);
+  }
+
+  void test_computeType_prefixedQuestionMark() {
+    expectComplexInfo('C.a? Function',
+        expectedAfter: 'Function',
+        expectedCalls: [
+          'handleIdentifier C prefixedTypeReference',
+          'handleIdentifier a typeReferenceContinuation',
+          'handleQualified .',
+          'handleNoTypeArguments ?',
+          'handleType C ?',
+        ]);
+  }
+
   void test_computeType_prefixedTypeArg() {
     expectComplexInfo('C.a<T>', required: true, expectedCalls: [
       'handleIdentifier C prefixedTypeReference',