Skip to content

Commit 02641c8

Browse files
eernstgCommit Queue
authored andcommitted
Transfer parser updates of CL449821 into this CL
Specification proposal: https://github.com/dart-lang/language/blob/main/working/0260-anonymous-methods/feature-specification.md. Change-Id: I57743ec663633a148087c1be36e492e1e45e406e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464704 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Erik Ernst <[email protected]>
1 parent 366f203 commit 02641c8

File tree

56 files changed

+8197
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+8197
-50
lines changed

pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,36 @@ Message _withArgumentsExpectedButGot({required String string}) {
700700
Message _withArgumentsOldExpectedButGot(String string) =>
701701
_withArgumentsExpectedButGot(string: string);
702702

703+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
704+
const Template<
705+
Message Function(String string, String string2),
706+
Message Function({required String string, required String string2})
707+
>
708+
codeExpectedButGot2 = const Template(
709+
"ExpectedButGot2",
710+
withArgumentsOld: _withArgumentsOldExpectedButGot2,
711+
withArguments: _withArgumentsExpectedButGot2,
712+
pseudoSharedCode: PseudoSharedCode.expectedToken,
713+
);
714+
715+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
716+
Message _withArgumentsExpectedButGot2({
717+
required String string,
718+
required String string2,
719+
}) {
720+
var string_0 = conversions.validateString(string);
721+
var string2_0 = conversions.validateString(string2);
722+
return new Message(
723+
codeExpectedButGot2,
724+
problemMessage: """Expected '${string_0}' or '${string2_0}' before this.""",
725+
arguments: {'string': string, 'string2': string2},
726+
);
727+
}
728+
729+
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
730+
Message _withArgumentsOldExpectedButGot2(String string, String string2) =>
731+
_withArgumentsExpectedButGot2(string: string, string2: string2);
732+
703733
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
704734
const MessageCode codeExpectedCatchClauseBody = const MessageCode(
705735
"ExpectedCatchClauseBody",

pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ class ForwardingListener implements Listener {
1313

1414
ForwardingListener([this.listener]);
1515

16+
@override
17+
void beginAnonymousMethodInvocation(Token token) {
18+
listener?.beginAnonymousMethodInvocation(token);
19+
}
20+
1621
@override
1722
void beginArguments(Token token) {
1823
listener?.beginArguments(token);
@@ -1497,6 +1502,26 @@ class ForwardingListener implements Listener {
14971502
listener?.handleAssignmentExpression(token, endToken);
14981503
}
14991504

1505+
@override
1506+
void endAnonymousMethodInvocation(
1507+
Token beginToken,
1508+
Token? functionDefinition,
1509+
Token endToken, {
1510+
required bool isExpression,
1511+
}) {
1512+
listener?.endAnonymousMethodInvocation(
1513+
beginToken,
1514+
functionDefinition,
1515+
endToken,
1516+
isExpression: isExpression,
1517+
);
1518+
}
1519+
1520+
@override
1521+
void handleImplicitFormalParameters(Token token) {
1522+
listener?.handleImplicitFormalParameters(token);
1523+
}
1524+
15001525
@override
15011526
void handleAsyncModifier(Token? asyncToken, Token? starToken) {
15021527
listener?.handleAsyncModifier(asyncToken, starToken);

pkg/_fe_analyzer_shared/lib/src/parser/listener.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,29 @@ abstract class Listener implements UnescapeErrorListener {
17331733
logEvent("AssignmentExpression");
17341734
}
17351735

1736+
void beginAnonymousMethodInvocation(Token token) {}
1737+
1738+
/// Called after the parser has consumed an anonymous method invocation.
1739+
/// Substructures:
1740+
/// - The target of the invocation.
1741+
/// - A formal parameter list (can be implicit, see
1742+
/// [handleImplicitFormalParameters]).
1743+
/// - The body of the anonymous method (either an expression or a block).
1744+
void endAnonymousMethodInvocation(
1745+
Token beginToken,
1746+
Token? functionDefinition,
1747+
Token endToken, {
1748+
required bool isExpression,
1749+
}) {
1750+
logEvent("AnonymousMethodInvocation");
1751+
}
1752+
1753+
/// Called when an anonymous method invocation does not have
1754+
/// an explicit formal parameter list.
1755+
void handleImplicitFormalParameters(Token token) {
1756+
logEvent("ImplicitFormalParameters");
1757+
}
1758+
17361759
/// Called when the parser encounters a binary operator, in between the LHS
17371760
/// and RHS subexpressions.
17381761
///

pkg/_fe_analyzer_shared/lib/src/parser/member_kind.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ enum MemberKind {
2323
/// A local function.
2424
Local,
2525

26+
/// An anonymous method.
27+
AnonymousMethod,
28+
2629
/// A non-static method in a class (including constructors).
2730
NonStaticMethod,
2831

pkg/_fe_analyzer_shared/lib/src/parser/modifier_context.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ class ModifierContext {
215215
switch (memberKind) {
216216
case MemberKind.StaticMethod:
217217
case MemberKind.TopLevelMethod:
218+
case MemberKind.AnonymousMethod:
218219
reportExtraneousModifier(this.covariantToken);
219220
case MemberKind.ExtensionNonStaticMethod:
220221
case MemberKind.ExtensionStaticMethod:

pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart

Lines changed: 152 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ class Parser {
331331
/// `true` if the 'private-named-parameters' feature is enabled.
332332
final bool _isPrivateNamedParametersEnabled;
333333

334+
/// `true` if the 'anonymous-methods' feature is enabled.
335+
final bool _isAnonymousMethodsFeatureEnabled;
336+
334337
/// Indicates whether the last pattern parsed is allowed inside unary
335338
/// patterns. This is set by [parsePrimaryPattern] and [parsePattern].
336339
///
@@ -357,7 +360,9 @@ class Parser {
357360
_isPrimaryConstructorsFeatureEnabled = experimentalFeatures
358361
.isExperimentEnabled(ExperimentalFlag.primaryConstructors),
359362
_isPrivateNamedParametersEnabled = experimentalFeatures
360-
.isExperimentEnabled(ExperimentalFlag.privateNamedParameters);
363+
.isExperimentEnabled(ExperimentalFlag.privateNamedParameters),
364+
_isAnonymousMethodsFeatureEnabled = experimentalFeatures
365+
.isExperimentEnabled(ExperimentalFlag.anonymousMethods);
361366

362367
/// Executes [callback]; however if `this` is the `TestParser` (from
363368
/// `pkg/front_end/test/parser_test_parser.dart`) then no output is printed
@@ -2007,6 +2012,7 @@ class Parser {
20072012
case MemberKind.FunctionTypedParameter:
20082013
case MemberKind.GeneralizedFunctionType:
20092014
case MemberKind.Local:
2015+
case MemberKind.AnonymousMethod:
20102016
case MemberKind.NonStaticField:
20112017
case MemberKind.StaticField:
20122018
case MemberKind.TopLevelField:
@@ -2079,6 +2085,7 @@ class Parser {
20792085
case MemberKind.FunctionTypedParameter:
20802086
case MemberKind.GeneralizedFunctionType:
20812087
case MemberKind.Local:
2088+
case MemberKind.AnonymousMethod:
20822089
case MemberKind.NonStaticMethod:
20832090
case MemberKind.NonStaticField:
20842091
case MemberKind.StaticField:
@@ -7012,49 +7019,55 @@ class Parser {
70127019
} else if (tokenLevel == SELECTOR_PRECEDENCE) {
70137020
if (identical(type, TokenType.PERIOD) ||
70147021
identical(type, TokenType.QUESTION_PERIOD)) {
7015-
// Left associative, so we recurse at the next higher precedence
7016-
// level. However, SELECTOR_PRECEDENCE is the highest level, so we
7017-
// should just call [parseUnaryExpression] directly. However, a
7018-
// unary expression isn't legal after a period, so we call
7019-
// [parsePrimary] instead.
70207022
Token dot = token.next!;
7021-
token = parsePrimary(
7022-
dot,
7023-
IdentifierContext.expressionContinuation,
7024-
constantPatternContext,
7025-
);
7026-
7027-
if (isDotShorthand) {
7028-
listener.handleDotShorthandHead(dot);
7029-
isDotShorthand = false;
7023+
Token afterDot = dot.next!;
7024+
// TODO(eernst): Call reportExperimentNotEnabled to guide user when
7025+
// `_beginsAnonymousMethod(afterDot)`, but experiment disabled.
7026+
if (_isAnonymousMethodsFeatureEnabled &&
7027+
_beginsAnonymousMethod(afterDot)) {
7028+
token = _parseAnonymousMethod(dot, afterDot);
70307029
} else {
7031-
listener.handleDotAccess(
7032-
operator,
7033-
token,
7034-
/* isNullAware = */ identical(type, TokenType.QUESTION_PERIOD),
7030+
// Left associative, so we recurse at the next higher precedence
7031+
// level. However, SELECTOR_PRECEDENCE is the highest level, so we
7032+
// should just call [parseUnaryExpression] directly. However, a
7033+
// unary expression isn't legal after a period, so we call
7034+
// [parsePrimary] instead.
7035+
token = parsePrimary(
7036+
dot,
7037+
IdentifierContext.expressionContinuation,
7038+
constantPatternContext,
70357039
);
7036-
}
7037-
7038-
Token bangToken = token;
7039-
if (token.next!.isA(TokenType.BANG)) {
7040-
bangToken = token.next!;
7041-
}
7042-
typeArg = computeMethodTypeArguments(bangToken);
7043-
if (typeArg != noTypeParamOrArg) {
7044-
// For example e.f<T>(c), where token is before '<'.
7045-
if (bangToken.isA(TokenType.BANG)) {
7046-
listener.handleNonNullAssertExpression(bangToken);
7040+
if (isDotShorthand) {
7041+
listener.handleDotShorthandHead(dot);
7042+
isDotShorthand = false;
7043+
} else {
7044+
listener.handleDotAccess(
7045+
operator,
7046+
token,
7047+
/* isNullAware = */ identical(type, TokenType.QUESTION_PERIOD),
7048+
);
70477049
}
7048-
token = typeArg.parseArguments(bangToken, this);
7049-
if (!token.next!.isA(TokenType.OPEN_PAREN)) {
7050-
if (constantPatternContext != ConstantPatternContext.none) {
7051-
reportRecoverableError(
7052-
bangToken.next!,
7053-
codes.codeInvalidConstantPatternGeneric,
7054-
);
7050+
Token bangToken = token;
7051+
if (token.next!.isA(TokenType.BANG)) {
7052+
bangToken = token.next!;
7053+
}
7054+
typeArg = computeMethodTypeArguments(bangToken);
7055+
if (typeArg != noTypeParamOrArg) {
7056+
// For example e.f<T>(c), where token is before '<'.
7057+
if (bangToken.isA(TokenType.BANG)) {
7058+
listener.handleNonNullAssertExpression(bangToken);
7059+
}
7060+
token = typeArg.parseArguments(bangToken, this);
7061+
if (!token.next!.isA(TokenType.OPEN_PAREN)) {
7062+
if (constantPatternContext != ConstantPatternContext.none) {
7063+
reportRecoverableError(
7064+
bangToken.next!,
7065+
codes.codeInvalidConstantPatternGeneric,
7066+
);
7067+
}
7068+
listener.handleTypeArgumentApplication(bangToken.next!);
7069+
typeArg = noTypeParamOrArg;
70557070
}
7056-
listener.handleTypeArgumentApplication(bangToken.next!);
7057-
typeArg = noTypeParamOrArg;
70587071
}
70597072
}
70607073
} else if (identical(type, TokenType.OPEN_PAREN) ||
@@ -7191,6 +7204,84 @@ class Parser {
71917204
return token;
71927205
}
71937206

7207+
/// Can the next input be an anonymous method?
7208+
///
7209+
/// Used during `_parsePrecedenceExpressionLoop` and
7210+
/// `parseCascadeExpression`.
7211+
///
7212+
/// Should only be invoked in a situation where the input before
7213+
/// [token] is a period or two periods, or a question mark followed by
7214+
/// one or two periods. Returns true if [token] shows that the next
7215+
/// construct to parse can only be an anonymous method.
7216+
bool _beginsAnonymousMethod(Token token) {
7217+
if (token.isA(TokenType.OPEN_CURLY_BRACKET) ||
7218+
token.isA(TokenType.FUNCTION)) {
7219+
return true;
7220+
}
7221+
if (token.isA(TokenType.OPEN_PAREN)) {
7222+
Token? matchingParenthesis = token.endGroup;
7223+
if (matchingParenthesis != null) {
7224+
Token? afterMatch = matchingParenthesis.next;
7225+
if (afterMatch != null &&
7226+
(afterMatch.isA(TokenType.OPEN_CURLY_BRACKET) ||
7227+
afterMatch.isA(TokenType.FUNCTION))) {
7228+
return true;
7229+
}
7230+
}
7231+
}
7232+
return false;
7233+
}
7234+
7235+
/// Parse an anonymous method.
7236+
///
7237+
/// Used during `_parsePrecedenceExpressionLoop` and
7238+
/// `parseCascadeExpression`.
7239+
///
7240+
/// Should only be invoked in a situation where
7241+
/// `_beginsAnonymousMethod(afterPunctuation)` has returned true.
7242+
Token _parseAnonymousMethod(Token punctuation, Token afterPunctuation) {
7243+
Token currentToken;
7244+
listener.beginAnonymousMethodInvocation(punctuation);
7245+
if (afterPunctuation.isA(TokenType.OPEN_PAREN)) {
7246+
currentToken = parseFormalParameters(
7247+
punctuation,
7248+
MemberKind.AnonymousMethod,
7249+
);
7250+
} else {
7251+
listener.handleImplicitFormalParameters(punctuation);
7252+
currentToken = punctuation;
7253+
}
7254+
Token afterParameters = currentToken.next!;
7255+
Token? functionDefinition = null;
7256+
final bool isExpression;
7257+
if (afterParameters.isA(TokenType.OPEN_CURLY_BRACKET)) {
7258+
isExpression = false;
7259+
currentToken = parseBlock(currentToken, BlockKind.functionBody);
7260+
} else if (afterParameters.isA(TokenType.FUNCTION)) {
7261+
isExpression = true;
7262+
functionDefinition = afterParameters;
7263+
currentToken = parseExpressionWithoutCascade(afterParameters);
7264+
} else {
7265+
reportRecoverableError(
7266+
afterParameters,
7267+
codes.codeExpectedButGot2.withArgumentsOld("{", "=>"),
7268+
);
7269+
functionDefinition = rewriter.insertSyntheticToken(
7270+
currentToken,
7271+
TokenType.FUNCTION,
7272+
);
7273+
isExpression = true;
7274+
currentToken = parseExpressionWithoutCascade(functionDefinition);
7275+
}
7276+
listener.endAnonymousMethodInvocation(
7277+
punctuation,
7278+
functionDefinition,
7279+
currentToken,
7280+
isExpression: isExpression,
7281+
);
7282+
return currentToken;
7283+
}
7284+
71947285
/// Attempt a recovery where [token].next is replaced.
71957286
bool _attemptPrecedenceLevelRecovery(
71967287
Token token,
@@ -7361,13 +7452,19 @@ class Parser {
73617452
cascadeOperator.isA(TokenType.QUESTION_PERIOD_PERIOD),
73627453
);
73637454
listener.beginCascade(cascadeOperator);
7364-
if (token.next!.isA(TokenType.OPEN_SQUARE_BRACKET)) {
7455+
Token afterDots = token.next!;
7456+
if (afterDots.isA(TokenType.OPEN_SQUARE_BRACKET)) {
73657457
token = parseArgumentOrIndexStar(
73667458
token,
73677459
noTypeParamOrArg,
73687460
/* checkedNullAware = */ false,
73697461
);
7462+
} else if (_isAnonymousMethodsFeatureEnabled &&
7463+
_beginsAnonymousMethod(afterDots)) {
7464+
token = _parseAnonymousMethod(cascadeOperator, afterDots);
73707465
} else {
7466+
// TODO(eernst): Call `reportExperimentNotEnabled` to guide user when
7467+
// `_beginsAnonymousMethod(afterDots)`.
73717468
token = parseSend(
73727469
token,
73737470
IdentifierContext.expressionContinuation,
@@ -7388,13 +7485,21 @@ class Parser {
73887485
if (next.isA(TokenType.PERIOD) || next.isA(TokenType.QUESTION_PERIOD)) {
73897486
bool isNullAware = next.isA(TokenType.QUESTION_PERIOD);
73907487
Token period = next;
7391-
token = parseSend(
7392-
next,
7393-
IdentifierContext.expressionContinuation,
7394-
ConstantPatternContext.none,
7395-
);
7396-
next = token.next!;
7397-
listener.handleDotAccess(period, token, isNullAware);
7488+
Token afterPeriod = period.next!;
7489+
// TODO(eernst): Call `reportExperimentNotEnabled` to guide user when
7490+
// there is a match except for the experiment being disabled.
7491+
if (_isAnonymousMethodsFeatureEnabled &&
7492+
_beginsAnonymousMethod(afterPeriod)) {
7493+
token = _parseAnonymousMethod(period, afterPeriod);
7494+
} else {
7495+
token = parseSend(
7496+
next,
7497+
IdentifierContext.expressionContinuation,
7498+
ConstantPatternContext.none,
7499+
);
7500+
next = token.next!;
7501+
listener.handleDotAccess(period, token, isNullAware);
7502+
}
73987503
} else if (next.isA(TokenType.BANG)) {
73997504
listener.handleNonNullAssertExpression(next);
74007505
token = next;

0 commit comments

Comments
 (0)