Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d92341b

Browse files
author
Dan Rubel
committed
first cut parse extension methods
This is an incomplete parser implementation of extension methods. Subsequent CLs will flesh out the details. Change-Id: I8c891827e97b451388cadc9cedea3afb7a8cf922 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103464 Reviewed-by: Brian Wilkerson <[email protected]>
1 parent df95340 commit d92341b

File tree

9 files changed

+253
-55
lines changed

9 files changed

+253
-55
lines changed

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

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import 'package:analyzer/src/dart/analysis/experiments.dart';
1212
import 'package:analyzer/src/dart/ast/ast.dart'
1313
show
1414
ClassDeclarationImpl,
15-
ClassOrMixinDeclarationImpl,
1615
CompilationUnitImpl,
16+
ExtensionDeclarationImpl,
1717
MixinDeclarationImpl;
1818
import 'package:analyzer/src/fasta/error_converter.dart';
1919
import 'package:analyzer/src/generated/utilities_dart.dart';
@@ -86,6 +86,9 @@ class AstBuilder extends StackListener {
8686
/// The mixin currently being parsed, or `null` if no mixin is being parsed.
8787
MixinDeclarationImpl mixinDeclaration;
8888

89+
/// The extension currently being parsed, or `null` if none.
90+
ExtensionDeclarationImpl extensionDeclaration;
91+
8992
/// If true, this is building a full AST. Otherwise, only create method
9093
/// bodies.
9194
final bool isFullAst;
@@ -127,6 +130,26 @@ class AstBuilder extends StackListener {
127130
: this.errorReporter = new FastaErrorReporter(errorReporter),
128131
uri = uri ?? fileUri;
129132

133+
NodeList<ClassMember> get currentDeclarationMembers {
134+
if (classDeclaration != null) {
135+
return classDeclaration.members;
136+
} else if (mixinDeclaration != null) {
137+
return mixinDeclaration.members;
138+
} else {
139+
return extensionDeclaration.members;
140+
}
141+
}
142+
143+
SimpleIdentifier get currentDeclarationName {
144+
if (classDeclaration != null) {
145+
return classDeclaration.name;
146+
} else if (mixinDeclaration != null) {
147+
return mixinDeclaration.name;
148+
} else {
149+
return extensionDeclaration.name;
150+
}
151+
}
152+
130153
@override
131154
void addProblem(Message message, int charOffset, int length,
132155
{bool wasHandled: false, List<LocatedMessage> context}) {
@@ -155,7 +178,9 @@ class AstBuilder extends StackListener {
155178

156179
@override
157180
void beginClassDeclaration(Token begin, Token abstractToken, Token name) {
158-
assert(classDeclaration == null && mixinDeclaration == null);
181+
assert(classDeclaration == null &&
182+
mixinDeclaration == null &&
183+
extensionDeclaration == null);
159184
push(new _Modifiers()..abstractKeyword = abstractToken);
160185
}
161186

@@ -164,6 +189,30 @@ class AstBuilder extends StackListener {
164189
push(token);
165190
}
166191

192+
@override
193+
void beginExtensionDeclaration(Token extensionKeyword, Token nameToken) {
194+
assert(optional('extension', extensionKeyword));
195+
assert(classDeclaration == null &&
196+
mixinDeclaration == null &&
197+
extensionDeclaration == null);
198+
debugEvent("ExtensionHeader");
199+
200+
TypeParameterList typeParameters = pop();
201+
SimpleIdentifier name = pop();
202+
List<Annotation> metadata = pop();
203+
Comment comment = _findComment(metadata, extensionKeyword);
204+
205+
extensionDeclaration = ast.extensionDeclaration(
206+
comment: comment,
207+
metadata: metadata,
208+
name: name,
209+
typeParameters: typeParameters,
210+
extendedType: null, // extendedType is set in [endExtensionDeclaration]
211+
) as ExtensionDeclarationImpl;
212+
213+
declarations.add(extensionDeclaration);
214+
}
215+
167216
@override
168217
void beginFactoryMethod(
169218
Token lastConsumed, Token externalToken, Token constToken) {
@@ -234,7 +283,9 @@ class AstBuilder extends StackListener {
234283

235284
@override
236285
void beginMixinDeclaration(Token mixinKeyword, Token name) {
237-
assert(classDeclaration == null && mixinDeclaration == null);
286+
assert(classDeclaration == null &&
287+
mixinDeclaration == null &&
288+
extensionDeclaration == null);
238289
}
239290

240291
@override
@@ -481,14 +532,25 @@ class AstBuilder extends StackListener {
481532
@override
482533
void endClassOrMixinBody(
483534
int memberCount, Token leftBracket, Token rightBracket) {
535+
// TODO(danrubel): consider renaming endClassOrMixinBody
536+
// to endClassOrMixinOrExtensionBody
484537
assert(optional('{', leftBracket));
485538
assert(optional('}', rightBracket));
486539
debugEvent("ClassOrMixinBody");
487540

488-
ClassOrMixinDeclarationImpl declaration =
489-
classDeclaration ?? mixinDeclaration;
490-
declaration.leftBracket = leftBracket;
491-
declaration.rightBracket = rightBracket;
541+
if (classDeclaration != null) {
542+
classDeclaration
543+
..leftBracket = leftBracket
544+
..rightBracket = rightBracket;
545+
} else if (mixinDeclaration != null) {
546+
mixinDeclaration
547+
..leftBracket = leftBracket
548+
..rightBracket = rightBracket;
549+
} else {
550+
extensionDeclaration
551+
..leftBracket = leftBracket
552+
..rightBracket = rightBracket;
553+
}
492554
}
493555

494556
@override
@@ -642,6 +704,15 @@ class AstBuilder extends StackListener {
642704
configurations, combinators, semicolon));
643705
}
644706

707+
@override
708+
void endExtensionDeclaration(Token onKeyword, Token token) {
709+
TypeAnnotation type = pop();
710+
extensionDeclaration
711+
..extendedType = type
712+
..onKeyword = onKeyword;
713+
extensionDeclaration = null;
714+
}
715+
645716
@override
646717
void endFactoryMethod(
647718
Token beginToken, Token factoryKeyword, Token endToken) {
@@ -694,21 +765,20 @@ class AstBuilder extends StackListener {
694765
ast.simpleIdentifier(typeName.identifier.token, isDeclaration: true);
695766
}
696767

697-
(classDeclaration ?? mixinDeclaration).members.add(
698-
ast.constructorDeclaration(
699-
comment,
700-
metadata,
701-
modifiers?.externalKeyword,
702-
modifiers?.finalConstOrVarKeyword,
703-
factoryKeyword,
704-
ast.simpleIdentifier(returnType.token),
705-
period,
706-
name,
707-
parameters,
708-
separator,
709-
null,
710-
redirectedConstructor,
711-
body));
768+
currentDeclarationMembers.add(ast.constructorDeclaration(
769+
comment,
770+
metadata,
771+
modifiers?.externalKeyword,
772+
modifiers?.finalConstOrVarKeyword,
773+
factoryKeyword,
774+
ast.simpleIdentifier(returnType.token),
775+
period,
776+
name,
777+
parameters,
778+
separator,
779+
null,
780+
redirectedConstructor,
781+
body));
712782
}
713783

714784
void endFieldInitializer(Token assignment, Token token) {
@@ -737,7 +807,7 @@ class AstBuilder extends StackListener {
737807
Token covariantKeyword = covariantToken;
738808
List<Annotation> metadata = pop();
739809
Comment comment = _findComment(metadata, beginToken);
740-
(classDeclaration ?? mixinDeclaration).members.add(ast.fieldDeclaration2(
810+
currentDeclarationMembers.add(ast.fieldDeclaration2(
741811
comment: comment,
742812
metadata: metadata,
743813
covariantKeyword: covariantKeyword,
@@ -1415,9 +1485,6 @@ class AstBuilder extends StackListener {
14151485
beginToken.charOffset, uri);
14161486
}
14171487

1418-
ClassOrMixinDeclarationImpl declaration =
1419-
classDeclaration ?? mixinDeclaration;
1420-
14211488
void constructor(
14221489
SimpleIdentifier prefixOrName, Token period, SimpleIdentifier name) {
14231490
if (typeParameters != null) {
@@ -1453,7 +1520,7 @@ class AstBuilder extends StackListener {
14531520
initializers,
14541521
redirectedConstructor,
14551522
body);
1456-
declaration.members.add(constructor);
1523+
currentDeclarationMembers.add(constructor);
14571524
if (mixinDeclaration != null) {
14581525
// TODO (danrubel): Report an error if this is a mixin declaration.
14591526
}
@@ -1466,7 +1533,7 @@ class AstBuilder extends StackListener {
14661533
messageConstMethod, modifiers.constKeyword, modifiers.constKeyword);
14671534
}
14681535
checkFieldFormalParameters(parameters);
1469-
declaration.members.add(ast.methodDeclaration(
1536+
currentDeclarationMembers.add(ast.methodDeclaration(
14701537
comment,
14711538
metadata,
14721539
modifiers?.externalKeyword,
@@ -1481,7 +1548,7 @@ class AstBuilder extends StackListener {
14811548
}
14821549

14831550
if (name is SimpleIdentifier) {
1484-
if (name.name == declaration.name.name && getOrSet == null) {
1551+
if (name.name == currentDeclarationName.name && getOrSet == null) {
14851552
constructor(name, null, null);
14861553
} else if (initializers.isNotEmpty && getOrSet == null) {
14871554
constructor(name, null, null);
@@ -2683,7 +2750,9 @@ class AstBuilder extends StackListener {
26832750
@override
26842751
void handleMixinHeader(Token mixinKeyword) {
26852752
assert(optional('mixin', mixinKeyword));
2686-
assert(classDeclaration == null && mixinDeclaration == null);
2753+
assert(classDeclaration == null &&
2754+
mixinDeclaration == null &&
2755+
extensionDeclaration == null);
26872756
debugEvent("MixinHeader");
26882757

26892758
ImplementsClause implementsClause = pop(NullValue.IdentifierList);

pkg/analyzer/test/generated/parser_fasta_listener.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ class ForwardingTestListener extends ForwardingListener {
200200
begin('Export');
201201
}
202202

203+
@override
204+
void beginExtensionDeclaration(Token extensionKeyword, Token name) {
205+
super.beginExtensionDeclaration(extensionKeyword, name);
206+
begin('ExtensionDeclaration');
207+
}
208+
203209
@override
204210
void beginFactoryMethod(
205211
Token lastConsumed, Token externalToken, Token constToken) {
@@ -689,6 +695,12 @@ class ForwardingTestListener extends ForwardingListener {
689695
super.endExport(exportKeyword, semicolon);
690696
}
691697

698+
@override
699+
void endExtensionDeclaration(Token onKeyword, Token token) {
700+
super.endExtensionDeclaration(onKeyword, token);
701+
end('ExtensionDeclaration');
702+
}
703+
692704
@override
693705
void endFactoryMethod(
694706
Token beginToken, Token factoryKeyword, Token endToken) {

pkg/analyzer/test/generated/parser_fasta_test.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import 'test_support.dart';
3636
main() {
3737
defineReflectiveSuite(() {
3838
defineReflectiveTests(ClassMemberParserTest_Fasta);
39+
defineReflectiveTests(ExtensionMethodsParserTest_Fasta);
3940
defineReflectiveTests(CollectionLiteralParserTest);
4041
defineReflectiveTests(ComplexParserTest_Fasta);
4142
defineReflectiveTests(ErrorParserTest_Fasta);
@@ -1435,6 +1436,43 @@ class ExpressionParserTest_Fasta extends FastaParserTestCase
14351436
}
14361437
}
14371438

1439+
@reflectiveTest
1440+
class ExtensionMethodsParserTest_Fasta extends FastaParserTestCase {
1441+
@override
1442+
CompilationUnit parseCompilationUnit(String content,
1443+
{List<ErrorCode> codes,
1444+
List<ExpectedError> errors,
1445+
FeatureSet featureSet}) {
1446+
return super.parseCompilationUnit(content,
1447+
codes: codes,
1448+
errors: errors,
1449+
featureSet: featureSet ??
1450+
FeatureSet.forTesting(
1451+
sdkVersion: '2.3.0',
1452+
additionalFeatures: [Feature.extension_methods],
1453+
));
1454+
}
1455+
1456+
void test_simple() {
1457+
var unit = parseCompilationUnit('extension E on C { }');
1458+
expect(unit.declarations, hasLength(1));
1459+
var extension = unit.declarations[0] as ExtensionDeclaration;
1460+
expect(extension.name.name, 'E');
1461+
expect(extension.onKeyword.lexeme, 'on');
1462+
expect((extension.extendedType as NamedType).name.name, 'C');
1463+
expect(extension.members, hasLength(0));
1464+
}
1465+
1466+
void test_simple_not_enabled() {
1467+
parseCompilationUnit('extension E on C { }',
1468+
errors: [
1469+
expectedError(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 0, 9),
1470+
expectedError(ParserErrorCode.MISSING_FUNCTION_PARAMETERS, 15, 1)
1471+
],
1472+
featureSet: FeatureSet.forTesting(sdkVersion: '2.3.0'));
1473+
}
1474+
}
1475+
14381476
/**
14391477
* Implementation of [AbstractParserTestCase] specialized for testing the
14401478
* Fasta parser.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ class ForwardingListener implements Listener {
145145
listener?.beginExport(token);
146146
}
147147

148+
@override
149+
void beginExtensionDeclaration(Token extensionKeyword, Token name) {
150+
listener?.beginExtensionDeclaration(extensionKeyword, name);
151+
}
152+
148153
@override
149154
void beginFactoryMethod(
150155
Token lastConsumed, Token externalToken, Token constToken) {
@@ -581,6 +586,11 @@ class ForwardingListener implements Listener {
581586
listener?.endExport(exportKeyword, semicolon);
582587
}
583588

589+
@override
590+
void endExtensionDeclaration(Token onKeyword, Token token) {
591+
listener?.endExtensionDeclaration(onKeyword, token);
592+
}
593+
584594
@override
585595
void endFactoryMethod(
586596
Token beginToken, Token factoryKeyword, Token endToken) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ class IdentifierContext {
9898
/// Identifier is the name being declared by a class declaration, a mixin
9999
/// declaration, or a named mixin application, for example,
100100
/// `Foo` in `class Foo = X with Y;`.
101-
static const classOrMixinDeclaration = const ClassOrMixinIdentifierContext();
101+
static const classOrMixinOrExtensionDeclaration =
102+
const ClassOrMixinOrExtensionIdentifierContext();
102103

103104
/// Identifier is the name of a type variable being declared (e.g. `Foo` in
104105
/// `class C<Foo extends num> {}`).

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ class CatchParameterIdentifierContext extends IdentifierContext {
4343
}
4444
}
4545

46-
/// See [IdentifierContext.classOrMixinDeclaration].
47-
class ClassOrMixinIdentifierContext extends IdentifierContext {
48-
const ClassOrMixinIdentifierContext()
46+
/// See [IdentifierContext.classOrMixinOrExtensionDeclaration].
47+
class ClassOrMixinOrExtensionIdentifierContext extends IdentifierContext {
48+
const ClassOrMixinOrExtensionIdentifierContext()
4949
: super('classOrMixinDeclaration',
5050
inDeclaration: true, isBuiltInIdentifierAllowed: false);
5151

@@ -59,8 +59,8 @@ class ClassOrMixinIdentifierContext extends IdentifierContext {
5959

6060
// Recovery
6161
if (looksLikeStartOfNextTopLevelDeclaration(identifier) ||
62-
isOneOfOrEof(
63-
identifier, const ['<', '{', 'extends', 'with', 'implements'])) {
62+
isOneOfOrEof(identifier,
63+
const ['<', '{', 'extends', 'with', 'implements', 'on'])) {
6464
identifier = parser.insertSyntheticIdentifier(token, this,
6565
message: fasta.templateExpectedIdentifier.withArguments(identifier));
6666
} else if (identifier.type.isBuiltIn) {

0 commit comments

Comments
 (0)