make extension declaration name optional
This CL updates the parser to parse extension declarations where
the extension name is omitted. For more details, see
https://github.com/dart-lang/language/pull/303/files#diff-9b84d2e3c88265bde2eb43544ddb757dR66
This change explicitly does not handle the case `extension on on on { … }`.
A subsequent CL should either make `on` a builtin
or add more lookahead in the parser to allow this edge case.
Change-Id: Ided821192770f88a51a3300fed8cb0fdef9eea3f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105900
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 73b6a7c..e9c51f7 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -198,10 +198,14 @@
debugEvent("ExtensionHeader");
TypeParameterList typeParameters = pop();
- SimpleIdentifier name = pop();
List<Annotation> metadata = pop();
Comment comment = _findComment(metadata, extensionKeyword);
+ SimpleIdentifier name;
+ if (nameToken != null) {
+ name = ast.simpleIdentifier(nameToken);
+ }
+
extensionDeclaration = ast.extensionDeclaration(
comment: comment,
metadata: metadata,
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index 291c7ff..d4e9461 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -1495,6 +1495,30 @@
expect(extension.members, hasLength(0));
}
+ void test_complex_type2() {
+ var unit = parseCompilationUnit('extension E<T> on C<T> { }');
+ expect(unit.declarations, hasLength(1));
+ var extension = unit.declarations[0] as ExtensionDeclaration;
+ expect(extension.name.name, 'E');
+ expect(extension.onKeyword.lexeme, 'on');
+ var namedType = (extension.extendedType as NamedType);
+ expect(namedType.name.name, 'C');
+ expect(namedType.typeArguments.arguments, hasLength(1));
+ expect(extension.members, hasLength(0));
+ }
+
+ void test_complex_type2_no_name() {
+ var unit = parseCompilationUnit('extension<T> on C<T> { }');
+ expect(unit.declarations, hasLength(1));
+ var extension = unit.declarations[0] as ExtensionDeclaration;
+ expect(extension.name, isNull);
+ expect(extension.onKeyword.lexeme, 'on');
+ var namedType = (extension.extendedType as NamedType);
+ expect(namedType.name.name, 'C');
+ expect(namedType.typeArguments.arguments, hasLength(1));
+ expect(extension.members, hasLength(0));
+ }
+
void test_missing_on() {
var unit = parseCompilationUnit('extension E', errors: [
expectedError(ParserErrorCode.EXPECTED_TOKEN, 10, 1),
@@ -1571,6 +1595,19 @@
expect(extension.members, hasLength(0));
}
+ void test_simple_no_name() {
+ var unit = parseCompilationUnit('extension on C { }');
+ expect(unit.declarations, hasLength(1));
+ var extension = unit.declarations[0] as ExtensionDeclaration;
+ expect(extension.name, isNull);
+ expect(extension.onKeyword.lexeme, 'on');
+ expect((extension.extendedType as NamedType).name.name, 'C');
+ var namedType = (extension.extendedType as NamedType);
+ expect(namedType.name.name, 'C');
+ expect(namedType.typeArguments, isNull);
+ expect(extension.members, hasLength(0));
+ }
+
void test_simple_not_enabled() {
parseCompilationUnit('extension E on C { }',
errors: [
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/extension_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/extension_declaration_test.dart
index e04eadd..ad7ff9a 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/extension_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/extension_declaration_test.dart
@@ -20,13 +20,19 @@
'keyword',
'extension',
[
- ParserErrorCode.MISSING_IDENTIFIER,
ParserErrorCode.EXPECTED_TOKEN,
ParserErrorCode.EXPECTED_TYPE_NAME,
ParserErrorCode.EXPECTED_BODY,
],
- 'extension _s_ on _s_ {}',
- failing: ['getter', 'functionNonVoid', 'functionVoid', 'mixin']),
+ 'extension on _s_ {}',
+ failing: [
+ 'getter',
+ 'functionNonVoid',
+ 'functionVoid',
+ 'mixin',
+ 'setter',
+ 'typedef'
+ ]),
new TestDescriptor(
'named',
'extension E',
diff --git a/pkg/front_end/lib/src/fasta/parser/listener.dart b/pkg/front_end/lib/src/fasta/parser/listener.dart
index 63d333f..e92f6f3 100644
--- a/pkg/front_end/lib/src/fasta/parser/listener.dart
+++ b/pkg/front_end/lib/src/fasta/parser/listener.dart
@@ -181,7 +181,6 @@
/// Handle the beginning of an extension methods declaration. Substructures:
/// - metadata
- /// - extension name
/// - type variables
void beginExtensionDeclaration(Token extensionKeyword, Token name) {}
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 3583dc6..8a74320 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -578,9 +578,19 @@
// as an identifier such as "abstract<T>() => 0;"
// or as a prefix such as "abstract.A b() => 0;".
String nextValue = keyword.next.stringValue;
- if (identical(nextValue, '(') ||
- identical(nextValue, '<') ||
- identical(nextValue, '.')) {
+ if (identical(nextValue, '(') || identical(nextValue, '.')) {
+ directiveState?.checkDeclaration();
+ return parseTopLevelMemberImpl(start);
+ } else if (identical(nextValue, '<')) {
+ if (identical(value, 'extension')) {
+ // The neame in an extension declaration is optional:
+ // `extension<T> on ...`
+ Token endGroup = keyword.next.endGroup;
+ if (endGroup != null && optional('on', endGroup.next)) {
+ directiveState?.checkDeclaration();
+ return parseExtension(keyword);
+ }
+ }
directiveState?.checkDeclaration();
return parseTopLevelMemberImpl(start);
} else {
@@ -2058,16 +2068,21 @@
}
/// ```
- /// 'extension' <identifier><typeParameters>? 'on' <type> '?'?
+ /// 'extension' <identifier>? <typeParameters>? 'on' <type> '?'?
// `{'
// <memberDeclaration>*
// `}'
/// ```
Token parseExtension(Token extensionKeyword) {
assert(optional('extension', extensionKeyword));
- Token name = ensureIdentifier(
- extensionKeyword, IdentifierContext.classOrMixinOrExtensionDeclaration);
- Token token = computeTypeParamOrArg(name, true).parseVariables(name, this);
+ Token token = extensionKeyword;
+ Token name = token.next;
+ if (name.isIdentifier && !optional('on', name)) {
+ token = name;
+ } else {
+ name = null;
+ }
+ token = computeTypeParamOrArg(token, true).parseVariables(token, this);
listener.beginExtensionDeclaration(extensionKeyword, name);
Token onKeyword = token.next;
if (!optional('on', onKeyword)) {