Skip to content

Commit

Permalink
Add parser support for for (var pattern = expression; ...; ...)
Browse files Browse the repository at this point in the history
Bug: #50035
Change-Id: I5f07b848ba06be403e56538a4cc64f6de51bd668
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273005
Reviewed-by: Konstantin Shcheglov <[email protected]>
Commit-Queue: Paul Berry <[email protected]>
  • Loading branch information
stereotype441 authored and Commit Queue committed Dec 1, 2022
1 parent 9221f34 commit 599911d
Show file tree
Hide file tree
Showing 254 changed files with 1,676 additions and 1,145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,12 @@ class ForwardingListener implements Listener {
listener?.handleForInitializerLocalVariableDeclaration(token, forIn);
}

@override
void handleForInitializerPatternVariableAssignment(
Token keyword, Token equals) {
listener?.handleForInitializerPatternVariableAssignment(keyword, equals);
}

@override
void handleForInLoopParts(Token? awaitToken, Token forToken,
Token leftParenthesis, Token inKeyword) {
Expand Down
11 changes: 10 additions & 1 deletion pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -585,11 +585,20 @@ class Listener implements UnescapeErrorListener {
}

/// Marks that the grammar term `forInitializerStatement` has been parsed and
/// it was a `localVariableDeclaration`.
/// it was a `localVariableDeclaration` of the form
/// `metadata initializedVariableDeclaration ';'`.
void handleForInitializerLocalVariableDeclaration(Token token, bool forIn) {
logEvent("ForInitializerLocalVariableDeclaration");
}

/// Marks that the grammar term `forInitializerStatement` has been parsed and
/// it was a `localVariableDeclaration` of the form
/// `metadata patternVariableDeclaration ';'`.
void handleForInitializerPatternVariableAssignment(
Token keyword, Token equals) {
logEvent("handleForInitializerPatternVariableAssignment");
}

/// Marks the start of a for statement which is ended by either
/// [endForStatement] or [endForIn].
void beginForStatement(Token token) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,24 @@ class ForCondition extends LiteralEntryInfo {
assert(optional('for', forToken));
parser.listener.beginForControlFlow(awaitToken, forToken);

token = parser.parseForLoopPartsStart(awaitToken, forToken);
ForPartsContext forPartsContext = new ForPartsContext();
token =
parser.parseForLoopPartsStart(awaitToken, forToken, forPartsContext);
Token? patternKeyword = forPartsContext.patternKeyword;
if (patternKeyword != null) {
if (optional('=', token.next!)) {
// Process `for ( pattern = expression ; ... ; ... )`
Token equals = token.next!;
token = parser.parseExpression(equals);
parser.listener.handleForInitializerPatternVariableAssignment(
patternKeyword, equals);
_inStyle = false;
return parser.parseForLoopPartsRest(token, forToken, awaitToken);
} else {
// Process `for ( pattern in expression )`
throw new UnimplementedError('TODO(paulberry)');
}
}
Token identifier = token.next!;
token = parser.parseForLoopPartsMid(token, awaitToken, forToken);

Expand Down
114 changes: 65 additions & 49 deletions pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5234,8 +5234,7 @@ class Parser {
token,
/* lateToken = */ null,
/* varFinalOrConst = */ null,
/* typeInfo = */ null,
/* onlyParseVariableDeclarationStart = */ false);
/* typeInfo = */ null);
}
final String? value = token.next!.stringValue;
if (identical(value, '{')) {
Expand All @@ -5251,13 +5250,8 @@ class Parser {
} else if (identical(value, 'var') || identical(value, 'final')) {
Token varOrFinal = token.next!;
if (!isModifier(varOrFinal.next!)) {
return parseExpressionStatementOrDeclarationAfterModifiers(
varOrFinal,
token,
/* lateToken = */ null,
varOrFinal,
/* typeInfo = */ null,
/* onlyParseVariableDeclarationStart = */ false);
return parseExpressionStatementOrDeclarationAfterModifiers(varOrFinal,
token, /* lateToken = */ null, varOrFinal, /* typeInfo = */ null);
}
return parseExpressionStatementOrDeclaration(token);
} else if (identical(value, 'if')) {
Expand Down Expand Up @@ -7358,30 +7352,30 @@ class Parser {
}
}
return parseExpressionStatementOrDeclarationAfterModifiers(
constToken,
start,
/* lateToken = */ null,
constToken,
typeInfo,
/* onlyParseVariableDeclarationStart = */ false);
constToken, start, /* lateToken = */ null, constToken, typeInfo);
}
return parseExpressionStatementOrDeclaration(start);
}

/// This method has two modes based upon [onlyParseVariableDeclarationStart].
///
/// If [onlyParseVariableDeclarationStart] is `false` (the default) then this
/// method will parse a local variable declaration, a local function,
/// or an expression statement, and then return the last consumed token.
/// If [forPartsContext] is `null` (the default), then the parser is currently
/// processing a statement or declaration. This method will parse a local
/// variable declaration, a local function, or an expression statement, and
/// then return the last consumed token.
///
/// If [onlyParseVariableDeclarationStart] is `true` then this method
/// will only parse the metadata, modifiers, and type of a local variable
/// declaration if it exists. It is the responsibility of the caller to
/// call [parseVariablesDeclarationRest] to finish parsing the local variable
/// declaration. If a local variable declaration is not found then this
/// method will return [start].
/// If [forPartsContext] is non-null, then this method will only parse the
/// metadata, modifiers, and type of a local variable declaration if it
/// exists; it is the responsibility of the caller to call
/// [parseVariablesDeclarationRest] to finish parsing the local variable
/// declaration. Or it will parse the metadata, `var` or `final` keyword, and
/// pattern of a pattern variable declaration, and store the `var` or `final`
/// keyword in [forPartsContext]; it is the responsibility of the caller to
/// consume the rest of the pattern variable declaration. Or, if neither a
/// local variable declaration nor a pattern variable declaration is found,
/// then this method will return [start].
Token parseExpressionStatementOrDeclaration(final Token start,
[bool onlyParseVariableDeclarationStart = false]) {
[ForPartsContext? forPartsContext]) {
Token token = start;
Token next = token.next!;
if (optional('@', next)) {
Expand Down Expand Up @@ -7424,27 +7418,18 @@ class Parser {
}
}

return parseExpressionStatementOrDeclarationAfterModifiers(
token,
start,
lateToken,
varFinalOrConst,
/* typeInfo = */ null,
onlyParseVariableDeclarationStart);
return parseExpressionStatementOrDeclarationAfterModifiers(token, start,
lateToken, varFinalOrConst, /* typeInfo = */ null, forPartsContext);
}

/// See [parseExpressionStatementOrDeclaration].
///
/// If `start.next` is an `@` token (i.e. this is a declaration with metadata)
/// then the caller should parse it before calling this method; otherwise,
/// this method will handle the lack of metadata appropriately.
Token parseExpressionStatementOrDeclarationAfterModifiers(
Token beforeType,
Token start,
Token? lateToken,
Token? varFinalOrConst,
TypeInfo? typeInfo,
bool onlyParseVariableDeclarationStart) {
Token parseExpressionStatementOrDeclarationAfterModifiers(Token beforeType,
Token start, Token? lateToken, Token? varFinalOrConst, TypeInfo? typeInfo,
[ForPartsContext? forPartsContext]) {
// In simple cases check for bad 'late' modifier in non-nnbd-mode.
if (typeInfo == null &&
lateToken == null &&
Expand All @@ -7470,24 +7455,28 @@ class Parser {
varFinalOrConst != null &&
(optional('var', varFinalOrConst) ||
optional('final', varFinalOrConst)) &&
!onlyParseVariableDeclarationStart &&
looksLikeOuterPatternEquals(beforeType)) {
// If there was any metadata, then the caller was responsible for parsing
// it; if not, then we need to let the listener know there wasn't any.
if (!optional('@', start.next!)) {
listener.beginMetadataStar(start.next!);
listener.endMetadataStar(/* count = */ 0);
}
return parsePatternVariableDeclarationStatement(
beforeType, start, varFinalOrConst);
if (forPartsContext != null) {
forPartsContext.patternKeyword = varFinalOrConst;
return parsePattern(beforeType, isRefutableContext: false);
} else {
return parsePatternVariableDeclarationStatement(
beforeType, start, varFinalOrConst);
}
}

typeInfo ??= computeType(beforeType, /* required = */ false);

Token token = typeInfo.skipType(beforeType);
Token next = token.next!;

if (onlyParseVariableDeclarationStart) {
if (forPartsContext != null) {
if (lateToken != null) {
reportRecoverableErrorWithToken(
lateToken, codes.templateExtraneousModifier);
Expand Down Expand Up @@ -7575,7 +7564,7 @@ class Parser {
if (token == start) {
// If no annotation, modifier, or type, and this is not a local function
// then this must be an expression statement.
if (onlyParseVariableDeclarationStart) {
if (forPartsContext != null) {
return start;
} else {
return parseExpressionStatement(start);
Expand All @@ -7592,7 +7581,7 @@ class Parser {
if (EQ_TOKEN != kind &&
SEMICOLON_TOKEN != kind &&
COMMA_TOKEN != kind) {
if (onlyParseVariableDeclarationStart) {
if (forPartsContext != null) {
if (!optional('in', next.next!)) {
return start;
}
Expand Down Expand Up @@ -7627,7 +7616,7 @@ class Parser {
token = typeInfo.parseType(beforeType, this);
next = token.next!;
listener.beginVariablesDeclaration(next, lateToken, varFinalOrConst);
if (!onlyParseVariableDeclarationStart) {
if (forPartsContext == null) {
token =
parseVariablesDeclarationRest(token, /* endWithSemicolon = */ true);
}
Expand Down Expand Up @@ -7707,7 +7696,22 @@ class Parser {
assert(optional('for', token));
listener.beginForStatement(forToken);

token = parseForLoopPartsStart(awaitToken, forToken);
ForPartsContext forPartsContext = new ForPartsContext();
token = parseForLoopPartsStart(awaitToken, forToken, forPartsContext);
Token? patternKeyword = forPartsContext.patternKeyword;
if (patternKeyword != null) {
if (optional('=', token.next!)) {
// Process `for ( pattern = expression ; ... ; ... )`
Token equals = token.next!;
token = parseExpression(equals);
listener.handleForInitializerPatternVariableAssignment(
patternKeyword, equals);
return parseForRest(awaitToken, token, forToken);
} else {
// Process `for ( pattern in expression )`
throw new UnimplementedError('TODO(paulberry)');
}
}
Token identifier = token.next!;
token = parseForLoopPartsMid(token, awaitToken, forToken);
if (optional('in', token.next!) || optional(':', token.next!)) {
Expand All @@ -7721,7 +7725,8 @@ class Parser {

/// Parse the start of a for loop control structure
/// from the open parenthesis up to but not including the identifier.
Token parseForLoopPartsStart(Token? awaitToken, Token forToken) {
Token parseForLoopPartsStart(
Token? awaitToken, Token forToken, ForPartsContext forPartsContext) {
Token leftParenthesis = forToken.next!;
if (!optional('(', leftParenthesis)) {
// Recovery
Expand Down Expand Up @@ -7757,7 +7762,7 @@ class Parser {
// declaration if it exists. This enables capturing [beforeIdentifier]
// for later error reporting.
return parseExpressionStatementOrDeclaration(
leftParenthesis, /* onlyParseVariableDeclarationStart = */ true);
leftParenthesis, forPartsContext);
}

/// Parse the remainder of the local variable declaration
Expand Down Expand Up @@ -9953,3 +9958,14 @@ class Parser {
typedef _MessageWithArgument<T> = codes.Message Function(T);

enum AwaitOrYieldContext { Statement, UnaryExpression }

/// Data structure tracking additional information when parsing the
/// `forLoopParts` grammar production.
class ForPartsContext {
/// If `forLoopParts` began with `( 'final' | 'var' ) outerPattern`, followed
/// by `=`, the `final` or `var` keyword. Otherwise `null`.
Token? patternKeyword;

@override
String toString() => 'ForPartsContext($patternKeyword)';
}
5 changes: 3 additions & 2 deletions pkg/analyzer/lib/src/dart/ast/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5511,8 +5511,9 @@ class ForPartsWithPatternImpl extends ForPartsImpl
Token get beginToken => variables.beginToken;

@override
ChildEntities get _childEntities =>
super._childEntities..addNode('variables', variables);
ChildEntities get _childEntities => ChildEntities()
..addNode('variables', variables)
..addAll(super._childEntities);

@override
E? accept<E>(AstVisitor<E> visitor) => visitor.visitForPartsWithPattern(this);
Expand Down
22 changes: 22 additions & 0 deletions pkg/analyzer/lib/src/fasta/ast_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4026,6 +4026,21 @@ class AstBuilder extends StackListener {
debugEvent("ForInitializerLocalVariableDeclaration");
}

@override
void handleForInitializerPatternVariableAssignment(
Token keyword, Token equals) {
var expression = pop() as ExpressionImpl;
var pattern = pop() as DartPatternImpl;
var metadata = pop() as List<AnnotationImpl>?;
push(PatternVariableDeclarationImpl(
keyword: keyword,
pattern: pattern,
equals: equals,
expression: expression,
comment: null,
metadata: metadata));
}

@override
void handleForInLoopParts(Token? awaitToken, Token forToken,
Token leftParenthesis, Token inKeyword) {
Expand Down Expand Up @@ -4102,6 +4117,13 @@ class AstBuilder extends StackListener {
rightSeparator: rightSeparator,
updaters: updates,
);
} else if (initializerPart is PatternVariableDeclarationImpl) {
forLoopParts = ForPartsWithPatternImpl(
variables: initializerPart,
leftSeparator: leftSeparator,
condition: condition,
rightSeparator: rightSeparator,
updaters: updates);
} else {
forLoopParts = ForPartsWithExpressionImpl(
initialization: initializerPart as ExpressionImpl?,
Expand Down
Loading

0 comments on commit 599911d

Please sign in to comment.