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}) {