Add support for simple nullable type return value in generalized function type
This only supports nullable return values of the form
<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: I42febae9f88f7e4d8b05907988deab97c7a7425c
Reviewed-on: https://dart-review.googlesource.com/c/87081
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 8b8b575..b1dd26d 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -2013,6 +2013,46 @@
expect(elseExpression, new TypeMatcher<SimpleIdentifier>());
}
+ void test_conditionalExpression_precedence_nullableTypeWithTypeArg1_is() {
+ Expression expression = parseExpression('x is String<S> ? (x + y) : z');
+ expect(expression, isNotNull);
+ expect(expression, new TypeMatcher<ConditionalExpression>());
+ ConditionalExpression conditional = expression;
+ Expression condition = conditional.condition;
+ expect(condition, new TypeMatcher<IsExpression>());
+ Expression thenExpression = conditional.thenExpression;
+ expect(thenExpression, new TypeMatcher<ParenthesizedExpression>());
+ Expression elseExpression = conditional.elseExpression;
+ expect(elseExpression, new TypeMatcher<SimpleIdentifier>());
+ }
+
+ void test_conditionalExpression_precedence_nullableTypeWithTypeArg1GFT_is() {
+ Expression expression =
+ parseExpression('x is String<S> Function() ? (x + y) : z');
+ expect(expression, isNotNull);
+ expect(expression, new TypeMatcher<ConditionalExpression>());
+ ConditionalExpression conditional = expression;
+ Expression condition = conditional.condition;
+ expect(condition, new TypeMatcher<IsExpression>());
+ Expression thenExpression = conditional.thenExpression;
+ expect(thenExpression, new TypeMatcher<ParenthesizedExpression>());
+ Expression elseExpression = conditional.elseExpression;
+ expect(elseExpression, new TypeMatcher<SimpleIdentifier>());
+ }
+
+ void test_conditionalExpression_precedence_nullableTypeWithTypeArg2_is() {
+ Expression expression = parseExpression('x is String<S,T> ? (x + y) : z');
+ expect(expression, isNotNull);
+ expect(expression, new TypeMatcher<ConditionalExpression>());
+ ConditionalExpression conditional = expression;
+ Expression condition = conditional.condition;
+ expect(condition, new TypeMatcher<IsExpression>());
+ Expression thenExpression = conditional.thenExpression;
+ expect(thenExpression, new TypeMatcher<ParenthesizedExpression>());
+ Expression elseExpression = conditional.elseExpression;
+ expect(elseExpression, new TypeMatcher<SimpleIdentifier>());
+ }
+
void test_constructor_initializer_withParenthesizedExpression() {
CompilationUnit unit = parseCompilationUnit(r'''
class C {
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 4dc7e66..5e27b8c 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -187,7 +187,7 @@
if (isGeneralizedFunctionType(next)) {
// `Function` ...
return new ComplexTypeInfo(token, noTypeParamOrArg)
- .computeNoTypeGFT(required);
+ .computeNoTypeGFT(token, required);
}
// We've seen an identifier.
@@ -262,20 +262,17 @@
}
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) &&
+ isOneOfOrEof(
+ next.next, const [';', ',', '=', '>', '>=', '>>', '>>>']))) {
// 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
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 2b87c5b..41df19b 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
@@ -376,6 +376,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;
@@ -390,12 +393,18 @@
ComplexTypeInfo(Token beforeStart, this.typeArguments)
: this.start = beforeStart.next;
+ ComplexTypeInfo._nonNullable(this.start, this.typeArguments, this.end,
+ this.typeVariableStarters, this.gftHasReturnType);
+
@override
bool get couldBeExpression => false;
@override
TypeInfo asNonNullableType() {
- return this;
+ return beforeQuestionMark == null
+ ? this
+ : new ComplexTypeInfo._nonNullable(start, typeArguments,
+ beforeQuestionMark, typeVariableStarters, gftHasReturnType);
}
@override
@@ -452,7 +461,15 @@
}
}
token = typeArguments.parseArguments(token, parser);
- parser.listener.handleType(typeRefOrPrefix, null);
+ Token questionMark = token.next;
+ if (optional('?', questionMark) &&
+ (typeVariableEndGroups.isNotEmpty || beforeQuestionMark != null)) {
+ // Only consume the `?` if it is part of the complex type
+ token = questionMark;
+ } else {
+ questionMark = null;
+ }
+ parser.listener.handleType(typeRefOrPrefix, questionMark);
}
}
@@ -492,10 +509,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;
}
@@ -509,7 +527,7 @@
assert(optional('void', start));
assert(optional('Function', start.next));
- computeRest(start.next, required);
+ computeRest(start, required);
if (gftHasReturnType == null) {
return voidType;
}
@@ -523,7 +541,7 @@
assert(isValidTypeReference(start));
assert(optional('Function', start.next));
- computeRest(start.next, required);
+ computeRest(start, required);
if (gftHasReturnType == null) {
return simpleType;
}
@@ -531,13 +549,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;
}
@@ -550,7 +583,7 @@
assert(typeArguments != noTypeParamOrArg);
end = typeArguments.skip(start);
- computeRest(end.next, required);
+ computeRest(end, required);
if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
return noType;
@@ -574,7 +607,7 @@
}
end = typeArguments.skip(token);
- computeRest(end.next, required);
+ computeRest(end, required);
if (!required && !looksLikeName(end.next) && gftHasReturnType == null) {
return noType;
}
@@ -583,6 +616,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
@@ -603,9 +641,14 @@
assert(optional(')', token));
gftHasReturnType ??= typeVariableStart != start;
typeVariableStarters = typeVariableStarters.prepend(typeVariableStart);
+ beforeQuestionMark = null;
end = token;
token = token.next;
}
+ if (optional('?', token)) {
+ beforeQuestionMark = end;
+ end = token;
+ }
}
}
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 7b9b003..64675bb 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -800,6 +800,19 @@
]);
}
+ 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_identifierTypeArg() {
expectComplexInfo('C<void>', required: true, expectedCalls: [
'handleIdentifier C typeReference',