@@ -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