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 cb2a41218b94e69df3cb937443cced98691977ad..67ba79a053a2cb1522715718cbd6e42b1683b797 100644 |
--- a/pkg/analyzer/lib/src/generated/parser.dart |
+++ b/pkg/analyzer/lib/src/generated/parser.dart |
@@ -90,7 +90,7 @@ Map<String, MethodTrampoline> methodTable_Parser = <String, MethodTrampoline>{ |
'parseTypeArgumentList_0': new MethodTrampoline( |
0, (Parser target) => target.parseTypeArgumentList()), |
'parseTypeName_0': |
- new MethodTrampoline(0, (Parser target) => target.parseTypeName()), |
+ new MethodTrampoline(0, (Parser target) => target.parseTypeName(false)), |
'parseTypeParameter_0': |
new MethodTrampoline(0, (Parser target) => target.parseTypeParameter()), |
'parseTypeParameterList_0': new MethodTrampoline( |
@@ -683,6 +683,12 @@ class Parser { |
bool _enableAssertInitializer = false; |
/** |
+ * A flag indicating whether the parser is to parse the non-nullable modifier |
+ * in type names. |
+ */ |
+ bool _enableNnbd = false; |
+ |
+ /** |
* A flag indicating whether the parser is to parse the async support. |
*/ |
bool _parseAsync = true; |
@@ -764,6 +770,20 @@ class Parser { |
} |
/** |
+ * Return `true` if the parser is to parse the non-nullable modifier in type |
+ * names. |
+ */ |
+ bool get enableNnbd => _enableNnbd; |
+ |
+ /** |
+ * Set whether the parser is to parse the non-nullable modifier in type names |
+ * to match the given [enable] flag. |
+ */ |
+ void set enableNnbd(bool enable) { |
+ _enableNnbd = enable; |
+ } |
+ |
+ /** |
* Return `true` if the current token is the first token of a return type that |
* is followed by an identifier, possibly followed by a list of type |
* parameters, followed by a left-parenthesis. This is used by |
@@ -1521,7 +1541,7 @@ class Parser { |
* type ('.' identifier)? |
*/ |
ConstructorName parseConstructorName() { |
- TypeName type = parseTypeName(); |
+ TypeName type = parseTypeName(false); |
Token period = null; |
SimpleIdentifier name = null; |
if (_matches(TokenType.PERIOD)) { |
@@ -1637,7 +1657,7 @@ class Parser { |
*/ |
ExtendsClause parseExtendsClause() { |
Token keyword = getAndAdvance(); |
- TypeName superclass = parseTypeName(); |
+ TypeName superclass = parseTypeName(false); |
return new ExtendsClause(keyword, superclass); |
} |
@@ -1718,9 +1738,9 @@ class Parser { |
ImplementsClause parseImplementsClause() { |
Token keyword = getAndAdvance(); |
List<TypeName> interfaces = <TypeName>[]; |
- interfaces.add(parseTypeName()); |
+ interfaces.add(parseTypeName(false)); |
while (_optional(TokenType.COMMA)) { |
- interfaces.add(parseTypeName()); |
+ interfaces.add(parseTypeName(false)); |
} |
return new ImplementsClause(keyword, interfaces); |
} |
@@ -1822,13 +1842,18 @@ class Parser { |
_reportErrorForToken( |
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR, holder.keyword); |
} |
+ Token question = null; |
+ if (enableNnbd && _matches(TokenType.QUESTION)) { |
+ question = getAndAdvance(); |
+ } |
return new FunctionTypedFormalParameter( |
commentAndMetadata.comment, |
commentAndMetadata.metadata, |
holder.type, |
new SimpleIdentifier(identifier.token, isDeclaration: true), |
typeParameters, |
- parameters); |
+ parameters, |
+ question: question); |
} else { |
return new FieldFormalParameter( |
commentAndMetadata.comment, |
@@ -1901,7 +1926,7 @@ class Parser { |
if (_currentToken.keyword == Keyword.VOID) { |
return new TypeName(new SimpleIdentifier(getAndAdvance()), null); |
} else { |
- return parseTypeName(); |
+ return parseTypeName(false); |
} |
} |
@@ -1991,9 +2016,9 @@ class Parser { |
*/ |
TypeArgumentList parseTypeArgumentList() { |
Token leftBracket = getAndAdvance(); |
- List<TypeName> arguments = <TypeName>[parseTypeName()]; |
+ List<TypeName> arguments = <TypeName>[parseTypeName(false)]; |
while (_optional(TokenType.COMMA)) { |
- arguments.add(parseTypeName()); |
+ arguments.add(parseTypeName(false)); |
} |
Token rightBracket = _expectGt(); |
return new TypeArgumentList(leftBracket, arguments, rightBracket); |
@@ -2005,8 +2030,8 @@ class Parser { |
* type ::= |
* qualified typeArguments? |
*/ |
- TypeName parseTypeName() { |
- TypeName realType = _parseTypeName(); |
+ TypeName parseTypeName(bool inExpression) { |
+ TypeName realType = _parseTypeName(inExpression); |
// 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 |
@@ -2026,7 +2051,7 @@ class Parser { |
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true); |
if (_matchesKeyword(Keyword.EXTENDS)) { |
Token keyword = getAndAdvance(); |
- TypeName bound = parseTypeName(); |
+ TypeName bound = parseTypeName(false); |
return new TypeParameter(commentAndMetadata.comment, |
commentAndMetadata.metadata, name, keyword, bound); |
} |
@@ -2063,9 +2088,9 @@ class Parser { |
*/ |
WithClause parseWithClause() { |
Token withKeyword = getAndAdvance(); |
- List<TypeName> types = <TypeName>[parseTypeName()]; |
+ List<TypeName> types = <TypeName>[parseTypeName(false)]; |
while (_optional(TokenType.COMMA)) { |
- types.add(parseTypeName()); |
+ types.add(parseTypeName(false)); |
} |
return new WithClause(withKeyword, types); |
} |
@@ -2101,6 +2126,30 @@ class Parser { |
} |
/** |
+ * Clone all token starting from the given [token] up to the end of the token |
+ * stream, and return the first token in the new token stream. |
+ */ |
+ Token _cloneTokens(Token token) { |
+ if (token == null) { |
+ return null; |
+ } |
+ token = token is CommentToken ? token.parent : token; |
+ Token head = new Token(TokenType.EOF, -1); |
+ head.setNext(head); |
+ Token current = head; |
+ while (token.type != TokenType.EOF) { |
+ Token clone = token.copy(); |
+ current.setNext(clone); |
+ current = clone; |
+ token = token.next; |
+ } |
+ Token tail = new Token(TokenType.EOF, 0); |
+ tail.setNext(tail); |
+ current.setNext(tail); |
+ return head.next; |
+ } |
+ |
+ /** |
* Return the content of a string with the given literal representation. The |
* [lexeme] is the literal representation of the string. The flag [isFirst] is |
* `true` if this is the first token in a string literal. The flag [isLast] is |
@@ -2497,6 +2546,23 @@ class Parser { |
} |
/** |
+ * Return `true` if the current token could be the question mark in a |
+ * condition expression. The current token is assumed to be a question mark. |
+ */ |
+ bool _isConditionalOperator() { |
+ void parseOperation(Parser parser) { |
+ parser.parseExpressionWithoutCascade(); |
+ } |
+ |
+ Token token = _skip(_currentToken.next, parseOperation); |
+ if (token == null || !_tokenMatches(token, TokenType.COLON)) { |
+ return false; |
+ } |
+ token = _skip(token.next, parseOperation); |
+ return token != null; |
+ } |
+ |
+ /** |
* Return `true` if the current token appears to be the beginning of a |
* function declaration. |
*/ |
@@ -3507,7 +3573,7 @@ class Parser { |
SimpleIdentifier className, |
TypeParameterList typeParameters) { |
Token equals = _expect(TokenType.EQ); |
- TypeName superclass = parseTypeName(); |
+ TypeName superclass = parseTypeName(false); |
WithClause withClause = null; |
if (_matchesKeyword(Keyword.WITH)) { |
withClause = parseWithClause(); |
@@ -4580,7 +4646,7 @@ class Parser { |
if (keyword == Keyword.FINAL || keyword == Keyword.CONST) { |
keywordToken = getAndAdvance(); |
if (_isTypedIdentifier(_currentToken)) { |
- type = parseTypeName(); |
+ type = parseTypeName(false); |
} else { |
// Support `final/*=T*/ x;` |
type = _parseOptionalTypeNameComment(); |
@@ -6217,7 +6283,7 @@ class Parser { |
TypeName _parseOptionalTypeNameComment() { |
if (_injectGenericCommentTypeAssign()) { |
- return _parseTypeName(); |
+ return _parseTypeName(false); |
} |
return null; |
} |
@@ -6555,7 +6621,7 @@ class Parser { |
Keyword keyword = _currentToken.keyword; |
if (keyword == Keyword.AS) { |
Token asOperator = getAndAdvance(); |
- return new AsExpression(expression, asOperator, parseTypeName()); |
+ return new AsExpression(expression, asOperator, parseTypeName(true)); |
} else if (keyword == Keyword.IS) { |
Token isOperator = getAndAdvance(); |
Token notOperator = null; |
@@ -6563,7 +6629,7 @@ class Parser { |
notOperator = getAndAdvance(); |
} |
return new IsExpression( |
- expression, isOperator, notOperator, parseTypeName()); |
+ expression, isOperator, notOperator, parseTypeName(true)); |
} else if (_currentToken.type.isRelationalOperator) { |
Token operator = getAndAdvance(); |
return new BinaryExpression( |
@@ -7002,7 +7068,7 @@ class Parser { |
TypeName exceptionType = null; |
if (_matchesString(_ON)) { |
onKeyword = getAndAdvance(); |
- exceptionType = parseTypeName(); |
+ exceptionType = parseTypeName(false); |
} |
Token catchKeyword = null; |
Token leftParenthesis = null; |
@@ -7092,7 +7158,7 @@ class Parser { |
return _parseFunctionTypeAlias(commentAndMetadata, keyword); |
} |
- TypeName _parseTypeName() { |
+ TypeName _parseTypeName(bool inExpression) { |
Identifier typeName; |
if (_matchesIdentifier()) { |
typeName = _parsePrefixedIdentifierUnchecked(); |
@@ -7104,7 +7170,13 @@ class Parser { |
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TYPE_NAME); |
} |
TypeArgumentList typeArguments = _parseOptionalTypeArguments(); |
- return new TypeName(typeName, typeArguments); |
+ Token question = null; |
+ if (enableNnbd && _matches(TokenType.QUESTION)) { |
+ if (!inExpression || !_isConditionalOperator()) { |
+ question = getAndAdvance(); |
+ } |
+ } |
+ return new TypeName(typeName, typeArguments, question: question); |
} |
/** |
@@ -7516,6 +7588,40 @@ class Parser { |
} |
/** |
+ * Execute the given [parseOperation] in a temporary parser whose current |
+ * token has been set to the given [startToken]. If the parse does not |
+ * generate any errors or exceptions, then return the token following the |
+ * matching portion of the token stream. Otherwise, return `null`. |
+ * |
+ * Note: This is an extremely inefficient way of testing whether the tokens in |
+ * the token stream match a given production. It should not be used for |
+ * production code. |
+ */ |
+ Token _skip(Token startToken, parseOperation(Parser parser)) { |
+ BooleanErrorListener listener = new BooleanErrorListener(); |
+ Parser parser = new Parser(_source, listener); |
+ parser._currentToken = _cloneTokens(startToken); |
+ parser._enableAssertInitializer = _enableAssertInitializer; |
+ parser._enableNnbd = _enableNnbd; |
+ parser._inAsync = _inAsync; |
+ parser._inGenerator = _inGenerator; |
+ parser._inInitializer = _inInitializer; |
+ parser._inLoop = _inLoop; |
+ parser._inSwitch = _inSwitch; |
+ parser._parseAsync = _parseAsync; |
+ parser._parseFunctionBodies = _parseFunctionBodies; |
+ try { |
+ parseOperation(parser); |
+ } catch (exception) { |
+ return null; |
+ } |
+ if (listener.errorReported) { |
+ return null; |
+ } |
+ return parser._currentToken; |
+ } |
+ |
+ /** |
* Skips a block with all containing blocks. |
*/ |
void _skipBlock() { |