Bläddra i källkod

Update precedence rules to match design. (#3081)

- Only allow assignment at the top level in an expression statement.
- Allow both negation and complement as subexpressions of both bitwise
  and numeric operators.
- Remove parsing support for postincrement and postdecrement.
- Add parsing support for `as` operator.
- Use the same ambient precedence for types and non-type expressions.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Richard Smith 2 år sedan
förälder
incheckning
fc5a9541ce

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -63,6 +63,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedStructLiteralField)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableDeclaration)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableName)
 CARBON_DIAGNOSTIC_KIND(OperatorRequiresParentheses)
+CARBON_DIAGNOSTIC_KIND(StatementOperatorAsSubexpression)
 CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresParentheses)
 CARBON_DIAGNOSTIC_KIND(UnaryOperatorHasWhitespace)
 CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresWhitespace)

+ 34 - 11
toolchain/parser/parser_handle_expression.cpp

@@ -6,6 +6,15 @@
 
 namespace Carbon {
 
+static auto DiagnoseStatementOperatorAsSubexpression(ParserContext& context)
+    -> void {
+  CARBON_DIAGNOSTIC(StatementOperatorAsSubexpression, Error,
+                    "Operator `{0}` can only be used as a complete statement.",
+                    TokenKind);
+  context.emitter().Emit(*context.position(), StatementOperatorAsSubexpression,
+                         context.PositionKind());
+}
+
 auto ParserHandleExpression(ParserContext& context) -> void {
   auto state = context.PopState();
 
@@ -17,13 +26,20 @@ auto ParserHandleExpression(ParserContext& context) -> void {
         OperatorPriority::RightFirst) {
       // The precedence rules don't permit this prefix operator in this
       // context. Diagnose this, but carry on and parse it anyway.
-      CARBON_DIAGNOSTIC(
-          UnaryOperatorRequiresParentheses, Error,
-          "Parentheses are required around this unary `{0}` operator.",
-          TokenKind);
-      context.emitter().Emit(*context.position(),
-                             UnaryOperatorRequiresParentheses,
-                             context.PositionKind());
+      if (PrecedenceGroup::GetPriority(PrecedenceGroup::ForTopLevelExpression(),
+                                       *operator_precedence) ==
+          OperatorPriority::RightFirst) {
+        CARBON_DIAGNOSTIC(
+            UnaryOperatorRequiresParentheses, Error,
+            "Parentheses are required around this unary `{0}` operator.",
+            TokenKind);
+        context.emitter().Emit(*context.position(),
+                               UnaryOperatorRequiresParentheses,
+                               context.PositionKind());
+      } else {
+        // This operator wouldn't be allowed even if parenthesized.
+        DiagnoseStatementOperatorAsSubexpression(context);
+      }
     } else {
       // Check that this operator follows the proper whitespace rules.
       context.DiagnoseOperatorFixity(ParserContext::OperatorFixity::Prefix);
@@ -188,10 +204,17 @@ auto ParserHandleExpressionLoop(ParserContext& context) -> void {
     // Either the LHS operator and this operator are ambiguous, or the
     // LHS operator is a unary operator that can't be nested within
     // this operator. Either way, parentheses are required.
-    CARBON_DIAGNOSTIC(
-        OperatorRequiresParentheses, Error,
-        "Parentheses are required to disambiguate operator precedence.");
-    context.emitter().Emit(*context.position(), OperatorRequiresParentheses);
+    if (PrecedenceGroup::GetPriority(PrecedenceGroup::ForTopLevelExpression(),
+                                     operator_precedence) ==
+        OperatorPriority::RightFirst) {
+      CARBON_DIAGNOSTIC(
+          OperatorRequiresParentheses, Error,
+          "Parentheses are required to disambiguate operator precedence.");
+      context.emitter().Emit(*context.position(), OperatorRequiresParentheses);
+    } else {
+      // This operator wouldn't be allowed even if parenthesized.
+      DiagnoseStatementOperatorAsSubexpression(context);
+    }
     state.has_error = true;
   } else {
     context.DiagnoseOperatorFixity(

+ 1 - 1
toolchain/parser/parser_handle_statement.cpp

@@ -46,7 +46,7 @@ auto ParserHandleStatement(ParserContext& context) -> void {
     }
     default: {
       context.PushState(ParserState::ExpressionStatementFinish);
-      context.PushState(ParserState::Expression);
+      context.PushStateForExpression(PrecedenceGroup::ForExpressionStatement());
       break;
     }
   }

+ 31 - 39
toolchain/parser/precedence.cpp

@@ -18,7 +18,6 @@ enum PrecedenceLevel : int8_t {
   TermPrefix,
   // Numeric.
   NumericPrefix,
-  NumericPostfix,
   Modulo,
   Multiplicative,
   Additive,
@@ -31,8 +30,8 @@ enum PrecedenceLevel : int8_t {
   // Type formation.
   TypePrefix,
   TypePostfix,
-  // Sentinel representing a type context.
-  Type,
+  // Casts.
+  As,
   // Logical.
   LogicalPrefix,
   Relational,
@@ -41,8 +40,7 @@ enum PrecedenceLevel : int8_t {
   // Conditional.
   If,
   // Assignment.
-  SimpleAssignment,
-  CompoundAssignment,
+  Assignment,
   // Sentinel representing a context in which any operator can appear.
   Lowest,
 };
@@ -54,27 +52,24 @@ struct OperatorPriorityTable {
   constexpr OperatorPriorityTable() : table() {
     // Start with a list of <higher precedence>, <lower precedence>
     // relationships.
-    MarkHigherThan({Highest}, {TermPrefix});
-    MarkHigherThan({TermPrefix}, {NumericPrefix, BitwisePrefix, LogicalPrefix,
-                                  NumericPostfix});
-    MarkHigherThan({NumericPrefix, NumericPostfix},
-                   {Modulo, Multiplicative, BitShift});
+    MarkHigherThan({Highest}, {TermPrefix, LogicalPrefix});
+    MarkHigherThan({TermPrefix}, {NumericPrefix, BitwisePrefix});
+    MarkHigherThan({NumericPrefix, BitwisePrefix},
+                   {As, Multiplicative, Modulo, BitwiseAnd, BitwiseOr,
+                    BitwiseXor, BitShift});
     MarkHigherThan({Multiplicative}, {Additive});
-    MarkHigherThan({BitwisePrefix},
-                   {BitwiseAnd, BitwiseOr, BitwiseXor, BitShift});
     MarkHigherThan(
-        {Modulo, Additive, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift},
+        {As, Additive, Modulo, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift},
         {Relational});
     MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr});
     MarkHigherThan({LogicalAnd, LogicalOr}, {If});
-    MarkHigherThan({If}, {SimpleAssignment, CompoundAssignment});
-    MarkHigherThan({SimpleAssignment, CompoundAssignment}, {Lowest});
+    MarkHigherThan({If}, {Assignment});
+    MarkHigherThan({Assignment}, {Lowest});
 
     // Types are mostly a separate precedence graph.
     MarkHigherThan({Highest}, {TypePrefix});
     MarkHigherThan({TypePrefix}, {TypePostfix});
-    MarkHigherThan({TypePostfix}, {Type});
-    MarkHigherThan({Type}, {If});
+    MarkHigherThan({TypePostfix}, {As});
 
     // Compute the transitive closure of the above relationships: if we parse
     // `a $ b @ c` as `(a $ b) @ c` and parse `b @ c % d` as `(b @ c) % d`,
@@ -142,17 +137,13 @@ struct OperatorPriorityTable {
     // Associativity rules occupy the diagonal
 
     // For prefix operators, RightFirst would mean `@@x` is `@(@x)` and
-    // Ambiguous would mean it's an error. LeftFirst is meaningless. For now we
-    // allow all prefix operators other than `const` to be repeated.
-    //
-    // TODO: The design does not permit repeating most unary operators.
-    for (PrecedenceLevel prefix :
-         {TermPrefix, NumericPrefix, BitwisePrefix, LogicalPrefix, If}) {
+    // Ambiguous would mean it's an error. LeftFirst is meaningless.
+    for (PrecedenceLevel prefix : {TermPrefix, If}) {
       table[prefix][prefix] = OperatorPriority::RightFirst;
     }
 
     // Postfix operators are symmetric with prefix operators.
-    for (PrecedenceLevel postfix : {NumericPostfix, TypePostfix}) {
+    for (PrecedenceLevel postfix : {TypePostfix}) {
       table[postfix][postfix] = OperatorPriority::LeftFirst;
     }
 
@@ -164,12 +155,7 @@ struct OperatorPriorityTable {
       table[assoc][assoc] = OperatorPriority::LeftFirst;
     }
 
-    // Assignment is given right-to-left associativity in order to support
-    // chained assignment.
-    table[SimpleAssignment][SimpleAssignment] = OperatorPriority::RightFirst;
-
-    // For other operators, there isn't an obvious answer and we require
-    // explicit parentheses.
+    // For other operators, we require explicit parentheses.
   }
 
   constexpr void ConsistencyCheck() {
@@ -196,11 +182,15 @@ auto PrecedenceGroup::ForPostfixExpression() -> PrecedenceGroup {
 }
 
 auto PrecedenceGroup::ForTopLevelExpression() -> PrecedenceGroup {
+  return PrecedenceGroup(If);
+}
+
+auto PrecedenceGroup::ForExpressionStatement() -> PrecedenceGroup {
   return PrecedenceGroup(Lowest);
 }
 
 auto PrecedenceGroup::ForType() -> PrecedenceGroup {
-  return PrecedenceGroup(Type);
+  return ForTopLevelExpression();
 }
 
 auto PrecedenceGroup::ForLeading(TokenKind kind)
@@ -214,9 +204,11 @@ auto PrecedenceGroup::ForLeading(TokenKind kind)
       return PrecedenceGroup(LogicalPrefix);
 
     case TokenKind::Minus:
+      return PrecedenceGroup(NumericPrefix);
+
     case TokenKind::MinusMinus:
     case TokenKind::PlusPlus:
-      return PrecedenceGroup(NumericPrefix);
+      return PrecedenceGroup(Assignment);
 
     case TokenKind::Caret:
       return PrecedenceGroup(BitwisePrefix);
@@ -237,7 +229,6 @@ auto PrecedenceGroup::ForTrailing(TokenKind kind, bool infix)
   switch (kind) {
     // Assignment operators.
     case TokenKind::Equal:
-      return Trailing{.level = SimpleAssignment, .is_binary = true};
     case TokenKind::PlusEqual:
     case TokenKind::MinusEqual:
     case TokenKind::StarEqual:
@@ -248,7 +239,7 @@ auto PrecedenceGroup::ForTrailing(TokenKind kind, bool infix)
     case TokenKind::CaretEqual:
     case TokenKind::GreaterGreaterEqual:
     case TokenKind::LessLessEqual:
-      return Trailing{.level = CompoundAssignment, .is_binary = true};
+      return Trailing{.level = Assignment, .is_binary = true};
 
     // Logical operators.
     case TokenKind::And:
@@ -293,14 +284,15 @@ auto PrecedenceGroup::ForTrailing(TokenKind kind, bool infix)
       return infix ? Trailing{.level = Multiplicative, .is_binary = true}
                    : Trailing{.level = TypePostfix, .is_binary = false};
 
-    // Postfix operators.
-    case TokenKind::MinusMinus:
-    case TokenKind::PlusPlus:
-      return Trailing{.level = NumericPostfix, .is_binary = false};
+    // Cast operator.
+    case TokenKind::As:
+      return Trailing{.level = As, .is_binary = true};
 
     // Prefix-only operators.
-    case TokenKind::Not:
     case TokenKind::Const:
+    case TokenKind::MinusMinus:
+    case TokenKind::Not:
+    case TokenKind::PlusPlus:
       break;
 
     // Symbolic tokens that might be operators eventually.

+ 9 - 4
toolchain/parser/precedence.h

@@ -38,15 +38,20 @@ class PrecedenceGroup {
   PrecedenceGroup() = delete;
 
   // Get the sentinel precedence level for a postfix expression. All operators
-  // should have lower precedence than this.
+  // have lower precedence than this.
   static auto ForPostfixExpression() -> PrecedenceGroup;
 
-  // Get the sentinel precedence level for a top-level expression context. All
-  // operators should have higher precedence than this.
+  // Get the precedence level for a top-level or parenthesized expression. All
+  // expression operators have higher precedence than this.
   static auto ForTopLevelExpression() -> PrecedenceGroup;
 
+  // Get the sentinel precedence level for a statement context. All operators,
+  // including statement operators like `=` and `++`, have higher precedence
+  // than this.
+  static auto ForExpressionStatement() -> PrecedenceGroup;
+
   // Get the precedence level at which to parse a type expression. All type
-  // operators should have higher precedence than this.
+  // operators have higher precedence than this.
   static auto ForType() -> PrecedenceGroup;
 
   // Look up the operator information of the given prefix operator token, or

+ 8 - 22
toolchain/parser/precedence_test.cpp

@@ -33,29 +33,19 @@ TEST(PrecedenceTest, OperatorsAreRecognized) {
 
   EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Minus, true)->is_binary);
   EXPECT_FALSE(
-      PrecedenceGroup::ForTrailing(TokenKind::MinusMinus, false)->is_binary);
+      PrecedenceGroup::ForTrailing(TokenKind::MinusMinus, false).has_value());
 }
 
 TEST(PrecedenceTest, InfixVsPostfix) {
-  // A trailing `-` is always infix; a trailing `--` is always postfix.
-  EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Minus, false)->is_binary);
-  EXPECT_FALSE(
-      PrecedenceGroup::ForTrailing(TokenKind::MinusMinus, true)->is_binary);
+  // A trailing `-` is always binary, even when written with whitespace that
+  // suggests it's postfix.
+  EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Minus, /*infix*/ false)
+                  ->is_binary);
 
   // A trailing `*` is interpreted based on context.
   EXPECT_TRUE(PrecedenceGroup::ForTrailing(TokenKind::Star, true)->is_binary);
   EXPECT_FALSE(PrecedenceGroup::ForTrailing(TokenKind::Star, false)->is_binary);
 
-  // Postfix `*` can appear in type contexts; infix `*` cannot.
-  EXPECT_THAT(PrecedenceGroup::GetPriority(
-                  PrecedenceGroup::ForTrailing(TokenKind::Star, true)->level,
-                  PrecedenceGroup::ForType()),
-              Eq(OperatorPriority::Ambiguous));
-  EXPECT_THAT(PrecedenceGroup::GetPriority(
-                  PrecedenceGroup::ForTrailing(TokenKind::Star, false)->level,
-                  PrecedenceGroup::ForType()),
-              Eq(OperatorPriority::LeftFirst));
-
   // Infix `*` can appear in `+` contexts; postfix `*` cannot.
   EXPECT_THAT(PrecedenceGroup::GetPriority(
                   PrecedenceGroup::ForTrailing(TokenKind::Star, true)->level,
@@ -69,18 +59,14 @@ TEST(PrecedenceTest, InfixVsPostfix) {
 
 TEST(PrecedenceTest, Associativity) {
   EXPECT_THAT(PrecedenceGroup::ForLeading(TokenKind::Minus)->GetAssociativity(),
+              Eq(Associativity::None));
+  EXPECT_THAT(PrecedenceGroup::ForLeading(TokenKind::Star)->GetAssociativity(),
               Eq(Associativity::RightToLeft));
-  EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::PlusPlus, false)
-                  ->level.GetAssociativity(),
-              Eq(Associativity::LeftToRight));
   EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::Plus, true)
                   ->level.GetAssociativity(),
               Eq(Associativity::LeftToRight));
   EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::Equal, true)
                   ->level.GetAssociativity(),
-              Eq(Associativity::RightToLeft));
-  EXPECT_THAT(PrecedenceGroup::ForTrailing(TokenKind::PlusEqual, true)
-                  ->level.GetAssociativity(),
               Eq(Associativity::None));
 }
 
@@ -134,7 +120,7 @@ TEST(PrecedenceTest, IncomparableOperators) {
                   *PrecedenceGroup::ForLeading(TokenKind::Minus)),
               Eq(OperatorPriority::Ambiguous));
   EXPECT_THAT(PrecedenceGroup::GetPriority(
-                  *PrecedenceGroup::ForLeading(TokenKind::Minus),
+                  *PrecedenceGroup::ForLeading(TokenKind::Not),
                   PrecedenceGroup::ForTrailing(TokenKind::Amp, true)->level),
               Eq(OperatorPriority::Ambiguous));
   EXPECT_THAT(

+ 43 - 0
toolchain/parser/testdata/if_expression/in_type.carbon

@@ -0,0 +1,43 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+var n: i32;
+
+fn F() -> if true then i32 else i32* {
+  return if true then n else &n;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'n'},
+// CHECK:STDOUT:     {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:   {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT: {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:         {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'ReturnType', text: '->', subtree_size: 8},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 13},
+// CHECK:STDOUT:     {kind: 'ReturnStatementStart', text: 'return'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:       {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:       {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'IfExpressionElse', text: 'else', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ReturnStatement', text: ';', subtree_size: 9},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 23},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 97 - 0
toolchain/parser/testdata/operators/assign.carbon

@@ -0,0 +1,97 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F() {
+  var a: i32 = 0;
+  var b: i32 = 1;
+  a = b;
+  a *= b;
+  a /= b;
+  a += b;
+  a -= b;
+  a %= b;
+  a &= b;
+  a |= b;
+  a ^= b;
+  a <<= b;
+  a >>= b;
+  ++a;
+  --a;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'a'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:     {kind: 'Literal', text: '0'},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'b'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:     {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '*=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '/=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '+=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '-=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '%=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '&=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '|=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '^=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '<<=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '>>=', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:     {kind: 'PrefixOperator', text: '++', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:     {kind: 'PrefixOperator', text: '--', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 3},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 70},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 42 - 0
toolchain/parser/testdata/operators/fail_chained_assign.carbon

@@ -0,0 +1,42 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F() {
+  var a: i32;
+  var b: i32;
+
+  // TODO: Produce a custom diagnostic for this.
+  // CHECK:STDERR: fail_chained_assign.carbon:[[@LINE+3]]:9: Operator `=` can only be used as a complete statement.
+  // CHECK:STDERR:   a = b = 1;
+  // CHECK:STDERR:         ^
+  a = b = 1;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'a'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'b'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: '=', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '=', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 22},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 39 - 0
toolchain/parser/testdata/operators/fail_postincrement.carbon

@@ -0,0 +1,39 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F() {
+  var n: i32 = 0;
+  // TODO: It'd be nice to produce a custom diagnostic here.
+  // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+3]]:4: Expected `;` after expression statement.
+  // CHECK:STDERR:   n++;
+  // CHECK:STDERR:    ^
+  n++;
+  // CHECK:STDERR: fail_postincrement.carbon:[[@LINE+3]]:4: Expected `;` after expression statement.
+  // CHECK:STDERR:   n--;
+  // CHECK:STDERR:    ^
+  n--;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'n'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:     {kind: 'Literal', text: '0'},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 53 - 0
toolchain/parser/testdata/operators/fail_precedence_as.carbon

@@ -0,0 +1,53 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F(n: i32) {
+  // No ordering between `not` and `as`.
+  // CHECK:STDERR: fail_precedence_as.carbon:[[@LINE+3]]:12: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   not true as bool;
+  // CHECK:STDERR:            ^
+  not true as bool;
+
+  // No ordering between most binary operators and `as`.
+  // CHECK:STDERR: fail_precedence_as.carbon:[[@LINE+3]]:9: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   1 + 1 as i32;
+  // CHECK:STDERR:         ^
+  1 + 1 as i32;
+  // CHECK:STDERR: fail_precedence_as.carbon:[[@LINE+3]]:9: Parentheses are required to disambiguate operator precedence.
+  // CHECK:STDERR:   5 % 2 as i32;
+  // CHECK:STDERR:         ^
+  5 % 2 as i32;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'Name', text: 'n'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'bool'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:         {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: '+', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'Literal', text: '5'},
+// CHECK:STDOUT:         {kind: 'Literal', text: '2'},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: '%', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 26},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 61 - 0
toolchain/parser/testdata/operators/fail_precedence_assign.carbon

@@ -0,0 +1,61 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F() {
+  var a: i32;
+  // Assignment can only appear as an expression statement, not as a subexpression.
+  // TODO: Produce the "can only be used as a complete statement" diagnostic in each of these cases.
+  // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+3]]:10: Expected `,` or `)`.
+  // CHECK:STDERR:   1 + (a = 1);
+  // CHECK:STDERR:          ^
+  1 + (a = 1);
+  // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+6]]:19: Expected `else` after `if ... then ...`.
+  // CHECK:STDERR:   (if true then a += 1 else a /= 2);
+  // CHECK:STDERR:                   ^
+  // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+3]]:19: Expected `,` or `)`.
+  // CHECK:STDERR:   (if true then a += 1 else a /= 2);
+  // CHECK:STDERR:                   ^
+  (if true then a += 1 else a /= 2);
+  // CHECK:STDERR: fail_precedence_assign.carbon:[[@LINE+3]]:7: Operator `++` can only be used as a complete statement.
+  // CHECK:STDERR:   a + ++a;
+  // CHECK:STDERR:       ^
+  a + ++a;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'a'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:         {kind: 'ParenExpressionOrTupleLiteralStart', text: '('},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'ParenExpression', text: ')', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '+', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'ParenExpressionOrTupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:         {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:         {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: '+=', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'if', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'ParenExpression', text: ')', has_error: yes, subtree_size: 8},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 9},
+// CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '++', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '+', subtree_size: 4},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 5},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 31},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 16 - 13
toolchain/parser/testdata/operators/fail_variety.carbon

@@ -5,6 +5,9 @@
 // AUTOUPDATE
 
 fn F() {
+  // CHECK:STDERR: fail_variety.carbon:[[@LINE+15]]:21: Operator `=` can only be used as a complete statement.
+  // CHECK:STDERR:   n = a * b + c * d = d * d << e & f - not g;
+  // CHECK:STDERR:                     ^
   // CHECK:STDERR: fail_variety.carbon:[[@LINE+12]]:29: Parentheses are required to disambiguate operator precedence.
   // CHECK:STDERR:   n = a * b + c * d = d * d << e & f - not g;
   // CHECK:STDERR:                             ^
@@ -26,7 +29,7 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:       {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
 // CHECK:STDOUT:             {kind: 'NameExpression', text: 'a'},
 // CHECK:STDOUT:             {kind: 'NameExpression', text: 'b'},
 // CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 3},
@@ -34,18 +37,18 @@ fn F() {
 // CHECK:STDOUT:             {kind: 'NameExpression', text: 'd'},
 // CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'InfixOperator', text: '+', subtree_size: 7},
-// CHECK:STDOUT:                 {kind: 'NameExpression', text: 'd'},
-// CHECK:STDOUT:                 {kind: 'NameExpression', text: 'd'},
-// CHECK:STDOUT:               {kind: 'InfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:               {kind: 'NameExpression', text: 'e'},
-// CHECK:STDOUT:             {kind: 'InfixOperator', text: '<<', has_error: yes, subtree_size: 5},
-// CHECK:STDOUT:             {kind: 'NameExpression', text: 'f'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '&', has_error: yes, subtree_size: 7},
-// CHECK:STDOUT:             {kind: 'NameExpression', text: 'g'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '-', has_error: yes, subtree_size: 10},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '=', subtree_size: 18},
-// CHECK:STDOUT:     {kind: 'InfixOperator', text: '=', subtree_size: 20},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: '=', subtree_size: 9},
+// CHECK:STDOUT:               {kind: 'NameExpression', text: 'd'},
+// CHECK:STDOUT:               {kind: 'NameExpression', text: 'd'},
+// CHECK:STDOUT:             {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'NameExpression', text: 'e'},
+// CHECK:STDOUT:           {kind: 'InfixOperator', text: '<<', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'f'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: '&', has_error: yes, subtree_size: 7},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'g'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: '-', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '=', has_error: yes, subtree_size: 20},
 // CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 21},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 27},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 8 - 6
toolchain/parser/testdata/operators/postfix_repeat.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 
 fn F() {
-  n++++;
+  i32****;
 }
 
 // CHECK:STDOUT: [
@@ -14,10 +14,12 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
 // CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
-// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '++', subtree_size: 2},
-// CHECK:STDOUT:     {kind: 'PostfixOperator', text: '++', subtree_size: 3},
-// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
-// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 10},
+// CHECK:STDOUT:             {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'PostfixOperator', text: '*', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]

+ 61 - 0
toolchain/parser/testdata/operators/precedence_as.carbon

@@ -0,0 +1,61 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F(n: i32) {
+  // Type operators and unary operators are lower precedence than `as`.
+  -n as const i32;
+  &n as i32*;
+
+  // `as` is lower precedence than relational comparisons and logical operators.
+  if (1 as i32 < 2 as i32 and true as bool and false as bool) {}
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'Name', text: 'n'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: 'const', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'as', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'n'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: 'as', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'IfConditionStart', text: '('},
+// CHECK:STDOUT:                   {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:                   {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:                 {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:                   {kind: 'Literal', text: '2'},
+// CHECK:STDOUT:                   {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:                 {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:               {kind: 'InfixOperator', text: '<', subtree_size: 7},
+// CHECK:STDOUT:             {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 8},
+// CHECK:STDOUT:               {kind: 'Literal', text: 'true'},
+// CHECK:STDOUT:               {kind: 'Literal', text: 'bool'},
+// CHECK:STDOUT:             {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperator', text: 'and', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'ShortCircuitOperand', text: 'and', subtree_size: 13},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'false'},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'bool'},
+// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperator', text: 'and', subtree_size: 17},
+// CHECK:STDOUT:     {kind: 'IfCondition', text: ')', subtree_size: 19},
+// CHECK:STDOUT:       {kind: 'CodeBlockStart', text: '{'},
+// CHECK:STDOUT:     {kind: 'CodeBlock', text: '}', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'IfStatement', text: 'if', subtree_size: 22},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 43},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 78 - 0
toolchain/parser/testdata/operators/precedence_assign.carbon

@@ -0,0 +1,78 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F(c: bool) {
+  var a: i32;
+  var b: i32;
+  var p: i32*;
+  *p = if c then 1 else 2;
+  // These are valid to _parse_ even though rejected semantically.
+  (if c then a else b) += if c then 1 else 2;
+  ++if c then a else b;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'Name', text: 'c'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'bool'},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'a'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'b'},
+// CHECK:STDOUT:       {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'p'},
+// CHECK:STDOUT:         {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'PatternBinding', text: ':', subtree_size: 4},
+// CHECK:STDOUT:   {kind: 'VariableDeclaration', text: ';', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'c'},
+// CHECK:STDOUT:         {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:         {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: '2'},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '=', subtree_size: 9},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 10},
+// CHECK:STDOUT:         {kind: 'ParenExpressionOrTupleLiteralStart', text: '('},
+// CHECK:STDOUT:             {kind: 'NameExpression', text: 'c'},
+// CHECK:STDOUT:           {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:           {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:         {kind: 'IfExpressionElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:       {kind: 'ParenExpression', text: ')', subtree_size: 8},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'c'},
+// CHECK:STDOUT:         {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'Literal', text: '1'},
+// CHECK:STDOUT:         {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'Literal', text: '2'},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '+=', subtree_size: 15},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 16},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'c'},
+// CHECK:STDOUT:         {kind: 'IfExpressionIf', text: 'if', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'a'},
+// CHECK:STDOUT:         {kind: 'IfExpressionThen', text: 'then', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'NameExpression', text: 'b'},
+// CHECK:STDOUT:       {kind: 'IfExpressionElse', text: 'else', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'PrefixOperator', text: '++', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 59},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 52 - 0
toolchain/parser/testdata/operators/precedence_unary.carbon

@@ -0,0 +1,52 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F(p: i32*) {
+  // Numeric, bitwise, and bit-shift binary operators are lower precedence than
+  // Numeric and bitwise unary operators, which are lower precedence than
+  // dereference.
+  -*p + ^*p;
+  -*p | ^*p;
+  -*p << ^*p;
+}
+
+// CHECK:STDOUT: [
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'F'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'Name', text: 'p'},
+// CHECK:STDOUT:           {kind: 'Literal', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PatternBinding', text: ':', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 6},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 9},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '^', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '+', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '^', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '|', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'p'},
+// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '^', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'InfixOperator', text: '<<', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 34},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]

+ 0 - 3
toolchain/semantics/testdata/pointer/fail_dereference_type.carbon

@@ -4,9 +4,6 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_dereference_type.carbon:[[@LINE+9]]:8: Parentheses are required around this unary `*` operator.
-// CHECK:STDERR: var p: *i32;
-// CHECK:STDERR:        ^
 // CHECK:STDERR: fail_dereference_type.carbon:[[@LINE+6]]:8: Cannot dereference operand of non-pointer type `type`.
 // CHECK:STDERR: var p: *i32;
 // CHECK:STDERR:        ^