Skip to content

Commit f0377b2

Browse files
danrubelcommit-bot@chromium.org
danrubel
authored andcommitted
Add support for parsing simple nullable types
... as part of adding NNBD as outlined in dart-lang/language#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. Due to current parser limitations, it also only supports nullable types in a limited number of statements such as T? t; if (x is String?) {} In addition, address comments in * https://dart-review.googlesource.com/c/sdk/+/87880 * https://dart-review.googlesource.com/c/sdk/+/87881 Change-Id: Ife4afadc0b72906fc0c4e9b1977ad838041c2a84 Reviewed-on: https://dart-review.googlesource.com/c/87920 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
1 parent aeb8731 commit f0377b2

File tree

8 files changed

+391
-41
lines changed

8 files changed

+391
-41
lines changed

pkg/analyzer/lib/src/dart/analysis/experiments.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ class ExperimentStatus {
9494

9595
/// Initializes a newly created set of experiments based on optional
9696
/// arguments.
97-
ExperimentStatus({bool constant_update_2018, bool set_literals, non_nullable})
97+
ExperimentStatus(
98+
{bool constant_update_2018, bool set_literals, bool non_nullable})
9899
: _enableFlags = <bool>[
99100
constant_update_2018 ?? IsEnabledByDefault.constant_update_2018,
100101
set_literals ?? IsEnabledByDefault.set_literals,

pkg/analyzer/lib/src/fasta/ast_builder.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ class AstBuilder extends StackListener {
967967
@override
968968
void handleType(Token beginToken, Token questionMark) {
969969
debugEvent("Type");
970-
if (!enableNonNullable || showNullableTypeErrors) {
970+
if (showNullableTypeErrors) {
971971
reportErrorIfNullableType(questionMark);
972972
}
973973

@@ -1128,7 +1128,7 @@ class AstBuilder extends StackListener {
11281128
void endFunctionType(Token functionToken, Token questionMark) {
11291129
assert(optional('Function', functionToken));
11301130
debugEvent("FunctionType");
1131-
if (!enableNonNullable || showNullableTypeErrors) {
1131+
if (showNullableTypeErrors) {
11321132
reportErrorIfNullableType(questionMark);
11331133
}
11341134

@@ -2093,14 +2093,16 @@ class AstBuilder extends StackListener {
20932093
List<SimpleIdentifier> libraryName = pop();
20942094
var name = ast.libraryIdentifier(libraryName);
20952095
List<Annotation> metadata = pop();
2096-
if (metadata != null) {
2096+
if (enableNonNullable && metadata != null) {
20972097
for (Annotation annotation in metadata) {
20982098
Identifier pragma = annotation.name;
20992099
if (pragma is SimpleIdentifier && pragma.name == 'pragma') {
21002100
NodeList<Expression> arguments = annotation.arguments.arguments;
21012101
if (arguments.length == 1) {
21022102
Expression tag = arguments[0];
21032103
if (tag is StringLiteral) {
2104+
// TODO(danrubel): handle other flags such as
2105+
// 'analyzer:non-nullable' without the trailing star.
21042106
if (tag.stringValue == 'analyzer:non-nullable*') {
21052107
showNullableTypeErrors = false;
21062108
break;

pkg/analyzer/test/generated/parser_fasta_test.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:analyzer/src/fasta/ast_builder.dart';
1414
import 'package:analyzer/src/generated/parser.dart' as analyzer;
1515
import 'package:analyzer/src/generated/utilities_dart.dart';
1616
import 'package:analyzer/src/string_source.dart';
17+
import 'package:analyzer/src/test_utilities/ast_type_matchers.dart';
1718
import 'package:front_end/src/fasta/parser/parser.dart' as fasta;
1819
import 'package:front_end/src/fasta/parser/forwarding_listener.dart' as fasta;
1920
import 'package:front_end/src/fasta/scanner.dart'
@@ -35,6 +36,7 @@ main() {
3536
defineReflectiveTests(ErrorParserTest_Fasta);
3637
defineReflectiveTests(ExpressionParserTest_Fasta);
3738
defineReflectiveTests(FormalParameterParserTest_Fasta);
39+
defineReflectiveTests(NNBDParserTest_Fasta);
3840
defineReflectiveTests(RecoveryParserTest_Fasta);
3941
defineReflectiveTests(SimpleParserTest_Fasta);
4042
defineReflectiveTests(StatementParserTest_Fasta);
@@ -1420,22 +1422,90 @@ mixin A {
14201422
MixinDeclaration declaration = parseFullCompilationUnitMember();
14211423
expectCommentText(declaration.documentationComment, '/// Doc');
14221424
}
1425+
}
1426+
1427+
/**
1428+
* Tests of the fasta parser based on [ComplexParserTestMixin].
1429+
*/
1430+
@reflectiveTest
1431+
class NNBDParserTest_Fasta extends FastaParserTestCase {
1432+
parseNNBDCompilationUnit(String code, {List<ExpectedError> errors}) {
1433+
createParser('''
1434+
@pragma('analyzer:non-nullable*') library nnbd.parser.test;
1435+
$code
1436+
''');
1437+
_parserProxy.astBuilder.enableNonNullable = true;
1438+
CompilationUnit unit = _parserProxy.parseCompilationUnit2();
1439+
assertNoErrors();
1440+
return unit;
1441+
}
1442+
1443+
void test_is_nullable() {
1444+
CompilationUnit unit =
1445+
parseNNBDCompilationUnit('main() { x is String? ? (x + y) : z; }');
1446+
FunctionDeclaration function = unit.declarations[0];
1447+
BlockFunctionBody body = function.functionExpression.body;
1448+
ExpressionStatement statement = body.block.statements[0];
1449+
ConditionalExpression expression = statement.expression;
1450+
1451+
IsExpression condition = expression.condition;
1452+
expect((condition.type as NamedType).question, isNotNull);
1453+
Expression thenExpression = expression.thenExpression;
1454+
expect(thenExpression, isParenthesizedExpression);
1455+
Expression elseExpression = expression.elseExpression;
1456+
expect(elseExpression, isSimpleIdentifier);
1457+
}
1458+
1459+
void test_is_nullable_parenthesis() {
1460+
CompilationUnit unit =
1461+
parseNNBDCompilationUnit('main() { (x is String?) ? (x + y) : z; }');
1462+
FunctionDeclaration function = unit.declarations[0];
1463+
BlockFunctionBody body = function.functionExpression.body;
1464+
ExpressionStatement statement = body.block.statements[0];
1465+
ConditionalExpression expression = statement.expression;
1466+
1467+
ParenthesizedExpression condition = expression.condition;
1468+
IsExpression isExpression = condition.expression;
1469+
expect((isExpression.type as NamedType).question, isNotNull);
1470+
Expression thenExpression = expression.thenExpression;
1471+
expect(thenExpression, isParenthesizedExpression);
1472+
Expression elseExpression = expression.elseExpression;
1473+
expect(elseExpression, isSimpleIdentifier);
1474+
}
1475+
1476+
void test_simple_assignment() {
1477+
parseNNBDCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
1478+
}
14231479

14241480
void test_pragma_missing() {
14251481
createParser("library foo;");
1482+
_parserProxy.astBuilder.enableNonNullable = true;
14261483
_parserProxy.parseCompilationUnit2();
14271484
expect(_parserProxy.astBuilder.showNullableTypeErrors, true);
14281485
}
14291486

14301487
void test_pragma_non_nullable() {
14311488
createParser("@pragma('analyzer:non-nullable*') library foo;");
1489+
_parserProxy.astBuilder.enableNonNullable = true;
14321490
_parserProxy.parseCompilationUnit2();
14331491
expect(_parserProxy.astBuilder.showNullableTypeErrors, false);
14341492
}
14351493

1494+
void test_pragma_non_nullable_not_enabled() {
1495+
createParser("@pragma('analyzer:non-nullable*') library foo;");
1496+
_parserProxy.parseCompilationUnit2();
1497+
expect(_parserProxy.astBuilder.showNullableTypeErrors, true);
1498+
}
1499+
14361500
void test_pragma_other() {
14371501
createParser("@pragma('analyzer:foo') library foo;");
1502+
_parserProxy.astBuilder.enableNonNullable = true;
14381503
_parserProxy.parseCompilationUnit2();
14391504
expect(_parserProxy.astBuilder.showNullableTypeErrors, true);
14401505
}
1506+
1507+
void test_without_pragma() {
1508+
parseCompilationUnit('main() { x is String? ? (x + y) : z; }',
1509+
errors: [expectedError(ParserErrorCode.UNEXPECTED_TOKEN, 20, 1)]);
1510+
}
14411511
}

pkg/analyzer/test/generated/parser_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1981,7 +1981,7 @@ mixin ComplexParserTestMixin implements AbstractParserTestCase {
19811981
expect(elseExpression, isSimpleIdentifier);
19821982
}
19831983

1984-
void test_conditionalExpression_precedence_nullableType_is() {
1984+
void test_conditionalExpression_precedence_is() {
19851985
ExpressionStatement statement =
19861986
parseStatement('x is String ? (x + y) : z;');
19871987
ConditionalExpression expression = statement.expression;

pkg/front_end/lib/src/fasta/parser/parser.dart

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4500,11 +4500,7 @@ class Parser {
45004500
Token name = beforeName.next;
45014501
if (name.isIdentifier) {
45024502
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(name);
4503-
Token next = name;
4504-
if (typeParam != noTypeParamOrArg) {
4505-
next = typeParam.skip(next);
4506-
}
4507-
next = next.next;
4503+
Token next = typeParam.skip(name).next;
45084504
if (optional('(', next)) {
45094505
if (looksLikeFunctionBody(next.endGroup.next)) {
45104506
return parseFunctionLiteral(
@@ -4886,8 +4882,10 @@ class Parser {
48864882
if (optional('!', token.next)) {
48874883
not = token = token.next;
48884884
}
4889-
// Ignore trailing `?` if there is one as it may be part of an expression
4890-
TypeInfo typeInfo = computeType(token, true).asNonNullableType();
4885+
TypeInfo typeInfo = computeType(token, true);
4886+
if (typeInfo.isConditionalExpressionStart(token, this)) {
4887+
typeInfo = typeInfo.asNonNullable;
4888+
}
48914889
token = typeInfo.ensureTypeNotVoid(token, this);
48924890
listener.handleIsOperator(operator, not);
48934891
return skipChainedAsIsOperators(token);
@@ -4901,8 +4899,10 @@ class Parser {
49014899
Token parseAsOperatorRest(Token token) {
49024900
Token operator = token = token.next;
49034901
assert(optional('as', operator));
4904-
// Ignore trailing `?` if there is one as it may be part of an expression
4905-
TypeInfo typeInfo = computeType(token, true).asNonNullableType();
4902+
TypeInfo typeInfo = computeType(token, true);
4903+
if (typeInfo.isConditionalExpressionStart(token, this)) {
4904+
typeInfo = typeInfo.asNonNullable;
4905+
}
49064906
token = typeInfo.ensureTypeNotVoid(token, this);
49074907
listener.handleAsOperator(operator);
49084908
return skipChainedAsIsOperators(token);
@@ -4921,7 +4921,11 @@ class Parser {
49214921
if (optional('!', next.next)) {
49224922
next = next.next;
49234923
}
4924-
token = computeType(next, true).skipType(next);
4924+
TypeInfo typeInfo = computeType(next, true);
4925+
if (typeInfo.isConditionalExpressionStart(next, this)) {
4926+
typeInfo = typeInfo.asNonNullable;
4927+
}
4928+
token = typeInfo.skipType(next);
49254929
next = token.next;
49264930
value = next.stringValue;
49274931
}
@@ -5042,6 +5046,10 @@ class Parser {
50425046
TypeInfo typeInfo,
50435047
bool onlyParseVariableDeclarationStart = false]) {
50445048
typeInfo ??= computeType(beforeType, false);
5049+
if (typeInfo.isConditionalExpressionStart(beforeType, this)) {
5050+
typeInfo = noType;
5051+
}
5052+
50455053
Token token = typeInfo.skipType(beforeType);
50465054
Token next = token.next;
50475055

pkg/front_end/lib/src/fasta/parser/type_info.dart

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import 'util.dart' show isOneOf, optional;
1919
abstract class TypeInfo {
2020
/// Return type info representing the receiver without the trailing `?`
2121
/// or the receiver if the receiver does not represent a nullable type.
22-
TypeInfo asNonNullableType();
22+
TypeInfo get asNonNullable;
2323

2424
/// Return `true` if the tokens comprising the type represented by the
2525
/// receiver could be interpreted as a valid standalone expression.
26-
/// For example, `A` or `A.b` could be interpreted as a type references
27-
/// or as expressions, while `A<T>` only looks like a type reference.
26+
/// For example, `A` or `A.b` could be interpreted as type references
27+
/// or expressions, while `A<T>` only looks like a type reference.
2828
bool get couldBeExpression;
2929

3030
/// Call this function when the token after [token] must be a type (not void).
@@ -41,6 +41,13 @@ abstract class TypeInfo {
4141
/// in valid code or during recovery.
4242
Token ensureTypeOrVoid(Token token, Parser parser);
4343

44+
/// Return `true` if the tokens comprising the type represented by the
45+
/// receiver are the start of a conditional expression.
46+
/// For example, `A?` or `A.b?` could be the start of a conditional expression
47+
/// and require arbitrary look ahead to determine if it is,
48+
/// while `A<T>?` cannot be the start of a conditional expression.
49+
bool isConditionalExpressionStart(Token token, Parser parser);
50+
4451
/// Call this function to parse an optional type (not void) after [token].
4552
/// This function will call the appropriate event methods on the [Parser]'s
4653
/// listener to handle the type. This may modify the token stream
@@ -199,13 +206,19 @@ TypeInfo computeType(final Token token, bool required,
199206
// We've seen identifier `<` identifier `>`
200207
next = typeParamOrArg.skip(next).next;
201208
if (!isGeneralizedFunctionType(next)) {
202-
if (required || looksLikeName(next)) {
203-
// identifier `<` identifier `>` identifier
204-
return typeParamOrArg.typeInfo;
209+
if (optional('?', next) && typeParamOrArg == simpleTypeArgument1) {
210+
if (required || looksLikeName(next.next)) {
211+
// identifier `<` identifier `>` `?` identifier
212+
return simpleNullableTypeWith1Argument;
213+
}
205214
} else {
206-
// identifier `<` identifier `>` non-identifier
207-
return noType;
215+
if (required || looksLikeName(next)) {
216+
// identifier `<` identifier `>` identifier
217+
return typeParamOrArg.typeInfo;
218+
}
208219
}
220+
// identifier `<` identifier `>` non-identifier
221+
return noType;
209222
}
210223
}
211224
// TODO(danrubel): Consider adding a const for
@@ -255,7 +268,21 @@ TypeInfo computeType(final Token token, bool required,
255268
.computeIdentifierGFT(required);
256269
}
257270

258-
if (required || looksLikeName(next)) {
271+
if (optional('?', next)) {
272+
if (required) {
273+
// identifier `?`
274+
return simpleNullableType;
275+
} else {
276+
next = next.next;
277+
if (isGeneralizedFunctionType(next)) {
278+
// identifier `?` Function `(`
279+
return simpleNullableType;
280+
} else if (looksLikeName(next)) {
281+
// identifier `?` identifier `=`
282+
return simpleNullableType;
283+
}
284+
}
285+
} else if (required || looksLikeName(next)) {
259286
// identifier identifier
260287
return simpleType;
261288
}

0 commit comments

Comments
 (0)