Update AstBuilder to build AST with nullable types
... as part of adding NNBD as outlined in
https://github.com/dart-lang/language/issues/110
This CL updates the parser to recognize two specialized comments at the
beginning of a library of the form '//@NNBD' and '//@NNBD*' for use
by the analyzer to aid developers when converting their libraries
to be non-nullable by default.
In addition, the AstBuilder now populates the generated analyzer AST
with nullable type information.
Change-Id: I80d221dd138973aa32f05bde631245d9ac6ee10f
Reviewed-on: https://dart-review.googlesource.com/c/87540
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index a83282c..f03b601 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -96,6 +96,8 @@
bool parseFunctionBodies = true;
+ bool showNullableTypeErrors = true;
+
AstBuilder(ErrorReporter errorReporter, this.fileUri, this.isFullAst,
[Uri uri])
: this.errorReporter = new FastaErrorReporter(errorReporter),
@@ -263,6 +265,11 @@
scriptTag = ast.scriptTag(token);
}
+ @override
+ void handleNNBD(bool isStar) {
+ showNullableTypeErrors = !isStar;
+ }
+
void handleStringJuxtaposition(int literalCount) {
debugEvent("StringJuxtaposition");
@@ -962,11 +969,13 @@
@override
void handleType(Token beginToken, Token questionMark) {
debugEvent("Type");
- reportErrorIfNullableType(questionMark);
+ if (showNullableTypeErrors) {
+ reportErrorIfNullableType(questionMark);
+ }
TypeArgumentList arguments = pop();
Identifier name = pop();
- push(ast.typeName(name, arguments));
+ push(ast.typeName(name, arguments, question: questionMark));
}
@override
@@ -1121,13 +1130,16 @@
void endFunctionType(Token functionToken, Token questionMark) {
assert(optional('Function', functionToken));
debugEvent("FunctionType");
- reportErrorIfNullableType(questionMark);
+ if (showNullableTypeErrors) {
+ reportErrorIfNullableType(questionMark);
+ }
FormalParameterList parameters = pop();
TypeAnnotation returnType = pop();
TypeParameterList typeParameters = pop();
push(ast.genericFunctionType(
- returnType, functionToken, typeParameters, parameters));
+ returnType, functionToken, typeParameters, parameters,
+ question: questionMark));
}
void handleFormalParameterWithoutValue(Token token) {
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index f272335..ced00a1 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -15,6 +15,7 @@
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/string_source.dart';
import 'package:front_end/src/fasta/parser/parser.dart' as fasta;
+import 'package:front_end/src/fasta/parser/forwarding_listener.dart' as fasta;
import 'package:front_end/src/fasta/scanner.dart'
show ScannerResult, scanString;
import 'package:front_end/src/fasta/scanner/error_token.dart' show ErrorToken;
@@ -1419,4 +1420,41 @@
MixinDeclaration declaration = parseFullCompilationUnitMember();
expectCommentText(declaration.documentationComment, '/// Doc');
}
+
+ void test_parseNNDB() {
+ void testNNBD(String comments, {bool nnbd, bool star}) {
+ final listener = new NNBDTestListener();
+ String code = '''
+$comments
+main() {}''';
+ ScannerResult result = scanString(code, includeComments: true);
+ new fasta.Parser(listener).parseUnit(result.tokens);
+
+ expect(listener.isNNBD, nnbd ?? isNull);
+ expect(listener.isStar, star ?? isNull);
+ }
+
+ testNNBD('', nnbd: false);
+ testNNBD('#!/bin/dart', nnbd: false);
+ testNNBD('//@NNBDx', nnbd: false);
+ testNNBD('//@NNBD', nnbd: true, star: false);
+ testNNBD('// @NNBD', nnbd: true, star: false);
+ testNNBD('//@NNBD*', nnbd: true, star: true);
+ testNNBD('// @NNBD*', nnbd: true, star: true);
+ testNNBD('''#!/bin/dart
+// @NNBD*
+/* more comments */
+/// and dartdoc''', nnbd: true, star: true);
+ }
+}
+
+class NNBDTestListener extends fasta.ForwardingListener {
+ bool isNNBD = false;
+ bool isStar;
+
+ @override
+ void handleNNBD(bool isStar) {
+ this.isNNBD = true;
+ this.isStar = isStar;
+ }
}
diff --git a/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart b/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
index c789a59..7c7c465 100644
--- a/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/forwarding_listener.dart
@@ -1205,6 +1205,11 @@
}
@override
+ void handleNNBD(bool isStar) {
+ listener?.handleNNBD(isStar);
+ }
+
+ @override
void handleNoFieldInitializer(Token token) {
listener?.handleNoFieldInitializer(token);
}
diff --git a/pkg/front_end/lib/src/fasta/parser/listener.dart b/pkg/front_end/lib/src/fasta/parser/listener.dart
index aba67e3..1a3856e 100644
--- a/pkg/front_end/lib/src/fasta/parser/listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/listener.dart
@@ -1357,4 +1357,13 @@
/// This event is generated by the parser when the parser's
/// `parseOneCommentReference` method is called.
void handleNoCommentReference() {}
+
+ /// A comment of the form `//@NNBD` or `//@NNBD*` has been encountered
+ /// at the beginning of the file indicating that this particular library
+ /// is in the process of being converted to non-nullable by default.
+ ///
+ /// If [isStar] is `true`, then the library should be considered
+ /// old style nullable but the `?` indicating that a type is nullable
+ /// should be ignored and no errors generated if they are present.
+ void handleNNBD(bool isStar) {}
}
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 8b0ff26..08ddb44 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -15,6 +15,7 @@
ASSIGNMENT_PRECEDENCE,
BeginToken,
CASCADE_PRECEDENCE,
+ CommentToken,
EQUALITY_PRECEDENCE,
Keyword,
POSTFIX_PRECEDENCE,
@@ -343,6 +344,11 @@
int count = 0;
DirectiveContext directiveState = new DirectiveContext();
token = syntheticPreviousToken(token);
+ if (identical(token.next.type, TokenType.SCRIPT_TAG)) {
+ directiveState?.checkScriptTag(this, token.next);
+ token = parseScript(token);
+ }
+ parseNNBD(token);
while (!token.next.isEof) {
final Token start = token.next;
token = parseTopLevelDeclarationImpl(token, directiveState);
@@ -369,6 +375,24 @@
return token;
}
+ /// Look for a comment of the form `//@NNBD` or `//@NNBD*`
+ /// indicating that this file is in process of being converted
+ /// to be non-nullable by default.
+ void parseNNBD(Token token) {
+ Token comment = token.next.precedingComments;
+ if (comment is CommentToken) {
+ String text = comment.lexeme;
+ if (text.startsWith('//')) {
+ text = text.substring(2).trim();
+ if (text == '@NNBD') {
+ listener.handleNNBD(false);
+ } else if (text == '@NNBD*') {
+ listener.handleNNBD(true);
+ }
+ }
+ }
+ }
+
/// This method exists for analyzer compatibility only
/// and will be removed once analyzer/fasta integration is complete.
///
@@ -459,10 +483,6 @@
/// ```
Token parseTopLevelDeclarationImpl(
Token token, DirectiveContext directiveState) {
- if (identical(token.next.type, TokenType.SCRIPT_TAG)) {
- directiveState?.checkScriptTag(this, token.next);
- return parseScript(token);
- }
token = parseMetadataStar(token);
Token next = token.next;
if (next.isTopLevelKeyword) {