Add support for parsing complex nullable types

Change-Id: I38a59f96e299387b7266e46082d876105eb60f1e
Reviewed-on: https://dart-review.googlesource.com/c/88640
Reviewed-by: Brian Wilkerson <brianwilkerson@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 3f8732d..1f618d1 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -109,7 +109,64 @@
  */
 @reflectiveTest
 class ComplexParserTest_Fasta extends FastaParserTestCase
-    with ComplexParserTestMixin {}
+    with ComplexParserTestMixin {
+  void test_conditionalExpression_precedence_nullableType_as2() {
+    ExpressionStatement statement = parseStatement('x as bool? ? (x + y) : z;');
+    ConditionalExpression expression = statement.expression;
+    Expression condition = expression.condition;
+    expect(condition, isAsExpression);
+    Expression thenExpression = expression.thenExpression;
+    expect(thenExpression, isParenthesizedExpression);
+    Expression elseExpression = expression.elseExpression;
+    expect(elseExpression, isSimpleIdentifier);
+    assertErrors(
+        errors: [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 9, 1)]);
+  }
+
+  void test_conditionalExpression_precedence_nullableType_as3() {
+    ExpressionStatement statement =
+        parseStatement('(x as bool?) ? (x + y) : z;');
+    ConditionalExpression expression = statement.expression;
+    Expression condition = expression.condition;
+    expect(condition, isParenthesizedExpression);
+    expect((condition as ParenthesizedExpression).expression, isAsExpression);
+    Expression thenExpression = expression.thenExpression;
+    expect(thenExpression, isParenthesizedExpression);
+    Expression elseExpression = expression.elseExpression;
+    expect(elseExpression, isSimpleIdentifier);
+    assertErrors(
+        errors: [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 10, 1)]);
+  }
+
+  void test_conditionalExpression_precedence_nullableType_is2() {
+    ExpressionStatement statement =
+        parseStatement('x is String? ? (x + y) : z;');
+    ConditionalExpression expression = statement.expression;
+    Expression condition = expression.condition;
+    expect(condition, isIsExpression);
+    Expression thenExpression = expression.thenExpression;
+    expect(thenExpression, isParenthesizedExpression);
+    Expression elseExpression = expression.elseExpression;
+    expect(elseExpression, isSimpleIdentifier);
+    assertErrors(
+        errors: [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 11, 1)]);
+  }
+
+  void test_conditionalExpression_precedence_nullableType_is3() {
+    ExpressionStatement statement =
+        parseStatement('(x is String?) ? (x + y) : z;');
+    ConditionalExpression expression = statement.expression;
+    Expression condition = expression.condition;
+    expect(condition, isParenthesizedExpression);
+    expect((condition as ParenthesizedExpression).expression, isIsExpression);
+    Expression thenExpression = expression.thenExpression;
+    expect(thenExpression, isParenthesizedExpression);
+    Expression elseExpression = expression.elseExpression;
+    expect(elseExpression, isSimpleIdentifier);
+    assertErrors(
+        errors: [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 12, 1)]);
+  }
+}
 
 /**
  * Tests of the fasta parser based on [ErrorParserTest].
@@ -1451,6 +1508,26 @@
     parseNNBDCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
   }
 
+  void test_gft_nullable() {
+    parseNNBDCompilationUnit('main() { C? Function() x = 7; }');
+  }
+
+  void test_gft_nullable_1() {
+    parseNNBDCompilationUnit('main() { C Function()? x = 7; }');
+  }
+
+  void test_gft_nullable_2() {
+    parseNNBDCompilationUnit('main() { C? Function()? x = 7; }');
+  }
+
+  void test_gft_nullable_3() {
+    parseNNBDCompilationUnit('main() { C? Function()? Function()? x = 7; }');
+  }
+
+  void test_gft_nullable_prefixed() {
+    parseNNBDCompilationUnit('main() { C.a? Function()? x = 7; }');
+  }
+
   void test_conditional() {
     parseNNBDCompilationUnit('D? foo(X? x) { X ? 7 : y; }');
   }
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 1335b49..bab974d 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -1981,7 +1981,7 @@
     expect(elseExpression, isSimpleIdentifier);
   }
 
-  void test_conditionalExpression_precedence_is() {
+  void test_conditionalExpression_precedence_nullableType_is() {
     ExpressionStatement statement =
         parseStatement('x is String ? (x + y) : z;');
     ConditionalExpression expression = statement.expression;
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 0871621..8214825 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -190,7 +190,7 @@
   if (isGeneralizedFunctionType(next)) {
     // `Function` ...
     return new ComplexTypeInfo(token, noTypeParamOrArg)
-        .computeNoTypeGFT(required);
+        .computeNoTypeGFT(token, required);
   }
 
   // We've seen an identifier.
@@ -201,17 +201,21 @@
     if (typeParamOrArg.isSimpleTypeArgument) {
       // We've seen identifier `<` identifier `>`
       next = typeParamOrArg.skip(next).next;
-      if (!isGeneralizedFunctionType(next)) {
-        if (optional('?', next) && typeParamOrArg == simpleTypeArgument1) {
-          if (required || looksLikeName(next.next)) {
+      if (optional('?', next)) {
+        next = next.next;
+        if (!isGeneralizedFunctionType(next)) {
+          if ((required || looksLikeName(next)) &&
+              typeParamOrArg == simpleTypeArgument1) {
             // identifier `<` identifier `>` `?` identifier
             return simpleNullableTypeWith1Argument;
           }
-        } else {
-          if (required || looksLikeName(next)) {
-            // identifier `<` identifier `>` identifier
-            return typeParamOrArg.typeInfo;
-          }
+          // identifier `<` identifier `>` `?` non-identifier
+          return noType;
+        }
+      } else if (!isGeneralizedFunctionType(next)) {
+        if (required || looksLikeName(next)) {
+          // identifier `<` identifier `>` identifier
+          return typeParamOrArg.typeInfo;
         }
         // identifier `<` identifier `>` non-identifier
         return noType;
@@ -234,14 +238,29 @@
       // 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;
+      if (typeParamOrArg == noTypeParamOrArg) {
+        if (optional('?', next)) {
+          next = next.next;
+          if (!isGeneralizedFunctionType(next)) {
+            if (required || looksLikeName(next)) {
+              // identifier `.` identifier `?` identifier
+              // TODO(danrubel): consider adding PrefixedNullableType
+              // Fall through to build complex type
+            } else {
+              // identifier `.` identifier `?` non-identifier
+              return noType;
+            }
+          }
         } else {
-          // identifier `.` identifier non-identifier
-          return noType;
+          if (!isGeneralizedFunctionType(next)) {
+            if (required || looksLikeName(next)) {
+              // identifier `.` identifier identifier
+              return prefixedType;
+            } else {
+              // identifier `.` identifier non-identifier
+              return noType;
+            }
+          }
         }
       }
       // identifier `.` identifier
@@ -265,18 +284,14 @@
   }
 
   if (optional('?', next)) {
-    if (required) {
+    next = next.next;
+    if (isGeneralizedFunctionType(next)) {
+      // identifier `?` Function `(`
+      return new ComplexTypeInfo(token, noTypeParamOrArg)
+          .computeIdentifierQuestionGFT(required);
+    } else if (required || looksLikeName(next)) {
       // identifier `?`
       return simpleNullableType;
-    } else {
-      next = next.next;
-      if (isGeneralizedFunctionType(next)) {
-        // identifier `?` Function `(`
-        return simpleNullableType;
-      } else if (looksLikeName(next)) {
-        // identifier `?` identifier `=`
-        return simpleNullableType;
-      }
     }
   } else if (required || looksLikeName(next)) {
     // identifier identifier
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 4204c6f..2063d4d 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
@@ -397,6 +397,9 @@
   /// Type arguments were seen during analysis.
   final TypeParamOrArgInfo typeArguments;
 
+  /// The token before the trailing question mark or `null` if none.
+  Token beforeQuestionMark;
+
   /// The last token in the type reference.
   Token end;
 
@@ -409,11 +412,19 @@
   bool gftHasReturnType;
 
   ComplexTypeInfo(Token beforeStart, this.typeArguments)
-      : this.start = beforeStart.next;
+      : this.start = beforeStart.next {
+    assert(typeArguments != null);
+  }
+
+  ComplexTypeInfo._nonNullable(this.start, this.typeArguments, this.end,
+      this.typeVariableStarters, this.gftHasReturnType);
 
   @override
   TypeInfo get asNonNullable {
-    return this;
+    return beforeQuestionMark == null
+        ? this
+        : new ComplexTypeInfo._nonNullable(start, typeArguments,
+            beforeQuestionMark, typeVariableStarters, gftHasReturnType);
   }
 
   @override
@@ -421,7 +432,7 @@
       typeArguments == noTypeParamOrArg && typeVariableStarters.isEmpty;
 
   @override
-  bool get isNullable => false;
+  bool get isNullable => beforeQuestionMark != null;
 
   @override
   Token ensureTypeNotVoid(Token token, Parser parser) =>
@@ -477,7 +488,17 @@
           }
         }
         token = typeArguments.parseArguments(token, parser);
-        parser.listener.handleType(typeRefOrPrefix, null);
+
+        // Only consume the `?` if it is part of the complex type
+        Token questionMark = token.next;
+        if (optional('?', questionMark) &&
+            (typeVariableEndGroups.isNotEmpty || beforeQuestionMark != null)) {
+          token = questionMark;
+        } else {
+          questionMark = null;
+        }
+
+        parser.listener.handleType(typeRefOrPrefix, questionMark);
       }
     }
 
@@ -486,15 +507,26 @@
       token = token.next;
       assert(optional('Function', token));
       Token functionToken = token;
+
       if (optional("<", token.next)) {
         // Skip type parameters, they were parsed above.
         token = typeVariableEndGroups[endGroupIndex];
         assert(optional('>', token));
       }
-      --endGroupIndex;
       token = parser.parseFormalParametersRequiredOpt(
           token, MemberKind.GeneralizedFunctionType);
-      parser.listener.endFunctionType(functionToken, null);
+
+      // Only consume the `?` if it is part of the complex type
+      Token questionMark = token.next;
+      if (optional('?', questionMark) &&
+          (endGroupIndex > 0 || beforeQuestionMark != null)) {
+        token = questionMark;
+      } else {
+        questionMark = null;
+      }
+
+      --endGroupIndex;
+      parser.listener.endFunctionType(functionToken, questionMark);
     }
 
     // There are two situations in which the [token] != [end]:
@@ -517,10 +549,11 @@
 
   /// Given `Function` non-identifier, compute the type
   /// and return the receiver or one of the [TypeInfo] constants.
-  TypeInfo computeNoTypeGFT(bool required) {
+  TypeInfo computeNoTypeGFT(Token beforeStart, bool required) {
     assert(optional('Function', start));
+    assert(beforeStart.next == start);
 
-    computeRest(start, required);
+    computeRest(beforeStart, required);
     if (gftHasReturnType == null) {
       return required ? simpleType : noType;
     }
@@ -534,7 +567,7 @@
     assert(optional('void', start));
     assert(optional('Function', start.next));
 
-    computeRest(start.next, required);
+    computeRest(start, required);
     if (gftHasReturnType == null) {
       return voidType;
     }
@@ -548,7 +581,7 @@
     assert(isValidTypeReference(start));
     assert(optional('Function', start.next));
 
-    computeRest(start.next, required);
+    computeRest(start, required);
     if (gftHasReturnType == null) {
       return simpleType;
     }
@@ -556,13 +589,28 @@
     return this;
   }
 
+  /// Given identifier `?` `Function` non-identifier, compute the type
+  /// and return the receiver or one of the [TypeInfo] constants.
+  TypeInfo computeIdentifierQuestionGFT(bool required) {
+    assert(isValidTypeReference(start));
+    assert(optional('?', start.next));
+    assert(optional('Function', start.next.next));
+
+    computeRest(start, required);
+    if (gftHasReturnType == null) {
+      return simpleNullableType;
+    }
+    assert(end != null);
+    return this;
+  }
+
   /// Given a builtin, return the receiver so that parseType will report
   /// an error for the builtin used as a type.
   TypeInfo computeBuiltinOrVarAsType(bool required) {
     assert(start.type.isBuiltIn || optional('var', start));
 
     end = typeArguments.skip(start);
-    computeRest(end.next, required);
+    computeRest(end, required);
     assert(end != null);
     return this;
   }
@@ -575,7 +623,7 @@
     assert(typeArguments != noTypeParamOrArg);
 
     end = typeArguments.skip(start);
-    computeRest(end.next, required);
+    computeRest(end, required);
 
     if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
       return noType;
@@ -599,7 +647,7 @@
     }
 
     end = typeArguments.skip(token);
-    computeRest(end.next, required);
+    computeRest(end, required);
     if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
       return noType;
     }
@@ -608,6 +656,11 @@
   }
 
   void computeRest(Token token, bool required) {
+    if (optional('?', token.next)) {
+      beforeQuestionMark = token;
+      end = token = token.next;
+    }
+    token = token.next;
     while (optional('Function', token)) {
       Token typeVariableStart = token;
       // TODO(danrubel): Consider caching TypeParamOrArgInfo
@@ -621,15 +674,27 @@
         break; // Not a function type.
       }
       if (!required) {
-        if (!(token.next.isIdentifier || optional('this', token.next))) {
+        Token next = token.next;
+        if (optional('?', next)) {
+          next = next.next;
+        }
+        if (!(next.isIdentifier || optional('this', next))) {
           break; // `Function` used as the name in a function declaration.
         }
       }
       assert(optional(')', token));
       gftHasReturnType ??= typeVariableStart != start;
       typeVariableStarters = typeVariableStarters.prepend(typeVariableStart);
+
+      beforeQuestionMark = null;
       end = token;
       token = token.next;
+
+      if (optional('?', token)) {
+        beforeQuestionMark = end;
+        end = token;
+        token = token.next;
+      }
     }
   }
 }
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 bf2f35b..2f13cfe 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -812,6 +812,169 @@
         ]);
   }
 
+  void test_computeType_identifierComplex_questionMark() {
+    expectComplexInfo('C? Function()', required: true, expectedCalls: [
+      'handleNoTypeVariables (',
+      'beginFunctionType C',
+      'handleIdentifier C typeReference',
+      'handleNoTypeArguments ?',
+      'handleType C ?',
+      'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+      'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+      'endFunctionType Function null',
+    ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark2() {
+    expectComplexInfo('C Function()?', required: true, expectedCalls: [
+      'handleNoTypeVariables (',
+      'beginFunctionType C',
+      'handleIdentifier C typeReference',
+      'handleNoTypeArguments Function',
+      'handleType C null',
+      'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+      'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+      'endFunctionType Function ?',
+    ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark3() {
+    expectComplexInfo('C<T>? Function()', required: true, expectedCalls: [
+      'handleNoTypeVariables (',
+      'beginFunctionType C',
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 1 < >',
+      'handleType C ?',
+      'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+      'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+      'endFunctionType Function null',
+    ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark4() {
+    expectComplexInfo('C<S,T>? Function()', required: true, expectedCalls: [
+      'handleNoTypeVariables (',
+      'beginFunctionType C',
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'handleNoTypeArguments ,',
+      'handleType S null',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 2 < >',
+      'handleType C ?',
+      'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+      'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+      'endFunctionType Function null',
+    ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark5() {
+    expectComplexInfo('C Function()? Function()',
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C typeReference',
+          'handleNoTypeArguments Function',
+          'handleType C null',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function null',
+        ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark6() {
+    expectComplexInfo('C Function() Function()?',
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C typeReference',
+          'handleNoTypeArguments Function',
+          'handleType C null',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function null',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+        ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark7() {
+    expectComplexInfo('C? Function() Function()?',
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C typeReference',
+          'handleNoTypeArguments ?',
+          'handleType C ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function null',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+        ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark8() {
+    expectComplexInfo('C Function()? Function()?',
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C typeReference',
+          'handleNoTypeArguments Function',
+          'handleType C null',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+        ]);
+  }
+
+  void test_computeType_identifierComplex_questionMark9() {
+    expectComplexInfo('C? Function()? Function()?',
+        required: true,
+        expectedCalls: [
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleNoTypeVariables (',
+          'beginFunctionType C',
+          'handleIdentifier C typeReference',
+          'handleNoTypeArguments ?',
+          'handleType C ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function ?',
+        ]);
+  }
+
   void test_computeType_identifierTypeArg() {
     expectComplexInfo('C<void>', required: true, expectedCalls: [
       'handleIdentifier C typeReference',
@@ -822,6 +985,16 @@
     ]);
   }
 
+  void test_computeType_identifierTypeArg_questionMark() {
+    expectComplexInfo('C<void>?', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleVoidKeyword void',
+      'endTypeArguments 1 < >',
+      'handleType C ?',
+    ]);
+  }
+
   void test_computeType_identifierTypeArgComplex() {
     expectComplexInfo('C<S,T>', required: true, expectedCalls: [
       'handleIdentifier C typeReference',
@@ -875,6 +1048,157 @@
     ]);
   }
 
+  void test_computeType_identifierTypeArgComplex_questionMark() {
+    expectComplexInfo('C<S,T>?', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'handleNoTypeArguments ,',
+      'handleType S null',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 2 < >',
+      'handleType C ?',
+    ]);
+    expectComplexInfo('C<S,T?>', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'handleNoTypeArguments ,',
+      'handleType S null',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments ?',
+      'handleType T ?',
+      'endTypeArguments 2 < >',
+      'handleType C null',
+    ]);
+    expectComplexInfo('C<S,T?>>',
+        expectedAfter: '>',
+        required: true,
+        expectedCalls: [
+          'handleIdentifier C typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'handleNoTypeArguments ,',
+          'handleType S null',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments ?',
+          'handleType T ?',
+          'endTypeArguments 2 < >',
+          'handleType C null',
+        ]);
+    expectComplexInfo('C<S,T?>=',
+        expectedAfter: '=',
+        required: true,
+        expectedCalls: [
+          'handleIdentifier C typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'handleNoTypeArguments ,',
+          'handleType S null',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments ?',
+          'handleType T ?',
+          'endTypeArguments 2 < >',
+          'handleType C null',
+        ]);
+    expectComplexInfo('C<S,T?>>>',
+        expectedAfter: '>>',
+        required: true,
+        expectedCalls: [
+          'handleIdentifier C typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'handleNoTypeArguments ,',
+          'handleType S null',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments ?',
+          'handleType T ?',
+          'endTypeArguments 2 < >',
+          'handleType C null',
+        ]);
+    expectComplexInfo('C<S?,T>', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'handleNoTypeArguments ?',
+      'handleType S ?',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 2 < >',
+      'handleType C null',
+    ]);
+    expectComplexInfo('C<S<T>>?', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 1 < >',
+      'handleType S null',
+      'endTypeArguments 1 < >',
+      'handleType C ?',
+    ]);
+    expectComplexInfo('C<S<T?>>', required: true, expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments ?',
+      'handleType T ?',
+      'endTypeArguments 1 < >',
+      'handleType S null',
+      'endTypeArguments 1 < >',
+      'handleType C null',
+    ]);
+    expectComplexInfo('C<S<T?>>>',
+        expectedAfter: '>',
+        required: true,
+        expectedCalls: [
+          'handleIdentifier C typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments ?',
+          'handleType T ?',
+          'endTypeArguments 1 < >',
+          'handleType S null',
+          'endTypeArguments 1 < >',
+          'handleType C null',
+        ]);
+    expectComplexInfo('C<S,T>? f', expectedAfter: 'f', expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'handleNoTypeArguments ,',
+      'handleType S null',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 2 < >',
+      'handleType C ?',
+    ]);
+    expectComplexInfo('C<S<T>>? f', expectedAfter: 'f', expectedCalls: [
+      'handleIdentifier C typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'handleNoTypeArguments >',
+      'handleType T null',
+      'endTypeArguments 1 < >',
+      'handleType S null',
+      'endTypeArguments 1 < >',
+      'handleType C ?',
+    ]);
+  }
+
   void test_computeType_identifierTypeArgGFT() {
     expectComplexInfo('C<T> Function(', // Scanner inserts synthetic ')'.
         required: true,
@@ -1009,6 +1333,40 @@
         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',
+        couldBeExpression: true,
+        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',
@@ -2057,17 +2415,33 @@
     couldBeExpression('S', true);
   }
 
+  void test_simple_nullable() {
+    couldBeExpression('S?', true);
+  }
+
   void test_partial() {
     couldBeExpression('.S', true);
   }
 
+  void test_partial_nullable() {
+    couldBeExpression('.S?', true);
+  }
+
   void test_prefixed() {
     couldBeExpression('p.S', true);
   }
 
+  void test_prefixed_nullable() {
+    couldBeExpression('p.S?', true);
+  }
+
   void test_typeArg() {
     couldBeExpression('S<T>', false);
   }
+
+  void test_typeArg_nullable() {
+    couldBeExpression('S<T>?', false);
+  }
 }
 
 void expectInfo(expectedInfo, String source, {bool required}) {