Chromium Code Reviews| Index: pkg/analyzer/lib/src/generated/parser.dart |
| diff --git a/pkg/analyzer/lib/src/generated/parser.dart b/pkg/analyzer/lib/src/generated/parser.dart |
| index 76f9dc7e76e32af10ddc3113de95be5c22ab1a81..d2c0c7c4601e8bb72fcfd79febf26a6fd5630ae7 100644 |
| --- a/pkg/analyzer/lib/src/generated/parser.dart |
| +++ b/pkg/analyzer/lib/src/generated/parser.dart |
| @@ -2123,6 +2123,12 @@ class Parser { |
| bool parseGenericMethods = false; |
| /** |
| + * A flag indicating whether to parse generic method comments, of the form |
| + * `/*=T*/` and `/*<T>*/`. |
| + */ |
| + bool parseGenericMethodComments = false; |
|
Jennifer Messerly
2015/11/11 18:28:06
aside, we don't actually need this flag in the par
Paul Berry
2015/11/11 19:26:53
My two cents: I'd actually lean slightly toward le
Jennifer Messerly
2015/11/11 19:30:25
good point.
Brian Wilkerson
2015/11/11 21:53:42
I agree, I'd leave the flag in. You'll probably ev
|
| + |
| + /** |
| * Initialize a newly created parser to parse the content of the given |
| * [_source] and to report any errors that are found to the given |
| * [_errorListener]. |
| @@ -2515,6 +2521,7 @@ class Parser { |
| parseFormalParameterList()); |
| } else if (_tokenMatches(_peek(), TokenType.OPEN_PAREN)) { |
| SimpleIdentifier methodName = parseSimpleIdentifier(); |
| + TypeParameterList typeParameters = _parseGenericCommentTypeParameters(); |
| FormalParameterList parameters = parseFormalParameterList(); |
| if (_matches(TokenType.COLON) || |
| modifiers.factoryKeyword != null || |
| @@ -2537,7 +2544,7 @@ class Parser { |
| modifiers.staticKeyword, |
| null, |
| methodName, |
| - null, |
| + typeParameters, |
| parameters); |
| } else if (_peek() |
| .matchesAny([TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON])) { |
| @@ -2617,6 +2624,7 @@ class Parser { |
| } |
| } else if (_tokenMatches(_peek(), TokenType.OPEN_PAREN)) { |
| SimpleIdentifier methodName = parseSimpleIdentifier(); |
| + TypeParameterList typeParameters = _parseGenericCommentTypeParameters(); |
| FormalParameterList parameters = parseFormalParameterList(); |
| if (methodName.name == className) { |
| _reportErrorForNode(ParserErrorCode.CONSTRUCTOR_WITH_RETURN_TYPE, type); |
| @@ -2638,7 +2646,7 @@ class Parser { |
| modifiers.staticKeyword, |
| type, |
| methodName, |
| - null, |
| + typeParameters, |
| parameters); |
| } else if (parseGenericMethods && _tokenMatches(_peek(), TokenType.LT)) { |
| return _parseMethodDeclarationAfterReturnType(commentAndMetadata, |
| @@ -3135,10 +3143,7 @@ class Parser { |
| * typeParameters? formalParameterList functionExpressionBody |
| */ |
| FunctionExpression parseFunctionExpression() { |
| - TypeParameterList typeParameters = null; |
| - if (parseGenericMethods && _matches(TokenType.LT)) { |
| - typeParameters = parseTypeParameterList(); |
| - } |
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters(); |
| FormalParameterList parameters = parseFormalParameterList(); |
| _validateFormalParameterList(parameters); |
| FunctionBody body = |
| @@ -3265,10 +3270,7 @@ class Parser { |
| period = _expect(TokenType.PERIOD); |
| } |
| SimpleIdentifier identifier = parseSimpleIdentifier(); |
| - TypeParameterList typeParameters = null; |
| - if (parseGenericMethods && _matches(TokenType.LT)) { |
| - typeParameters = parseTypeParameterList(); |
| - } |
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters(); |
| if (_matches(TokenType.OPEN_PAREN)) { |
| FormalParameterList parameters = parseFormalParameterList(); |
| if (thisKeyword == null) { |
| @@ -3476,6 +3478,18 @@ class Parser { |
| * qualified typeArguments? |
| */ |
| TypeName parseTypeName() { |
| + TypeName result = _parseTypeName(); |
| + // If this is followed by a generic method type comment, allow the comment |
| + // type to replace the real type name. |
| + // TODO(jmesserly): this feels like a big hammer. Can we restrict it to |
| + // only work inside generic methods? |
|
Jennifer Messerly
2015/11/11 18:28:06
This feature weirds me out. I kind of wonder if it
Brian Wilkerson
2015/11/11 21:53:42
Given that this only applies to return types and p
Paul Berry
2015/11/11 22:01:58
I would think it could potentially apply to other
Jennifer Messerly
2015/11/11 22:04:12
EDIT: Paul beat me to it. But here was my comment.
|
| + if (_injectGenericCommentTypeAssign()) { |
| + return _parseTypeName(); |
| + } |
| + return result; |
| + } |
| + |
| + TypeName _parseTypeName() { |
| Identifier typeName; |
| if (_matchesKeyword(Keyword.VAR)) { |
| _reportErrorForCurrentToken(ParserErrorCode.VAR_AS_TYPE_NAME); |
| @@ -3486,10 +3500,7 @@ class Parser { |
| typeName = _createSyntheticIdentifier(); |
| _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TYPE_NAME); |
| } |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| return new TypeName(typeName, typeArguments); |
| } |
| @@ -3909,6 +3920,47 @@ class Parser { |
| return null; |
| } |
| + bool _injectGenericComment(TokenType type, int prefixLen) { |
| + if (parseGenericMethodComments) { |
|
Jennifer Messerly
2015/11/11 18:28:06
as noted above, this guard shouldn't really be nec
|
| + CommentToken t = _currentToken.precedingComments; |
| + for (; t != null; t = t.next) { |
| + if (t.type == TokenType.GENERIC_METHOD_TYPE_LIST) { |
|
Jennifer Messerly
2015/11/11 20:52:27
Bug here. the /*=T*/ comments weren't working beca
|
| + String comment = t.lexeme.substring(prefixLen, t.lexeme.length - 2); |
| + Token list = _scanGenericMethodComment(comment, t.offset + prefixLen); |
|
Paul Berry
2015/11/11 19:26:53
What will happen if the comment is matched by Scan
Jennifer Messerly
2015/11/11 19:30:25
Should be exactly as if you wrote that without the
|
| + if (list != null) { |
| + // TODO(jmesserly): detach the old comment token? |
|
Brian Wilkerson
2015/11/11 21:53:42
It shouldn't actually hurt anything if you leave i
Jennifer Messerly
2015/11/12 00:20:00
Sounds good. Removed the TODO.
|
| + // Insert the tokens into the stream. |
| + _injectTokenList(list); |
| + return true; |
| + } |
| + } |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + /** |
| + * Matches a generic comment type substitution and injects it into the token |
|
Jennifer Messerly
2015/11/11 18:28:06
oops, fixed this locally. Not sure how it happened
|
| + * stream. Returns true if a match was injected, otherwise false. |
| + * |
| + * These comments are of the form `/*=T*/`, in other words, a [TypeName] |
| + * inside a slash-star comment, preceded by equals sign. |
| + */ |
| + bool _injectGenericCommentTypeAssign() { |
| + return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_ASSIGN, 3); |
| + } |
| + |
| + /** |
| + * Matches a generic comment type parameters and injects them into the token |
| + * stream. Returns true if a match was injected, otherwise false. |
| + * |
| + * These comments are of the form `/*<K, V>*/`, in other words, a |
| + * [TypeParameterList] or [TypeArgumentList] inside a slash-star comment. |
| + */ |
| + bool _injectGenericCommentTypeList() { |
| + return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_LIST, 2); |
| + } |
| + |
| /** |
| * Inject the given [token] into the token stream immediately before the |
| * current token. |
| @@ -3920,6 +3972,19 @@ class Parser { |
| return token; |
| } |
| + void _injectTokenList(Token firstToken) { |
|
Jennifer Messerly
2015/11/11 18:28:06
I used the same pattern as the existing _injectTok
|
| + // Scanner creates a cyclic EOF token. |
| + Token lastToken = firstToken; |
| + while (lastToken.next.type != TokenType.EOF) { |
| + lastToken = lastToken.next; |
| + } |
| + // Inject these new tokens into the stream. |
| + Token previous = _currentToken.previous; |
| + lastToken.setNext(_currentToken); |
| + previous.setNext(firstToken); |
| + _currentToken = firstToken; |
| + } |
| + |
| /** |
| * Return `true` if the current token appears to be the beginning of a |
| * function declaration. |
| @@ -4271,6 +4336,17 @@ class Parser { |
| _currentToken.lexeme == identifier; |
| /** |
| + * Parse a [TypeArgumentList] if present, otherwise return null. |
| + * This also supports the comment form, if enabled: `/*<T>*/` |
| + */ |
| + TypeArgumentList _maybeParseTypeArguments() { |
|
Brian Wilkerson
2015/11/11 21:53:42
For consistency with "_parseOptionalReturnType", p
Jennifer Messerly
2015/11/12 00:20:00
Done.
|
| + if (_matches(TokenType.LT) || _injectGenericCommentTypeList()) { |
| + return parseTypeArgumentList(); |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| * If the current token has the given [type], then advance to the next token |
| * and return `true`. Otherwise, return `false` without advancing. This method |
| * should not be invoked with an argument value of [TokenType.GT]. |
| @@ -4361,10 +4437,7 @@ class Parser { |
| bool isOptional = primaryAllowed || expression is SimpleIdentifier; |
| while (true) { |
| while (_isLikelyParameterList()) { |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| ArgumentList argumentList = parseArgumentList(); |
| if (expression is SimpleIdentifier) { |
| expression = new MethodInvocation(null, null, |
| @@ -4573,10 +4646,7 @@ class Parser { |
| (expression != null && functionName == null)); |
| if (_isLikelyParameterList()) { |
| while (_isLikelyParameterList()) { |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| if (functionName != null) { |
| expression = new MethodInvocation(expression, period, functionName, |
| typeArguments, parseArgumentList()); |
| @@ -4604,10 +4674,7 @@ class Parser { |
| expression = selector; |
| progress = true; |
| while (_isLikelyParameterList()) { |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| if (expression is PropertyAccess) { |
| PropertyAccess propertyAccess = expression as PropertyAccess; |
| expression = new MethodInvocation( |
| @@ -6025,10 +6092,7 @@ class Parser { |
| keyword = getAndAdvance(); |
| } |
| SimpleIdentifier name = parseSimpleIdentifier(); |
| - TypeParameterList typeParameters = null; |
| - if (parseGenericMethods && _matches(TokenType.LT)) { |
| - typeParameters = parseTypeParameterList(); |
| - } |
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters(); |
| FormalParameterList parameters = null; |
| if (!isGetter) { |
| if (_matches(TokenType.OPEN_PAREN)) { |
| @@ -6182,6 +6246,36 @@ class Parser { |
| } |
| /** |
| + * Parses generic type parameters from a comment. |
| + * |
| + * Normally this is handled by [_parseGenericMethodTypeParameters], but if the |
|
Jennifer Messerly
2015/11/11 18:28:06
fixed this locally, will be in next upload
|
| + * code already handles the normal generic type parameters, the comment |
| + * matcher can be called directly. For example, we may have already tried |
| + * matching `<` (less than sign) in a method declaration, and be currently |
| + * on the `(` (open paren) because we didn't find it. In that case, this |
| + * function will parse the preceding comment such as `/*<T, R>*/`. |
| + */ |
| + TypeParameterList _parseGenericCommentTypeParameters() { |
| + if (_injectGenericCommentTypeList()) { |
| + return parseTypeParameterList(); |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| + * Parse the generic method or function's type parameters. |
| + * |
| + * For backwards compatibility this can optionally use comments. |
| + * See [parseGenericMethodComments]. |
| + */ |
| + TypeParameterList _parseGenericMethodTypeParameters() { |
| + if (parseGenericMethods && _matches(TokenType.LT) || |
| + _injectGenericCommentTypeList()) { |
| + return parseTypeParameterList(); |
| + } |
| + } |
| + |
| + /** |
| * Parse a getter. The [commentAndMetadata] is the documentation comment and |
| * metadata to be associated with the declaration. The externalKeyword] is the |
| * 'external' token. The staticKeyword] is the static keyword, or `null` if |
| @@ -6473,10 +6567,7 @@ class Parser { |
| * | mapLiteral |
| */ |
| TypedLiteral _parseListOrMapLiteral(Token modifier) { |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| if (_matches(TokenType.OPEN_CURLY_BRACKET)) { |
| return _parseMapLiteral(modifier, typeArguments); |
| } else if (_matches(TokenType.OPEN_SQUARE_BRACKET) || |
| @@ -6610,10 +6701,7 @@ class Parser { |
| Token staticKeyword, |
| TypeName returnType) { |
| SimpleIdentifier methodName = parseSimpleIdentifier(); |
| - TypeParameterList typeParameters = null; |
| - if (parseGenericMethods && _matches(TokenType.LT)) { |
| - typeParameters = parseTypeParameterList(); |
| - } |
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters(); |
| FormalParameterList parameters; |
| if (!_matches(TokenType.OPEN_PAREN) && |
| (_matches(TokenType.OPEN_CURLY_BRACKET) || |
| @@ -7081,10 +7169,7 @@ class Parser { |
| (parseGenericMethods && _matches(TokenType.LT))) { |
| do { |
| if (_isLikelyParameterList()) { |
| - TypeArgumentList typeArguments = null; |
| - if (_matches(TokenType.LT)) { |
| - typeArguments = parseTypeArgumentList(); |
| - } |
| + TypeArgumentList typeArguments = _maybeParseTypeArguments(); |
| ArgumentList argumentList = parseArgumentList(); |
| if (operand is PropertyAccess) { |
| PropertyAccess access = operand as PropertyAccess; |
| @@ -8084,6 +8169,23 @@ class Parser { |
| } |
| /** |
| + * Scans the generic method comment, and returns the tokens, otherwise |
| + * returns null. |
| + */ |
| + Token _scanGenericMethodComment(String code, int offset) { |
| + BooleanErrorListener listener = new BooleanErrorListener(); |
| + Scanner scanner = |
| + new Scanner(null, new SubSequenceReader(code, offset), listener); |
| + scanner.setSourceStart(1, 1); |
| + Token firstToken = scanner.tokenize(); |
| + if (listener.errorReported) { |
| + // TODO(jmesserly): should we report these messages? |
|
Brian Wilkerson
2015/11/11 21:53:42
It would help you find bugs while using this featu
Jennifer Messerly
2015/11/12 00:20:00
Good point. Removed the TODO.
|
| + return null; |
| + } |
| + return firstToken; |
| + } |
| + |
| + /** |
| * Skips a block with all containing blocks. |
| */ |
| void _skipBlock() { |
| @@ -8420,7 +8522,8 @@ class Parser { |
| */ |
| Token _skipTypeArgumentList(Token startToken) { |
| Token token = startToken; |
| - if (!_tokenMatches(token, TokenType.LT)) { |
| + if (!_tokenMatches(token, TokenType.LT) && |
| + !_injectGenericCommentTypeList()) { |
| return null; |
| } |
| token = _skipTypeName(token.next); |