Chromium Code Reviews| Index: pkg/front_end/lib/src/fasta/parser/parser.dart |
| diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart |
| index 781525cd21443da07746b665d108377153df0924..12fb7b880ebc83fb65c33e52311279e38ddd905c 100644 |
| --- a/pkg/front_end/lib/src/fasta/parser/parser.dart |
| +++ b/pkg/front_end/lib/src/fasta/parser/parser.dart |
| @@ -201,6 +201,22 @@ enum TypeContinuation { |
| /// Indicates that the keyword `typedef` has just been seen, and the parser |
| /// should parse the following as a type unless it is followed by `=`. |
| Typedef, |
| + |
| + /// Indicates that what follows is either a local declaration or an |
| + /// expression. |
| + ExpressionStatementOrDeclaration, |
| + |
| + /// Indicates that the keyword `const` has just been seen, and what follows |
| + /// may be a local variable declaration or an expression. |
| + ExpressionStatementOrConstDeclaration, |
| + |
| + /// Indicates that the parser is parsing an expression and has just seen an |
| + /// identifier. |
| + SendOrFunctionLiteral, |
|
danrubel
2017/06/28 03:22:00
What is 'Send' ? An invocation or call site?
ahe
2017/06/28 08:06:58
"Send" comes from message send. All method invocat
|
| + |
| + /// Indicates that the parser has just parsed `for '('` and is looking to |
| + /// parse a variable declaration or expression. |
| + VariablesDeclarationOrExpression, |
| } |
| /// An event generating parser of Dart programs. This parser expects all tokens |
| @@ -1157,30 +1173,132 @@ class Parser { |
| /// If this method can parse a type, it will return the next (non-null) token |
| /// after the type. Otherwise, it returns null. |
| Token parseType(Token token, |
| - [TypeContinuation continuation = TypeContinuation.Required]) { |
| - Token begin = token; |
| + [TypeContinuation continuation = TypeContinuation.Required, |
| + IdentifierContext continuationContext]) { |
| + /// Returns the close brace, bracket, or parenthesis of [left]. For '<', it |
| + /// may return null. |
| + Token getClose(BeginToken left) => left.endToken; |
| + |
| + /// Where the type begins. |
| + Token begin; |
| + |
| + /// Non-null if 'void' is the first token. |
| + Token voidToken; |
| + |
| + /// True if the tokens at [begin] looks like a type. |
| + bool looksLikeType = false; |
| + |
| + /// True if a type that could be a return type for a generalized function |
| + /// type was seen during analysis. |
| + bool hasReturnType = false; |
| + |
| + /// The identifier context to use for parsing the type. |
| + IdentifierContext context = IdentifierContext.typeReference; |
| + |
| + /// Non-null if type arguments were seen during analysis. |
| + BeginToken typeArguments; |
| + |
| + /// The number of function types seen during analysis. |
| + int functionTypes = 0; |
| + |
| + /// The start of type variables of function types seen during |
| + /// analysis. Notice that the tokens in this list might be either `'<'` or |
| + /// `'('` as not all function types have type parameters. Also, it is safe |
| + /// to assume that [getClose] will return non-null for all these tokens. |
| + Link<Token> typeVariableStarters = const Link<Token>(); |
| + |
| + { |
| + // Analyse the next tokens to see if they could be a type. |
| + |
| + if (continuation == |
| + TypeContinuation.ExpressionStatementOrConstDeclaration) { |
| + // This is a special case. The first token is `const` and we need to |
| + // analyze the tokens following the const keyword. |
| + assert(optional("const", token)); |
| + begin = token; |
| + token = token.next; |
| + token = listener.injectGenericCommentTypeAssign(token); |
| + assert(begin.next == token); |
| + } else { |
| + // Modify [begin] in case generic type are injected from a comment. |
| + begin = token = listener.injectGenericCommentTypeAssign(token); |
| + } |
| + |
| + if (optional("void", token)) { |
| + // `void` is a type. |
| + looksLikeType = true; |
| + voidToken = token; |
| + token = token.next; |
| + } else if (isValidTypeReference(token) && |
| + !isGeneralizedFunctionType(token)) { |
| + // We're looking at an identifier that could be a type (or `dynamic`). |
| + looksLikeType = true; |
| + token = token.next; |
| + if (optional(".", token) && isValidTypeReference(token.next)) { |
| + // We're looking at `prefix '.' identifier`. |
| + context = IdentifierContext.prefixedTypeReference; |
| + token = token.next.next; |
| + } |
| + if (optional("<", token)) { |
| + Token close = getClose(token); |
| + if (close != null && |
| + (optional(">", close) || optional(">>", close))) { |
| + // We found some type arguments. |
| + typeArguments = token; |
| + token = close.next; |
| + } |
| + } |
| + } |
| + |
| + // If what we have seen so far looks like a type, that could be a return |
| + // type for a generalized function type. |
| + hasReturnType = looksLikeType; |
| + |
| + while (optional("Function", token)) { |
| + Token typeVariableStart = token.next; |
| + if (optional("<", token.next)) { |
| + Token close = getClose(token.next); |
| + if (close != null && optional(">", close)) { |
| + token = close; |
| + } else { |
| + break; // Not a function type. |
| + } |
| + } |
| + if (optional("(", token.next)) { |
| + // This is a function type. |
| + Token close = getClose(token.next); |
| + assert(optional(")", close)); |
| + looksLikeType = true; |
| + functionTypes++; |
| + typeVariableStarters = |
| + typeVariableStarters.prepend(typeVariableStart); |
| + token = close.next; |
| + } else { |
| + break; // Not a funtion type. |
| + } |
| + } |
| + } |
| /// Call this function when it's known that [begin] is a type. This |
| /// function will call the appropriate event methods on [listener] to |
| /// handle the type. |
| Token commitType() { |
| - if (isGeneralizedFunctionType(token)) { |
| + assert(typeVariableStarters.length == functionTypes); |
| + |
| + if (functionTypes > 0 && !hasReturnType) { |
| // A function type without return type. |
| // Push the non-existing return type first. The loop below will |
| // generate the full type. |
| - listener.handleNoType(token); |
| - } else if (optional("void", token) && |
| - isGeneralizedFunctionType(token.next)) { |
| - listener.handleVoidKeyword(token); |
| - token = token.next; |
| + listener.handleNoType(begin); |
| + token = begin; |
| + } else if (functionTypes > 0 && voidToken != null) { |
| + listener.handleVoidKeyword(voidToken); |
| + token = voidToken.next; |
| } else { |
| - IdentifierContext context = IdentifierContext.typeReference; |
| - if (token.isIdentifier && optional(".", token.next)) { |
| - context = IdentifierContext.prefixedTypeReference; |
| - } |
| - token = parseIdentifier(token, context); |
| + token = parseIdentifier(begin, context); |
| token = parseQualifiedRestOpt( |
| token, IdentifierContext.typeReferenceContinuation); |
| + assert(typeArguments == null || typeArguments == token); |
| token = parseTypeArgumentsOpt(token); |
| listener.handleType(begin, token); |
| } |
| @@ -1196,45 +1314,159 @@ class Parser { |
| // While we see a `Function(` treat the pushed type as return type. |
| // For example: `int Function() Function(int) Function(String x)`. |
| - while (isGeneralizedFunctionType(token)) { |
| + for (int i = 0; i < functionTypes; i++) { |
| + assert(isGeneralizedFunctionType(token)); |
| token = parseFunctionType(token); |
| } |
| + |
| return token; |
| } |
| + /// Returns true if [kind] is '=', ';', or ',', that is, if [kind] could be |
| + /// the end of a variable declaration. |
| + bool looksLikeVariableDeclarationEnd(int kind) { |
| + return EQ_TOKEN == kind || SEMICOLON_TOKEN == kind || COMMA_TOKEN == kind; |
| + } |
| + |
| + /// Returns true if [token] could be the start of a function body. |
| + bool looksLikeFunctionBody(Token token) { |
| + return optional('{', token) || |
| + optional('=>', token) || |
| + optional('async', token) || |
| + optional('sync', token); |
| + } |
| + |
| switch (continuation) { |
| case TypeContinuation.Required: |
| return commitType(); |
| optional: |
| case TypeContinuation.Optional: |
| - if (optional("void", token)) { |
| - if (isGeneralizedFunctionType(token.next)) { |
| - return commitType(); // This is a type, parse it. |
| - } else { |
| - listener.handleVoidKeyword(token); |
| - return token.next; |
| + if (looksLikeType) { |
| + if (functionTypes > 0) { |
| + return commitType(); // Parse function type. |
| } |
| - } else { |
| - if (isGeneralizedFunctionType(token)) { |
| - return commitType(); // Function type without return type, parse it. |
| + if (voidToken != null) { |
| + listener.handleVoidKeyword(voidToken); |
| + return voidToken.next; |
| } |
| - token = listener.injectGenericCommentTypeAssign(token); |
| - Token peek = peekAfterIfType(token); |
| - if (peek != null && (peek.isIdentifier || optional('this', peek))) { |
| - // This is a type followed by an identifier, parse it. |
| - return commitType(); |
| + if (token.isIdentifier || optional('this', token)) { |
| + return commitType(); // Parse type. |
| } |
| - listener.handleNoType(token); |
| - return token; |
| } |
| - break; |
| + listener.handleNoType(begin); |
| + return begin; |
| case TypeContinuation.Typedef: |
| - if (optional('=', peekAfterNominalType(token))) { |
| + if (optional('=', token)) { |
| return null; // This isn't a type, it's a new-style typedef. |
| } |
| continue optional; |
| + |
| + case TypeContinuation.ExpressionStatementOrDeclaration: |
| + assert(begin.isIdentifier || identical(begin.stringValue, 'void')); |
| + if (!inPlainSync && optional("await", begin)) { |
| + return parseExpressionStatement(begin); |
| + } |
| + |
| + if (looksLikeType && token.isIdentifier) { |
| + // If the identifier token has a type substitution comment /*=T*/, |
| + // then the set of tokens type tokens should be replaced with the |
| + // tokens parsed from the comment. |
| + Token afterId = token.next; |
| + |
| + begin = |
| + listener.replaceTokenWithGenericCommentTypeAssign(begin, token); |
| + |
| + int afterIdKind = afterId.kind; |
| + if (looksLikeVariableDeclarationEnd(afterIdKind)) { |
| + // We are looking at `type identifier` followed by |
| + // `(',' | '=' | ';')`. |
| + |
| + // TODO(ahe): Generate type events and call |
| + // parseVariablesDeclarationRest instead. |
| + return parseVariablesDeclaration(begin); |
| + } else if (OPEN_PAREN_TOKEN == afterIdKind) { |
| + // We are looking at `type identifier '('`. |
| + if (looksLikeFunctionBody(getClose(afterId).next)) { |
| + // We are looking at `type identifier '(' ... ')'` followed |
| + // `( '{' | '=>' | 'async' | 'sync' )`. |
| + return parseFunctionDeclaration(begin); |
| + } |
| + } else if (identical(afterIdKind, LT_TOKEN)) { |
| + // We are looking at `type identifier '<'`. |
| + Token afterTypeVariables = getClose(afterId)?.next; |
| + if (afterTypeVariables != null && |
| + optional("(", afterTypeVariables)) { |
| + if (looksLikeFunctionBody(getClose(afterTypeVariables).next)) { |
| + // We are looking at "type identifier '<' ... '>' '(' ... ')'" |
| + // followed by '{', '=>', 'async', or 'sync'. |
| + return parseFunctionDeclaration(begin); |
| + } |
| + } |
| + } |
| + // Fall-through to expression statement. |
| + } else { |
| + token = begin; |
| + if (optional(':', token.next)) { |
| + return parseLabeledStatement(token); |
| + } else if (optional('(', token.next)) { |
| + if (looksLikeFunctionBody(getClose(token.next).next)) { |
| + return parseFunctionDeclaration(token); |
| + } |
| + } else if (optional('<', token.next)) { |
| + Token afterTypeVariables = getClose(token.next)?.next; |
| + if (afterTypeVariables != null && |
| + optional("(", afterTypeVariables)) { |
| + if (looksLikeFunctionBody(getClose(afterTypeVariables).next)) { |
| + return parseFunctionDeclaration(token); |
| + } |
| + } |
| + // Fall through to expression statement. |
| + } |
| + } |
| + return parseExpressionStatement(begin); |
| + |
| + case TypeContinuation.ExpressionStatementOrConstDeclaration: |
| + Token identifier; |
| + if (looksLikeType && token.isIdentifier) { |
| + identifier = token; |
| + } else if (begin.next.isIdentifier) { |
| + identifier = begin.next; |
| + } |
| + if (identifier != null) { |
| + if (looksLikeVariableDeclarationEnd(identifier.next.kind)) { |
| + // We are looking at "const type identifier" followed by '=', ';', |
| + // or ','. |
| + |
| + // TODO(ahe): Generate type events and call |
| + // parseVariablesDeclarationRest instead. |
| + return parseVariablesDeclaration(begin); |
| + } |
| + // Fall-through to expression statement. |
| + } |
| + |
| + return parseExpressionStatement(begin); |
| + |
| + case TypeContinuation.SendOrFunctionLiteral: |
| + if (looksLikeType && |
| + token.isIdentifier && |
| + isFunctionDeclaration(token.next)) { |
| + return parseFunctionExpression(begin); |
| + } else if (isFunctionDeclaration(begin.next)) { |
| + return parseFunctionExpression(begin); |
| + } |
| + return parseSend(begin, continuationContext); |
| + |
| + case TypeContinuation.VariablesDeclarationOrExpression: |
| + if (looksLikeType && |
| + token.isIdentifier && |
| + isOneOf4(token.next, '=', ';', ',', 'in')) { |
| + // TODO(ahe): Generate type events and call |
| + // parseVariablesDeclarationNoSemicolonRest instead. |
| + return parseVariablesDeclarationNoSemicolon(begin); |
| + } |
| + return parseExpression(begin); |
| } |
| throw "Internal error: Unhandled continuation '$continuation'."; |
| @@ -1848,103 +2080,6 @@ class Parser { |
| return token; |
| } |
| - /// Returns the first token after the type starting at [token]. |
| - /// |
| - /// This method assumes that [token] is an identifier (or void). Use |
| - /// [peekAfterIfType] if [token] isn't known to be an identifier. |
| - Token peekAfterType(Token token) { |
| - // We are looking at "identifier ...". |
| - Token peek = token; |
| - if (!isGeneralizedFunctionType(token)) { |
| - peek = peekAfterNominalType(token); |
| - } |
| - |
| - // We might have just skipped over the return value of the function type. |
| - // Check again, if we are now at a function type position. |
| - while (isGeneralizedFunctionType(peek)) { |
| - peek = peekAfterFunctionType(peek.next); |
| - } |
| - return peek; |
| - } |
| - |
| - /// Returns the first token after the nominal type starting at [token]. |
| - /// |
| - /// This method assumes that [token] is an identifier (or void). |
| - Token peekAfterNominalType(Token token) { |
| - Token peek = token.next; |
| - if (identical(peek.kind, PERIOD_TOKEN)) { |
| - if (peek.next.isIdentifier) { |
| - // Look past a library prefix. |
| - peek = peek.next.next; |
| - } |
| - } |
| - // We are looking at "qualified ...". |
| - if (identical(peek.kind, LT_TOKEN)) { |
| - // Possibly generic type. |
| - // We are looking at "qualified '<'". |
| - BeginToken beginGroupToken = peek; |
| - Token gtToken = beginGroupToken.endGroup; |
| - if (gtToken != null) { |
| - // We are looking at "qualified '<' ... '>' ...". |
| - peek = gtToken.next; |
| - } |
| - } |
| - return peek; |
| - } |
| - |
| - /// Returns the first token after the function type starting at [token]. |
| - /// |
| - /// The token must be at the token *after* the `Function` token |
| - /// position. That is, the return type and the `Function` token must have |
| - /// already been skipped. |
| - /// |
| - /// This function only skips over one function type syntax. If necessary, |
| - /// this function must be called multiple times. |
| - /// |
| - /// Example: |
| - /// |
| - /// int Function() Function<T>(int) |
| - /// ^ ^ |
| - /// |
| - /// A call to this function must be either at `(` or at `<`. If `token` |
| - /// pointed to the first `(`, then the returned token points to the second |
| - /// `Function` token. |
| - Token peekAfterFunctionType(Token token) { |
| - // Possible inputs are: |
| - // ( ... ) |
| - // < ... >( ... ) |
| - |
| - Token peek = token; |
| - // If there is a generic argument to the function, skip over that one first. |
| - if (identical(peek.kind, LT_TOKEN)) { |
| - BeginToken beginGroupToken = peek; |
| - Token closeToken = beginGroupToken.endGroup; |
| - if (closeToken != null) { |
| - peek = closeToken.next; |
| - } |
| - } |
| - |
| - // Now we just need to skip over the formals. |
| - expect('(', peek); |
| - |
| - BeginToken beginGroupToken = peek; |
| - Token closeToken = beginGroupToken.endGroup; |
| - if (closeToken != null) { |
| - peek = closeToken.next; |
| - } |
| - |
| - return peek; |
| - } |
| - |
| - /// If [token] is the start of a type, returns the token after that type. |
| - /// If [token] is not the start of a type, null is returned. |
| - Token peekAfterIfType(Token token) { |
| - if (!optional('void', token) && !token.isIdentifier) { |
| - return null; |
| - } |
| - return peekAfterType(token); |
| - } |
| - |
| Token skipClassBody(Token token) { |
| if (!optional('{', token)) { |
| return reportUnrecoverableErrorCodeWithToken( |
| @@ -2568,145 +2703,18 @@ class Parser { |
| return expectSemicolon(token); |
| } |
| - Token peekIdentifierAfterType(Token token) { |
| - Token peek = peekAfterType(token); |
| - if (peek != null && peek.isIdentifier) { |
| - // We are looking at "type identifier". |
| - return peek; |
| - } else { |
| - return null; |
| - } |
| - } |
| - |
| - Token peekIdentifierAfterOptionalType(Token token) { |
| - Token peek = peekAfterIfType(token); |
| - if (peek != null && peek.isIdentifier) { |
| - // We are looking at "type identifier". |
| - return peek; |
| - } else if (token.isIdentifier) { |
| - // We are looking at "identifier". |
| - return token; |
| - } else { |
| - return null; |
| - } |
| - } |
| - |
| Token parseExpressionStatementOrDeclaration(Token token) { |
| - if (!inPlainSync && optional("await", token)) { |
| - return parseExpressionStatement(token); |
| - } |
| - assert(token.isIdentifier || identical(token.stringValue, 'void')); |
| - Token identifier = peekIdentifierAfterType(token); |
| - if (identifier != null) { |
| - assert(identifier.isIdentifier); |
| - |
| - // If the identifier token has a type substitution comment /*=T*/, |
| - // then the set of tokens type tokens should be replaced with the |
| - // tokens parsed from the comment. |
| - token = |
| - listener.replaceTokenWithGenericCommentTypeAssign(token, identifier); |
| - |
| - Token afterId = identifier.next; |
| - int afterIdKind = afterId.kind; |
| - if (identical(afterIdKind, EQ_TOKEN) || |
| - identical(afterIdKind, SEMICOLON_TOKEN) || |
| - identical(afterIdKind, COMMA_TOKEN)) { |
| - // We are looking at "type identifier" followed by '=', ';', ','. |
| - return parseVariablesDeclaration(token); |
| - } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) { |
| - // We are looking at "type identifier '('". |
| - BeginToken beginParen = afterId; |
| - Token endParen = beginParen.endGroup; |
| - // TODO(eernst): Check for NPE as described in issue 26252. |
| - Token afterParens = endParen.next; |
| - if (optional('{', afterParens) || |
| - optional('=>', afterParens) || |
| - optional('async', afterParens) || |
| - optional('sync', afterParens)) { |
| - // We are looking at "type identifier '(' ... ')'" followed |
| - // by '{', '=>', 'async', or 'sync'. |
| - return parseFunctionDeclaration(token); |
| - } |
| - } else if (identical(afterIdKind, LT_TOKEN)) { |
| - // We are looking at "type identifier '<'". |
| - BeginToken beginAngle = afterId; |
| - Token endAngle = beginAngle.endGroup; |
| - if (endAngle != null && |
| - identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) { |
| - BeginToken beginParen = endAngle.next; |
| - Token endParen = beginParen.endGroup; |
| - if (endParen != null) { |
| - Token afterParens = endParen.next; |
| - if (optional('{', afterParens) || |
| - optional('=>', afterParens) || |
| - optional('async', afterParens) || |
| - optional('sync', afterParens)) { |
| - // We are looking at "type identifier '<' ... '>' '(' ... ')'" |
| - // followed by '{', '=>', 'async', or 'sync'. |
| - return parseFunctionDeclaration(token); |
| - } |
| - } |
| - } |
| - } |
| - // Fall-through to expression statement. |
| - } else { |
| - if (optional(':', token.next)) { |
| - return parseLabeledStatement(token); |
| - } else if (optional('(', token.next)) { |
| - BeginToken begin = token.next; |
| - // TODO(eernst): Check for NPE as described in issue 26252. |
| - String afterParens = begin.endGroup.next.stringValue; |
| - if (identical(afterParens, '{') || |
| - identical(afterParens, '=>') || |
| - identical(afterParens, 'async') || |
| - identical(afterParens, 'sync')) { |
| - return parseFunctionDeclaration(token); |
| - } |
| - } else if (optional('<', token.next)) { |
| - BeginToken beginAngle = token.next; |
| - Token endAngle = beginAngle.endGroup; |
| - if (endAngle != null && |
| - identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) { |
| - BeginToken beginParen = endAngle.next; |
| - Token endParen = beginParen.endGroup; |
| - if (endParen != null) { |
| - String afterParens = endParen.next.stringValue; |
| - if (identical(afterParens, '{') || |
| - identical(afterParens, '=>') || |
| - identical(afterParens, 'async') || |
| - identical(afterParens, 'sync')) { |
| - return parseFunctionDeclaration(token); |
| - } |
| - } |
| - } |
| - // Fall through to expression statement. |
| - } |
| - } |
| - return parseExpressionStatement(token); |
| + return parseType(token, TypeContinuation.ExpressionStatementOrDeclaration); |
| } |
| Token parseExpressionStatementOrConstDeclaration(Token token) { |
| - assert(identical(token.stringValue, 'const')); |
| + assert(optional('const', token)); |
| if (isModifier(token.next)) { |
| return parseVariablesDeclaration(token); |
| + } else { |
| + return parseType( |
| + token, TypeContinuation.ExpressionStatementOrConstDeclaration); |
| } |
| - listener.injectGenericCommentTypeAssign(token.next); |
| - Token identifier = peekIdentifierAfterOptionalType(token.next); |
| - if (identifier != null) { |
| - assert(identifier.isIdentifier); |
| - Token afterId = identifier.next; |
| - int afterIdKind = afterId.kind; |
| - if (identical(afterIdKind, EQ_TOKEN) || |
| - identical(afterIdKind, SEMICOLON_TOKEN) || |
| - identical(afterIdKind, COMMA_TOKEN)) { |
| - // We are looking at "const type identifier" followed by '=', ';', or |
| - // ','. |
| - return parseVariablesDeclaration(token); |
| - } |
| - // Fall-through to expression statement. |
| - } |
| - |
| - return parseExpressionStatement(token); |
| } |
| Token parseLabel(Token token) { |
| @@ -3062,7 +3070,6 @@ class Parser { |
| Token parseParenthesizedExpressionOrFunctionLiteral(Token token) { |
| BeginToken beginGroup = token; |
| - // TODO(eernst): Check for NPE as described in issue 26252. |
| Token nextToken = beginGroup.endGroup.next; |
| int kind = nextToken.kind; |
| if (mayParseFunctionExpressions && |
| @@ -3241,16 +3248,8 @@ class Parser { |
| Token parseSendOrFunctionLiteral(Token token, IdentifierContext context) { |
| if (!mayParseFunctionExpressions) { |
| return parseSend(token, context); |
| - } |
| - Token peek = peekAfterIfType(token); |
| - if (peek != null && |
| - identical(peek.kind, IDENTIFIER_TOKEN) && |
| - isFunctionDeclaration(peek.next)) { |
| - return parseFunctionExpression(token); |
| - } else if (isFunctionDeclaration(token.next)) { |
| - return parseFunctionExpression(token); |
| } else { |
| - return parseSend(token, context); |
| + return parseType(token, TypeContinuation.SendOrFunctionLiteral, context); |
| } |
| } |
| @@ -3262,7 +3261,6 @@ class Parser { |
| } |
| if (optional('(', token)) { |
| BeginToken begin = token; |
| - // TODO(eernst): Check for NPE as described in issue 26252. |
| String afterParens = begin.endGroup.next.stringValue; |
| if (identical(afterParens, '{') || |
| identical(afterParens, '=>') || |
| @@ -3517,14 +3515,22 @@ class Parser { |
| return parseVariablesDeclarationMaybeSemicolon(token, true); |
| } |
| + Token parseVariablesDeclarationRest(Token token) { |
| + return parseVariablesDeclarationMaybeSemicolonRest(token, true); |
| + } |
| + |
| Token parseVariablesDeclarationNoSemicolon(Token token) { |
| // Only called when parsing a for loop, so this is for parsing locals. |
| return parseVariablesDeclarationMaybeSemicolon(token, false); |
| } |
| + Token parseVariablesDeclarationNoSemicolonRest(Token token) { |
| + // Only called when parsing a for loop, so this is for parsing locals. |
| + return parseVariablesDeclarationMaybeSemicolonRest(token, false); |
| + } |
| + |
| Token parseVariablesDeclarationMaybeSemicolon( |
| Token token, bool endWithSemicolon) { |
| - int count = 1; |
| token = parseMetadataStar(token); |
| // If the next token has a type substitution comment /*=T*/, then |
| @@ -3535,6 +3541,12 @@ class Parser { |
| } |
| token = parseModifiers(token, MemberKind.Local, isVariable: true); |
| + return parseVariablesDeclarationMaybeSemicolonRest(token, endWithSemicolon); |
| + } |
| + |
| + Token parseVariablesDeclarationMaybeSemicolonRest( |
| + Token token, bool endWithSemicolon) { |
| + int count = 1; |
| listener.beginVariablesDeclaration(token); |
| token = parseOptionallyInitializedIdentifier(token); |
| while (optional(',', token)) { |
| @@ -3605,14 +3617,7 @@ class Parser { |
| } else if (isOneOf4(token, '@', 'var', 'final', 'const')) { |
| return parseVariablesDeclarationNoSemicolon(token); |
| } |
| - Token identifier = peekIdentifierAfterType(token); |
| - if (identifier != null) { |
| - assert(identifier.isIdentifier); |
| - if (isOneOf4(identifier.next, '=', ';', ',', 'in')) { |
| - return parseVariablesDeclarationNoSemicolon(token); |
| - } |
| - } |
| - return parseExpression(token); |
| + return parseType(token, TypeContinuation.VariablesDeclarationOrExpression); |
| } |
| Token parseForRest(Token forToken, Token leftParenthesis, Token token) { |