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: I144858946cb115755af437299899c2631105bf8c
Reviewed-on: https://dart-review.googlesource.com/c/87501
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 8b0ff26..92ef4bb 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -4489,11 +4489,7 @@
Token name = beforeName.next;
if (name.isIdentifier) {
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(name);
- Token next = name;
- if (typeParam != noTypeParamOrArg) {
- next = typeParam.skip(next);
- }
- next = next.next;
+ Token next = typeParam.skip(name).next;
if (optional('(', next)) {
if (looksLikeFunctionBody(next.endGroup.next)) {
return parseFunctionLiteral(
@@ -4873,8 +4869,10 @@
if (optional('!', token.next)) {
not = token = token.next;
}
- // Ignore trailing `?` if there is one as it may be part of an expression
- TypeInfo typeInfo = computeType(token, true).asNonNullableType();
+ TypeInfo typeInfo = computeType(token, true);
+ if (typeInfo.isConditionalExpressionStart(token, this)) {
+ typeInfo = typeInfo.asNonNullable;
+ }
token = typeInfo.ensureTypeNotVoid(token, this);
listener.handleIsOperator(operator, not);
return skipChainedAsIsOperators(token);
@@ -4888,8 +4886,10 @@
Token parseAsOperatorRest(Token token) {
Token operator = token = token.next;
assert(optional('as', operator));
- // Ignore trailing `?` if there is one as it may be part of an expression
- TypeInfo typeInfo = computeType(token, true).asNonNullableType();
+ TypeInfo typeInfo = computeType(token, true);
+ if (typeInfo.isConditionalExpressionStart(token, this)) {
+ typeInfo = typeInfo.asNonNullable;
+ }
token = typeInfo.ensureTypeNotVoid(token, this);
listener.handleAsOperator(operator);
return skipChainedAsIsOperators(token);
@@ -4908,7 +4908,11 @@
if (optional('!', next.next)) {
next = next.next;
}
- token = computeType(next, true).skipType(next);
+ TypeInfo typeInfo = computeType(next, true);
+ if (typeInfo.isConditionalExpressionStart(next, this)) {
+ typeInfo = typeInfo.asNonNullable;
+ }
+ token = typeInfo.skipType(next);
next = token.next;
value = next.stringValue;
}
@@ -5029,6 +5033,10 @@
TypeInfo typeInfo,
bool onlyParseVariableDeclarationStart = false]) {
typeInfo ??= computeType(beforeType, false);
+ if (typeInfo.isConditionalExpressionStart(beforeType, this)) {
+ typeInfo = noType;
+ }
+
Token token = typeInfo.skipType(beforeType);
Token next = token.next;
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..c42442a 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -19,12 +19,12 @@
abstract class TypeInfo {
/// Return type info representing the receiver without the trailing `?`
/// or the receiver if the receiver does not represent a nullable type.
- TypeInfo asNonNullableType();
+ TypeInfo get asNonNullable;
/// Return `true` if the tokens comprising the type represented by the
/// receiver could be interpreted as a valid standalone expression.
- /// For example, `A` or `A.b` could be interpreted as a type references
- /// or as expressions, while `A<T>` only looks like a type reference.
+ /// For example, `A` or `A.b` could be interpreted as type references
+ /// or expressions, while `A<T>` only looks like a type reference.
bool get couldBeExpression;
/// Call this function when the token after [token] must be a type (not void).
@@ -41,6 +41,13 @@
/// in valid code or during recovery.
Token ensureTypeOrVoid(Token token, Parser parser);
+ /// Return `true` if the tokens comprising the type represented by the
+ /// receiver are the start of a conditional expression.
+ /// For example, `A?` or `A.b?` could be the start of a conditional expression
+ /// and require arbitrary look ahead to determine if it is,
+ /// while `A<T>?` cannot be the start of a conditional expression.
+ bool isConditionalExpressionStart(Token token, Parser parser);
+
/// Call this function to parse an optional type (not void) after [token].
/// This function will call the appropriate event methods on the [Parser]'s
/// listener to handle the type. This may modify the token stream
@@ -199,13 +206,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 +268,21 @@
.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)) {
+ // 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 2a2b6ab..7cb7f45 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 `>`.
@@ -82,10 +92,10 @@
const NoType();
@override
- bool get couldBeExpression => false;
+ TypeInfo get asNonNullable => this;
@override
- TypeInfo asNonNullableType() => this;
+ bool get couldBeExpression => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) {
@@ -100,6 +110,9 @@
ensureTypeNotVoid(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) => false;
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -120,10 +133,10 @@
const PrefixedType();
@override
- bool get couldBeExpression => true;
+ TypeInfo get asNonNullable => this;
@override
- TypeInfo asNonNullableType() => this;
+ bool get couldBeExpression => true;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
@@ -134,6 +147,9 @@
parseType(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) => false;
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -164,6 +180,33 @@
}
}
+/// See documentation on the [simpleNullableTypeWith1Argument] const.
+class SimpleNullableTypeWith1Argument extends SimpleTypeWith1Argument {
+ const SimpleNullableTypeWith1Argument() : super(simpleTypeArgument1);
+
+ @override
+ TypeInfo get asNonNullable => simpleTypeWith1Argument;
+
+ @override
+ bool isConditionalExpressionStart(Token token, Parser parser) =>
+ isConditionalThenExpression(skipType(token), parser);
+
+ @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;
@@ -171,10 +214,10 @@
const SimpleTypeWith1Argument(this.typeArg);
@override
- bool get couldBeExpression => false;
+ TypeInfo get asNonNullable => this;
@override
- TypeInfo asNonNullableType() => this;
+ bool get couldBeExpression => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
@@ -185,6 +228,9 @@
parseType(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) => false;
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -210,15 +256,40 @@
}
}
+/// See documentation on the [simpleNullableType] const.
+class SimpleNullableType extends SimpleType {
+ const SimpleNullableType();
+
+ @override
+ TypeInfo get asNonNullable => simpleType;
+
+ @override
+ bool isConditionalExpressionStart(Token token, Parser parser) =>
+ isConditionalThenExpression(skipType(token), parser);
+
+ @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();
@override
- bool get couldBeExpression => true;
+ TypeInfo get asNonNullable => this;
@override
- TypeInfo asNonNullableType() => this;
+ bool get couldBeExpression => true;
@override
Token ensureTypeNotVoid(Token token, Parser parser) =>
@@ -229,6 +300,9 @@
parseType(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) => false;
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -257,10 +331,10 @@
const VoidType();
@override
- bool get couldBeExpression => false;
+ TypeInfo get asNonNullable => this;
@override
- TypeInfo asNonNullableType() => this;
+ bool get couldBeExpression => false;
@override
Token ensureTypeNotVoid(Token token, Parser parser) {
@@ -274,6 +348,9 @@
parseType(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) => false;
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
ensureTypeNotVoid(token, parser);
@@ -337,14 +414,14 @@
: this.start = beforeStart.next;
@override
- bool get couldBeExpression => false;
-
- @override
- TypeInfo asNonNullableType() {
+ TypeInfo get asNonNullable {
return this;
}
@override
+ bool get couldBeExpression => false;
+
+ @override
Token ensureTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -353,6 +430,12 @@
parseType(token, parser);
@override
+ bool isConditionalExpressionStart(Token token, Parser parser) {
+ //return isConditionalThenExpression(token.next.next, parser);
+ return false;
+ }
+
+ @override
Token parseTypeNotVoid(Token token, Parser parser) =>
parseType(token, parser);
@@ -1061,3 +1144,16 @@
}
return null;
}
+
+/// Return `true` if the tokens after [token]
+/// represent a valid expression followed by a `:`.
+bool isConditionalThenExpression(Token token, Parser parser) {
+ // TODO(danrubel): Consider adding checks for simple situations
+ // before resorting to heavy weight lookahead.
+
+ final originalListener = parser.listener;
+ parser.listener = new ForwardingListener();
+ token = parser.parseExpression(token);
+ parser.listener = originalListener;
+ return optional(':', 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 9a0400d..0febbe0 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,7 +937,9 @@
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);