Extra precedence level in parser to distinguish postfix operators and selectors
Fixes https://github.com/dart-lang/sdk/issues/31185
Change-Id: I77d617f6abd367d857741d04cf47571ea7492c6a
Reviewed-on: https://dart-review.googlesource.com/56111
Reviewed-by: Dan Rubel <danrubel@google.com>
Reviewed-by: Peter von der Ahé <ahe@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index e446df1..0b81640 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -21,6 +21,7 @@
Keyword,
POSTFIX_PRECEDENCE,
RELATIONAL_PRECEDENCE,
+ SELECTOR_PRECEDENCE,
SyntheticBeginToken,
SyntheticKeywordToken,
SyntheticStringToken,
@@ -3963,7 +3964,7 @@
Token parsePrecedenceExpression(
Token token, int precedence, bool allowCascades) {
assert(precedence >= 1);
- assert(precedence <= POSTFIX_PRECEDENCE);
+ assert(precedence <= SELECTOR_PRECEDENCE);
token = parseUnaryExpression(token, allowCascades);
Token next = token.next;
TokenType type = next.type;
@@ -3994,10 +3995,16 @@
token = parsePrecedenceExpression(token.next, level, allowCascades);
listener.handleAssignmentExpression(operator);
} else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
+ if ((identical(type, TokenType.PLUS_PLUS)) ||
+ (identical(type, TokenType.MINUS_MINUS))) {
+ listener.handleUnaryPostfixAssignmentExpression(token.next);
+ token = next;
+ }
+ } else if (identical(tokenLevel, SELECTOR_PRECEDENCE)) {
if (identical(type, TokenType.PERIOD) ||
identical(type, TokenType.QUESTION_PERIOD)) {
// Left associative, so we recurse at the next higher precedence
- // level. However, POSTFIX_PRECEDENCE is the highest level, so we
+ // level. However, SELECTOR_PRECEDENCE is the highest level, so we
// should just call [parseUnaryExpression] directly. However, a
// unary expression isn't legal after a period, so we call
// [parsePrimary] instead.
@@ -4008,10 +4015,6 @@
(identical(type, TokenType.OPEN_SQUARE_BRACKET))) {
token = parseArgumentOrIndexStar(token, typeArguments);
next = token.next;
- } else if ((identical(type, TokenType.PLUS_PLUS)) ||
- (identical(type, TokenType.MINUS_MINUS))) {
- listener.handleUnaryPostfixAssignmentExpression(token.next);
- token = next;
} else if (identical(type, TokenType.INDEX)) {
BeginToken replacement = link(
new BeginToken(TokenType.OPEN_SQUARE_BRACKET, next.charOffset,
diff --git a/pkg/front_end/lib/src/scanner/token.dart b/pkg/front_end/lib/src/scanner/token.dart
index 7842a42..3543578 100644
--- a/pkg/front_end/lib/src/scanner/token.dart
+++ b/pkg/front_end/lib/src/scanner/token.dart
@@ -29,6 +29,7 @@
const int MULTIPLICATIVE_PRECEDENCE = 14;
const int PREFIX_PRECEDENCE = 15;
const int POSTFIX_PRECEDENCE = 16;
+const int SELECTOR_PRECEDENCE = 17;
/**
* The opening half of a grouping pair of tokens. This is used for curly
@@ -1289,7 +1290,7 @@
const TokenType('#', 'HASH', NO_PRECEDENCE, HASH_TOKEN);
static const TokenType INDEX = const TokenType(
- '[]', 'INDEX', POSTFIX_PRECEDENCE, INDEX_TOKEN,
+ '[]', 'INDEX', SELECTOR_PRECEDENCE, INDEX_TOKEN,
isOperator: true, isUserDefinableOperator: true);
static const TokenType INDEX_EQ = const TokenType(
@@ -1328,10 +1329,10 @@
'{', 'OPEN_CURLY_BRACKET', NO_PRECEDENCE, OPEN_CURLY_BRACKET_TOKEN);
static const TokenType OPEN_PAREN =
- const TokenType('(', 'OPEN_PAREN', POSTFIX_PRECEDENCE, OPEN_PAREN_TOKEN);
+ const TokenType('(', 'OPEN_PAREN', SELECTOR_PRECEDENCE, OPEN_PAREN_TOKEN);
static const TokenType OPEN_SQUARE_BRACKET = const TokenType('[',
- 'OPEN_SQUARE_BRACKET', POSTFIX_PRECEDENCE, OPEN_SQUARE_BRACKET_TOKEN);
+ 'OPEN_SQUARE_BRACKET', SELECTOR_PRECEDENCE, OPEN_SQUARE_BRACKET_TOKEN);
static const TokenType PERCENT = const TokenType(
'%', 'PERCENT', MULTIPLICATIVE_PRECEDENCE, PERCENT_TOKEN,
@@ -1342,7 +1343,7 @@
isOperator: true);
static const TokenType PERIOD =
- const TokenType('.', 'PERIOD', POSTFIX_PRECEDENCE, PERIOD_TOKEN);
+ const TokenType('.', 'PERIOD', SELECTOR_PRECEDENCE, PERIOD_TOKEN);
static const TokenType PERIOD_PERIOD = const TokenType(
'..', 'PERIOD_PERIOD', CASCADE_PRECEDENCE, PERIOD_PERIOD_TOKEN,
@@ -1365,7 +1366,7 @@
isOperator: true);
static const TokenType QUESTION_PERIOD = const TokenType(
- '?.', 'QUESTION_PERIOD', POSTFIX_PRECEDENCE, QUESTION_PERIOD_TOKEN,
+ '?.', 'QUESTION_PERIOD', SELECTOR_PRECEDENCE, QUESTION_PERIOD_TOKEN,
isOperator: true);
static const TokenType QUESTION_QUESTION = const TokenType(
@@ -1700,6 +1701,12 @@
this == TokenType.PLUS_PLUS ||
this == TokenType.MINUS_MINUS;
+ /**
+ * Return `true` if this type of token represents a selector operator
+ * (starting token of a selector).
+ */
+ bool get isSelectorOperator => precedence == SELECTOR_PRECEDENCE;
+
@override
String toString() => name;
diff --git a/pkg/front_end/test/precedence_info_test.dart b/pkg/front_end/test/precedence_info_test.dart
index 60cb57a..3d6ee26 100644
--- a/pkg/front_end/test/precedence_info_test.dart
+++ b/pkg/front_end/test/precedence_info_test.dart
@@ -216,12 +216,7 @@
void test_isUnaryPostfixOperator() {
const unaryPostfixLexemes = const [
'--',
- '(',
- '[',
- '.',
'++',
- '?.',
- '[]',
];
assertInfo((String source, Token token) {
expect(token.type.isUnaryPostfixOperator,
@@ -245,6 +240,20 @@
});
}
+ void test_isSelectorOperator() {
+ const selectorLexemes = const [
+ '(',
+ '[',
+ '.',
+ '?.',
+ '[]',
+ ];
+ assertInfo((String source, Token token) {
+ expect(token.type.isSelectorOperator, selectorLexemes.contains(source),
+ reason: source);
+ });
+ }
+
void test_isUserDefinableOperator() {
const userDefinableOperatorLexemes = const [
'&',
@@ -358,7 +367,8 @@
/// because it is interpreted as a minus token (precedence 13).
void test_precedence() {
const precedenceTable = const <int, List<String>>{
- 16: const <String>['.', '?.', '++', '--', '[', '('],
+ 17: const <String>['.', '?.', '[', '('],
+ 16: const <String>['++', '--'],
15: const <String>['!', '~'], // excluded '-', '++', '--'
14: const <String>['*', '/', '~/', '%'],
13: const <String>['+', '-'],
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart b/pkg/front_end/testcases/regress/issue_31185.dart
new file mode 100644
index 0000000..568d2af
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+int i = 5;
+
+int test1() {
+ return i ++ (i);
+}
+
+int test2() {
+ return (i) ++ (i);
+}
+
+main() {
+ test1();
+ // Don't call test2, as error recovery has put a runtime error in there.
+}
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart.direct.expect b/pkg/front_end/testcases/regress/issue_31185.dart.direct.expect
new file mode 100644
index 0000000..bf6678e
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart.direct.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static field core::int i = 5;
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_31185.dart:8:15: Error: Expected ';' before this.
+ return i ++ (i);
+ ^", "pkg/front_end/testcases/regress/issue_31185.dart:12:17: Error: Expected ';' before this.
+ return (i) ++ (i);
+ ^"]/* from null */;
+static method test1() → core::int {
+ return let final dynamic #t1 = self::i in let final dynamic #t2 = self::i = #t1.+(1) in #t1;
+ self::i;
+}
+static method test2() → core::int {
+ return let final dynamic #t3 = self::i in let final dynamic #t4 = #t3 in let final dynamic #t5 = invalid-expression "pkg/front_end/testcases/regress/issue_31185.dart:12:12: Error: Can't assign to a parenthesized expression.
+ return (i) ++ (i);
+ ^" in #t4;
+ self::i;
+}
+static method main() → dynamic {
+ self::test1();
+}
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart.direct.transformed.expect b/pkg/front_end/testcases/regress/issue_31185.dart.direct.transformed.expect
new file mode 100644
index 0000000..bf6678e
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart.direct.transformed.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static field core::int i = 5;
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_31185.dart:8:15: Error: Expected ';' before this.
+ return i ++ (i);
+ ^", "pkg/front_end/testcases/regress/issue_31185.dart:12:17: Error: Expected ';' before this.
+ return (i) ++ (i);
+ ^"]/* from null */;
+static method test1() → core::int {
+ return let final dynamic #t1 = self::i in let final dynamic #t2 = self::i = #t1.+(1) in #t1;
+ self::i;
+}
+static method test2() → core::int {
+ return let final dynamic #t3 = self::i in let final dynamic #t4 = #t3 in let final dynamic #t5 = invalid-expression "pkg/front_end/testcases/regress/issue_31185.dart:12:12: Error: Can't assign to a parenthesized expression.
+ return (i) ++ (i);
+ ^" in #t4;
+ self::i;
+}
+static method main() → dynamic {
+ self::test1();
+}
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart.outline.expect b/pkg/front_end/testcases/regress/issue_31185.dart.outline.expect
new file mode 100644
index 0000000..608d890
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart.outline.expect
@@ -0,0 +1,11 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static field core::int i;
+static method test1() → core::int
+ ;
+static method test2() → core::int
+ ;
+static method main() → dynamic
+ ;
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart.strong.expect b/pkg/front_end/testcases/regress/issue_31185.dart.strong.expect
new file mode 100644
index 0000000..a6e3919
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart.strong.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static field core::int i = 5;
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_31185.dart:8:15: Error: Expected ';' before this.
+ return i ++ (i);
+ ^", "pkg/front_end/testcases/regress/issue_31185.dart:12:17: Error: Expected ';' before this.
+ return (i) ++ (i);
+ ^"]/* from null */;
+static method test1() → core::int {
+ return let final core::int #t1 = self::i in let final core::int #t2 = self::i = #t1.{core::num::+}(1) in #t1;
+ self::i;
+}
+static method test2() → core::int {
+ return (let final dynamic #t3 = self::i in let final dynamic #t4 = #t3 in let final dynamic #t5 = invalid-expression "pkg/front_end/testcases/regress/issue_31185.dart:12:12: Error: Can't assign to a parenthesized expression.
+ return (i) ++ (i);
+ ^" in #t4) as{TypeError} core::int;
+ self::i;
+}
+static method main() → dynamic {
+ self::test1();
+}
diff --git a/pkg/front_end/testcases/regress/issue_31185.dart.strong.transformed.expect b/pkg/front_end/testcases/regress/issue_31185.dart.strong.transformed.expect
new file mode 100644
index 0000000..719e1e1
--- /dev/null
+++ b/pkg/front_end/testcases/regress/issue_31185.dart.strong.transformed.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static field core::int i = 5;
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_31185.dart:8:15: Error: Expected ';' before this.
+ return i ++ (i);
+ ^", "pkg/front_end/testcases/regress/issue_31185.dart:12:17: Error: Expected ';' before this.
+ return (i) ++ (i);
+ ^"]/* from null */;
+static method test1() → core::int {
+ return let final core::int #t1 = self::i in let final core::int #t2 = self::i = #t1.{core::num::+}(1) in #t1;
+ self::i;
+}
+static method test2() → core::int {
+ return (let final core::int #t3 = self::i in let final core::int #t4 = #t3 in let final dynamic #t5 = invalid-expression "pkg/front_end/testcases/regress/issue_31185.dart:12:12: Error: Can't assign to a parenthesized expression.
+ return (i) ++ (i);
+ ^" in #t4) as{TypeError} core::int;
+ self::i;
+}
+static method main() → dynamic {
+ self::test1();
+}