Skip to content

Commit

Permalink
fix parsing of "?.." cascade operator
Browse files Browse the repository at this point in the history
Fix dart-lang/sdk#37859

Change-Id: Ie29f4fd000199f0099137308603335e36af6ae26
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/116089
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
  • Loading branch information
danrubel authored and commit-bot@chromium.org committed Sep 10, 2019
1 parent 13fc090 commit 30ea777
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 21 deletions.
3 changes: 2 additions & 1 deletion pkg/analyzer/lib/dart/ast/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,8 @@ abstract class BreakStatement implements Statement {
/// [Expression] cascadeSection*
///
/// cascadeSection ::=
/// '..' (cascadeSelector arguments*) (assignableSelector arguments*)*
/// ('..' | '?..') (cascadeSelector arguments*)
/// (assignableSelector arguments*)*
/// (assignmentOperator expressionWithoutCascade)?
///
/// cascadeSelector ::=
Expand Down
14 changes: 9 additions & 5 deletions pkg/analyzer/lib/src/dart/ast/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5913,8 +5913,8 @@ class IndexExpressionImpl extends ExpressionImpl implements IndexExpression {
/// index expression is part of a cascade expression.
ExpressionImpl _target;

/// The period ("..") before a cascaded index expression, or `null` if this
/// index expression is not part of a cascade expression.
/// The period (".." | "?..") before a cascaded index expression,
/// or `null` if this index expression is not part of a cascade expression.
@override
Token period;

Expand Down Expand Up @@ -7131,7 +7131,7 @@ class MethodInvocationImpl extends InvocationExpressionImpl
/// The operator that separates the target from the method name, or `null`
/// if there is no target. In an ordinary method invocation this will be a
/// period ('.'). In a cascade section this will be the cascade operator
/// ('..').
/// ('..' | '?..').
@override
Token operator;

Expand Down Expand Up @@ -7180,7 +7180,9 @@ class MethodInvocationImpl extends InvocationExpressionImpl

@override
bool get isCascaded =>
operator != null && operator.type == TokenType.PERIOD_PERIOD;
operator != null &&
(operator.type == TokenType.PERIOD_PERIOD ||
operator.type == TokenType.QUESTION_PERIOD_PERIOD);

@override
SimpleIdentifier get methodName => _methodName;
Expand Down Expand Up @@ -8437,7 +8439,9 @@ class PropertyAccessImpl extends ExpressionImpl implements PropertyAccess {

@override
bool get isCascaded =>
operator != null && operator.type == TokenType.PERIOD_PERIOD;
operator != null &&
(operator.type == TokenType.PERIOD_PERIOD ||
operator.type == TokenType.QUESTION_PERIOD_PERIOD);

@override
Precedence get precedence => Precedence.postfix;
Expand Down
12 changes: 6 additions & 6 deletions pkg/analyzer/lib/src/dart/ast/utilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7526,7 +7526,7 @@ class ToSourceVisitor implements AstVisitor<void> {
@override
void visitIndexExpression(IndexExpression node) {
if (node.isCascaded) {
_writer.print("..");
_writer.print(node.period.lexeme);
} else {
_visitNode(node.target);
}
Expand Down Expand Up @@ -7635,7 +7635,7 @@ class ToSourceVisitor implements AstVisitor<void> {
@override
void visitMethodInvocation(MethodInvocation node) {
if (node.isCascaded) {
_writer.print("..");
_writer.print(node.operator.lexeme);
} else {
if (node.target != null) {
node.target.accept(this);
Expand Down Expand Up @@ -7735,7 +7735,7 @@ class ToSourceVisitor implements AstVisitor<void> {
@override
void visitPropertyAccess(PropertyAccess node) {
if (node.isCascaded) {
_writer.print("..");
_writer.print(node.operator.lexeme);
} else {
_visitNode(node.target);
_writer.print(node.operator.lexeme);
Expand Down Expand Up @@ -8833,7 +8833,7 @@ class ToSourceVisitor2 implements AstVisitor<void> {
@override
void visitIndexExpression(IndexExpression node) {
if (node.isCascaded) {
sink.write("..");
sink.write(node.period.lexeme);
} else {
safelyVisitNode(node.target);
}
Expand Down Expand Up @@ -8942,7 +8942,7 @@ class ToSourceVisitor2 implements AstVisitor<void> {
@override
void visitMethodInvocation(MethodInvocation node) {
if (node.isCascaded) {
sink.write("..");
sink.write(node.operator.lexeme);
} else {
if (node.target != null) {
node.target.accept(this);
Expand Down Expand Up @@ -9042,7 +9042,7 @@ class ToSourceVisitor2 implements AstVisitor<void> {
@override
void visitPropertyAccess(PropertyAccess node) {
if (node.isCascaded) {
sink.write("..");
sink.write(node.operator.lexeme);
} else {
safelyVisitNode(node.target);
sink.write(node.operator.lexeme);
Expand Down
8 changes: 5 additions & 3 deletions pkg/analyzer/lib/src/fasta/ast_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ class AstBuilder extends StackListener {
}

void beginCascade(Token token) {
assert(optional('..', token));
assert(optional('..', token) || optional('?..', token));
debugEvent("beginCascade");

Expression expression = pop();
Expand Down Expand Up @@ -577,12 +577,14 @@ class AstBuilder extends StackListener {
assert(operatorToken.isOperator ||
optional('.', operatorToken) ||
optional('?.', operatorToken) ||
optional('..', operatorToken));
optional('..', operatorToken) ||
optional('?..', operatorToken));
debugEvent("BinaryExpression");

if (identical(".", operatorToken.stringValue) ||
identical("?.", operatorToken.stringValue) ||
identical("..", operatorToken.stringValue)) {
identical("..", operatorToken.stringValue) ||
identical("?..", operatorToken.stringValue)) {
doDotExpression(operatorToken);
} else {
Expression right = pop();
Expand Down
43 changes: 43 additions & 0 deletions pkg/analyzer/test/generated/parser_fasta_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2544,6 +2544,16 @@ class NNBDParserTest_Fasta extends FastaParserTestCase {
parseCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x + bar(7); }');
}

void test_assignment_complex2() {
parseCompilationUnit(r'''
main() {
A? a;
String? s = '';
a?..foo().length..x27 = s!..toString().length;
}
''');
}

void test_assignment_simple() {
parseCompilationUnit('D? foo(X? x) { X? x1; X? x2 = x; }');
}
Expand All @@ -2561,6 +2571,39 @@ class NNBDParserTest_Fasta extends FastaParserTestCase {
expect(rhs.name, 'x2');
}

void test_cascade_withNullCheck_indexExpression() {
var unit = parseCompilationUnit('main() { a?..[27]; }');
FunctionDeclaration funct = unit.declarations[0];
BlockFunctionBody body = funct.functionExpression.body;
ExpressionStatement statement = body.block.statements[0];
CascadeExpression cascade = statement.expression;
IndexExpression indexExpression = cascade.cascadeSections[0];
expect(indexExpression.period.lexeme, '?..');
expect(indexExpression.toSource(), '?..[27]');
}

void test_cascade_withNullCheck_methodInvocation() {
var unit = parseCompilationUnit('main() { a?..foo(); }');
FunctionDeclaration funct = unit.declarations[0];
BlockFunctionBody body = funct.functionExpression.body;
ExpressionStatement statement = body.block.statements[0];
CascadeExpression cascade = statement.expression;
MethodInvocation invocation = cascade.cascadeSections[0];
expect(invocation.operator.lexeme, '?..');
expect(invocation.toSource(), '?..foo()');
}

void test_cascade_withNullCheck_propertyAccess() {
var unit = parseCompilationUnit('main() { a?..x27; }');
FunctionDeclaration funct = unit.declarations[0];
BlockFunctionBody body = funct.functionExpression.body;
ExpressionStatement statement = body.block.statements[0];
CascadeExpression cascade = statement.expression;
PropertyAccess propertyAccess = cascade.cascadeSections[0];
expect(propertyAccess.operator.lexeme, '?..');
expect(propertyAccess.toSource(), '?..x27');
}

void test_conditional() {
parseCompilationUnit('D? foo(X? x) { X ? 7 : y; }');
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/front_end/lib/src/fasta/kernel/body_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1479,7 +1479,9 @@ class BodyBuilder extends ScopeListener<JumpTarget>
@override
void endBinaryExpression(Token token) {
debugEvent("BinaryExpression");
if (optional(".", token) || optional("..", token)) {
if (optional(".", token) ||
optional("..", token) ||
optional("?..", token)) {
return doDotOrCascadeExpression(token);
}
if (optional("&&", token) || optional("||", token)) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/front_end/lib/src/fasta/parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4285,7 +4285,7 @@ class Parser {

Token parseCascadeExpression(Token token) {
Token cascadeOperator = token = token.next;
assert(optional('..', cascadeOperator));
assert(optional('..', cascadeOperator) || optional('?..', cascadeOperator));
listener.beginCascade(cascadeOperator);
if (optional('[', token.next)) {
token = parseArgumentOrIndexStar(token, noTypeParamOrArg);
Expand Down
14 changes: 10 additions & 4 deletions pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -877,16 +877,22 @@ abstract class AbstractScanner implements Scanner {
}

int tokenizeQuestion(int next) {
// ? ?. ?? ??=
// ? ?. ?.. ?? ??=
next = advance();
if (identical(next, $QUESTION)) {
return select(
$EQ, TokenType.QUESTION_QUESTION_EQ, TokenType.QUESTION_QUESTION);
} else if (identical(next, $PERIOD)) {
next = advance();
if (_enableNonNullable && identical($OPEN_SQUARE_BRACKET, next)) {
appendBeginGroup(TokenType.QUESTION_PERIOD_OPEN_SQUARE_BRACKET);
return advance();
if (_enableNonNullable) {
if (identical($PERIOD, next)) {
appendPrecedenceToken(TokenType.QUESTION_PERIOD_PERIOD);
return advance();
}
if (identical($OPEN_SQUARE_BRACKET, next)) {
appendBeginGroup(TokenType.QUESTION_PERIOD_OPEN_SQUARE_BRACKET);
return advance();
}
}
appendPrecedenceToken(TokenType.QUESTION_PERIOD);
return next;
Expand Down
2 changes: 2 additions & 0 deletions pkg/front_end/lib/src/fasta/scanner/token_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,5 @@ const int GT_GT_GT_TOKEN = GENERIC_METHOD_TYPE_LIST_TOKEN + 1;
const int PERIOD_PERIOD_PERIOD_QUESTION_TOKEN = GT_GT_GT_TOKEN + 1;
const int GT_GT_GT_EQ_TOKEN = PERIOD_PERIOD_PERIOD_QUESTION_TOKEN + 1;
const int QUESTION_PERIOD_OPEN_SQUARE_BRACKET_TOKEN = GT_GT_GT_EQ_TOKEN + 1;
const int QUESTION_PERIOD_PERIOD_TOKEN =
QUESTION_PERIOD_OPEN_SQUARE_BRACKET_TOKEN + 1;
6 changes: 6 additions & 0 deletions pkg/front_end/lib/src/scanner/token.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,12 @@ class TokenType {
SELECTOR_PRECEDENCE,
QUESTION_PERIOD_OPEN_SQUARE_BRACKET_TOKEN);

static const TokenType QUESTION_PERIOD_PERIOD = const TokenType(
'?..',
'QUESTION_PERIOD_PERIOD',
CASCADE_PRECEDENCE,
QUESTION_PERIOD_PERIOD_TOKEN);

static const TokenType AS = Keyword.AS;

static const TokenType IS = Keyword.IS;
Expand Down

0 comments on commit 30ea777

Please sign in to comment.