Преглед на файлове

Split out infix and prefix operators to separate node kinds. (#3481)

This leaves a single state for each in the expr loop. I was trying to
think through ways to have per-token states, but they felt sort of
bulky.

Note this is more verbose: but I think the long-term is going to be that
when we start wanting to add handlers, we're going to need to switch to
different names based on the token found. As a consequence, the parse
state logic will end up diverging a little, and we'll just want to align
towards boilerplate handlers.

Short-term, this opens up a path for saying that each parse node
corresponds to precisely one token in success states, and separates out
what were becoming big handler functions in check.
Jon Ross-Perkins преди 2 години
родител
ревизия
7c7afc9e32
променени са 55 файла, в които са добавени 562 реда и са изтрити 358 реда
  1. 276 132
      toolchain/check/handle_operator.cpp
  2. 7 3
      toolchain/check/node_stack.h
  3. 1 1
      toolchain/check/testdata/index/fail_negative_indexing.carbon
  4. 1 1
      toolchain/check/testdata/operators/fail_type_mismatch_once.carbon
  5. 1 1
      toolchain/check/testdata/operators/fail_unimplemented_op.carbon
  6. 43 14
      toolchain/parse/handle_expr.cpp
  7. 76 49
      toolchain/parse/node_kind.def
  8. 2 2
      toolchain/parse/state.def
  9. 2 2
      toolchain/parse/testdata/class/fn_definitions.carbon
  10. 1 1
      toolchain/parse/testdata/function/declaration/addr.carbon
  11. 1 1
      toolchain/parse/testdata/function/definition/with_params.carbon
  12. 1 1
      toolchain/parse/testdata/generics/interface/default_fn.carbon
  13. 2 2
      toolchain/parse/testdata/generics/interface/self_pointer.carbon
  14. 2 2
      toolchain/parse/testdata/if_expr/in_type.carbon
  15. 13 13
      toolchain/parse/testdata/operators/assign.carbon
  16. 2 2
      toolchain/parse/testdata/operators/fail_chained_assign.carbon
  17. 1 1
      toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon
  18. 3 3
      toolchain/parse/testdata/operators/fail_invalid_infix.carbon
  19. 6 6
      toolchain/parse/testdata/operators/fail_precedence_as.carbon
  20. 4 4
      toolchain/parse/testdata/operators/fail_precedence_assign.carbon
  21. 2 2
      toolchain/parse/testdata/operators/fail_precedence_star_minus.carbon
  22. 2 2
      toolchain/parse/testdata/operators/fail_precedence_star_star.carbon
  23. 2 2
      toolchain/parse/testdata/operators/fail_star_star_no_space.carbon
  24. 10 10
      toolchain/parse/testdata/operators/fail_variety.carbon
  25. 2 2
      toolchain/parse/testdata/operators/fixity_in_call.carbon
  26. 1 1
      toolchain/parse/testdata/operators/fixity_in_params.carbon
  27. 2 2
      toolchain/parse/testdata/operators/fixity_in_var.carbon
  28. 10 10
      toolchain/parse/testdata/operators/fixity_with_assign.carbon
  29. 1 1
      toolchain/parse/testdata/operators/infix.carbon
  30. 1 1
      toolchain/parse/testdata/operators/infix_no_space.carbon
  31. 1 1
      toolchain/parse/testdata/operators/infix_with_paren_after.carbon
  32. 1 1
      toolchain/parse/testdata/operators/infix_with_paren_before.carbon
  33. 1 1
      toolchain/parse/testdata/operators/postfix.carbon
  34. 4 4
      toolchain/parse/testdata/operators/postfix_repeat.carbon
  35. 1 1
      toolchain/parse/testdata/operators/postfix_space_after_op.carbon
  36. 11 11
      toolchain/parse/testdata/operators/precedence_as.carbon
  37. 4 4
      toolchain/parse/testdata/operators/precedence_assign.carbon
  38. 3 3
      toolchain/parse/testdata/operators/precedence_not.carbon
  39. 16 16
      toolchain/parse/testdata/operators/precedence_unary.carbon
  40. 2 2
      toolchain/parse/testdata/operators/prefix.carbon
  41. 1 1
      toolchain/parse/testdata/operators/prefix_no_space.carbon
  42. 4 4
      toolchain/parse/testdata/operators/prefix_repeat.carbon
  43. 1 1
      toolchain/parse/testdata/operators/recover_infix_uneven_space_before.carbon
  44. 1 1
      toolchain/parse/testdata/operators/recover_postfix_space.carbon
  45. 1 1
      toolchain/parse/testdata/operators/recover_postfix_space_before_comma.carbon
  46. 1 1
      toolchain/parse/testdata/operators/recover_postfix_space_in_call.carbon
  47. 1 1
      toolchain/parse/testdata/operators/recover_postfix_space_surrounding.carbon
  48. 3 3
      toolchain/parse/testdata/operators/recover_prefix_repeat.carbon
  49. 1 1
      toolchain/parse/testdata/operators/recover_prefix_space.carbon
  50. 1 1
      toolchain/parse/testdata/operators/recover_prefix_uneven_space_with_assign.carbon
  51. 1 1
      toolchain/parse/testdata/operators/spaceship.carbon
  52. 6 6
      toolchain/parse/testdata/pointer/const_pointer.carbon
  53. 7 7
      toolchain/parse/testdata/pointer/fail_pointer_type_in_expr.carbon
  54. 4 4
      toolchain/parse/testdata/pointer/pointer_type.carbon
  55. 7 7
      toolchain/parse/testdata/pointer/pointer_value.carbon

+ 276 - 132
toolchain/check/handle_operator.cpp

@@ -7,156 +7,300 @@
 
 namespace Carbon::Check {
 
-auto HandleInfixOperator(Context& context, Parse::NodeId parse_node) -> bool {
+auto HandleInfixOperatorAmp(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorAmp");
+}
+
+auto HandleInfixOperatorAmpEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorAmpEqual");
+}
+
+auto HandleInfixOperatorAs(Context& context, Parse::NodeId parse_node) -> bool {
   auto [rhs_node, rhs_id] = context.node_stack().PopExprWithParseNode();
   auto [lhs_node, lhs_id] = context.node_stack().PopExprWithParseNode();
 
-  // Figure out the operator for the token.
-  auto token = context.parse_tree().node_token(parse_node);
-  switch (auto token_kind = context.tokens().GetKind(token)) {
-    case Lex::TokenKind::As: {
-      auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id);
-      context.node_stack().Push(
-          parse_node,
-          ConvertForExplicitAs(context, parse_node, lhs_id, rhs_type_id));
-      return true;
-    }
-    case Lex::TokenKind::Equal: {
-      // TODO: handle complex assignment expression such as `a += 1`.
-      if (auto lhs_cat = SemIR::GetExprCategory(context.sem_ir(), lhs_id);
-          lhs_cat != SemIR::ExprCategory::DurableRef &&
-          lhs_cat != SemIR::ExprCategory::Error) {
-        CARBON_DIAGNOSTIC(AssignmentToNonAssignable, Error,
-                          "Expression is not assignable.");
-        context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
-      }
-      // TODO: Destroy the old value before reinitializing. This will require
-      // building the destruction code before we build the RHS subexpression.
-      rhs_id = Initialize(context, parse_node, lhs_id, rhs_id);
-      context.AddInst(SemIR::Assign{parse_node, lhs_id, rhs_id});
-      // We model assignment as an expression, so we need to push a value for
-      // it, even though it doesn't produce a value.
-      // TODO: Consider changing our parse tree to model assignment as a
-      // different kind of statement than an expression statement.
-      context.node_stack().Push(parse_node, lhs_id);
-      return true;
-    }
-    default:
-      return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
+  auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id);
+  context.node_stack().Push(
+      parse_node,
+      ConvertForExplicitAs(context, parse_node, lhs_id, rhs_type_id));
+  return true;
+}
+
+auto HandleInfixOperatorCaret(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorCaret");
+}
+
+auto HandleInfixOperatorCaretEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorCaretEqual");
+}
+
+auto HandleInfixOperatorEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  auto [rhs_node, rhs_id] = context.node_stack().PopExprWithParseNode();
+  auto [lhs_node, lhs_id] = context.node_stack().PopExprWithParseNode();
+
+  // TODO: handle complex assignment expression such as `a += 1`.
+  if (auto lhs_cat = SemIR::GetExprCategory(context.sem_ir(), lhs_id);
+      lhs_cat != SemIR::ExprCategory::DurableRef &&
+      lhs_cat != SemIR::ExprCategory::Error) {
+    CARBON_DIAGNOSTIC(AssignmentToNonAssignable, Error,
+                      "Expression is not assignable.");
+    context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
   }
+  // TODO: Destroy the old value before reinitializing. This will require
+  // building the destruction code before we build the RHS subexpression.
+  rhs_id = Initialize(context, parse_node, lhs_id, rhs_id);
+  context.AddInst(SemIR::Assign{parse_node, lhs_id, rhs_id});
+  // We model assignment as an expression, so we need to push a value for
+  // it, even though it doesn't produce a value.
+  // TODO: Consider changing our parse tree to model assignment as a
+  // different kind of statement than an expression statement.
+  context.node_stack().Push(parse_node, lhs_id);
+  return true;
 }
 
-auto HandlePostfixOperator(Context& context, Parse::NodeId parse_node) -> bool {
-  auto value_id = context.node_stack().PopExpr();
+auto HandleInfixOperatorEqualEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorEqualEqual");
+}
 
-  // Figure out the operator for the token.
-  auto token = context.parse_tree().node_token(parse_node);
-  switch (auto token_kind = context.tokens().GetKind(token)) {
-    case Lex::TokenKind::Star: {
-      auto inner_type_id = ExprAsType(context, parse_node, value_id);
-      context.AddInstAndPush(
-          parse_node, SemIR::PointerType{parse_node, SemIR::TypeId::TypeType,
-                                         inner_type_id});
-      return true;
-    }
+auto HandleInfixOperatorExclaimEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorExclaimEqual");
+}
+
+auto HandleInfixOperatorGreater(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorGreater");
+}
+
+auto HandleInfixOperatorGreaterEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorGreaterEqual");
+}
+
+auto HandleInfixOperatorGreaterGreater(Context& context,
+                                       Parse::NodeId parse_node) -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorGreaterGreater");
+}
+
+auto HandleInfixOperatorGreaterGreaterEqual(Context& context,
+                                            Parse::NodeId parse_node) -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorGreaterGreaterEqual");
+}
+
+auto HandleInfixOperatorLess(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorLess");
+}
+
+auto HandleInfixOperatorLessEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorLessEqual");
+}
+
+auto HandleInfixOperatorLessEqualGreater(Context& context,
+                                         Parse::NodeId parse_node) -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorLessEqualGreater");
+}
+
+auto HandleInfixOperatorLessLess(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorLessLess");
+}
+
+auto HandleInfixOperatorLessLessEqual(Context& context,
+                                      Parse::NodeId parse_node) -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorLessLessEqual");
+}
+
+auto HandleInfixOperatorMinus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorMinus");
+}
+
+auto HandleInfixOperatorMinusEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorMinusEqual");
+}
+
+auto HandleInfixOperatorPercent(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPercent");
+}
+
+auto HandleInfixOperatorPercentEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPercentEqual");
+}
+
+auto HandleInfixOperatorPipe(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPipe");
+}
+
+auto HandleInfixOperatorPipeEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPipeEqual");
+}
+
+auto HandleInfixOperatorPlus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPlus");
+}
+
+auto HandleInfixOperatorPlusEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorPlusEqual");
+}
+
+auto HandleInfixOperatorSlash(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorSlash");
+}
+
+auto HandleInfixOperatorSlashEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorSlashEqual");
+}
+
+auto HandleInfixOperatorStar(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorStar");
+}
+
+auto HandleInfixOperatorStarEqual(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandleInfixOperatorStarEqual");
+}
 
+auto HandlePostfixOperatorStar(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  auto value_id = context.node_stack().PopExpr();
+  auto inner_type_id = ExprAsType(context, parse_node, value_id);
+  context.AddInstAndPush(
+      parse_node,
+      SemIR::PointerType{parse_node, SemIR::TypeId::TypeType, inner_type_id});
+  return true;
+}
+
+auto HandlePrefixOperatorAmp(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  auto value_id = context.node_stack().PopExpr();
+  // Only durable reference expressions can have their address taken.
+  switch (SemIR::GetExprCategory(context.sem_ir(), value_id)) {
+    case SemIR::ExprCategory::DurableRef:
+    case SemIR::ExprCategory::Error:
+      break;
+    case SemIR::ExprCategory::EphemeralRef:
+      CARBON_DIAGNOSTIC(AddressOfEphemeralRef, Error,
+                        "Cannot take the address of a temporary object.");
+      context.emitter().Emit(TokenOnly(parse_node), AddressOfEphemeralRef);
+      break;
     default:
-      CARBON_FATAL() << "Unexpected postfix operator " << token_kind;
+      CARBON_DIAGNOSTIC(AddressOfNonRef, Error,
+                        "Cannot take the address of non-reference expression.");
+      context.emitter().Emit(TokenOnly(parse_node), AddressOfNonRef);
+      break;
+  }
+  context.AddInstAndPush(
+      parse_node,
+      SemIR::AddressOf{parse_node,
+                       context.GetPointerType(
+                           parse_node, context.insts().Get(value_id).type_id()),
+                       value_id});
+  return true;
+}
+
+auto HandlePrefixOperatorCaret(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandlePrefixOperatorCaret");
+}
+
+auto HandlePrefixOperatorConst(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  auto value_id = context.node_stack().PopExpr();
+
+  // `const (const T)` is probably not what the developer intended.
+  // TODO: Detect `const (const T)*` and suggest moving the `*` inside the
+  // parentheses.
+  if (context.insts().Get(value_id).kind() == SemIR::ConstType::Kind) {
+    CARBON_DIAGNOSTIC(RepeatedConst, Warning,
+                      "`const` applied repeatedly to the same type has no "
+                      "additional effect.");
+    context.emitter().Emit(parse_node, RepeatedConst);
   }
+  auto inner_type_id = ExprAsType(context, parse_node, value_id);
+  context.AddInstAndPush(
+      parse_node,
+      SemIR::ConstType{parse_node, SemIR::TypeId::TypeType, inner_type_id});
+  return true;
+}
+
+auto HandlePrefixOperatorMinus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandlePrefixOperatorMinus");
+}
+
+auto HandlePrefixOperatorMinusMinus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandlePrefixOperatorMinusMinus");
 }
 
-auto HandlePrefixOperator(Context& context, Parse::NodeId parse_node) -> bool {
+auto HandlePrefixOperatorNot(Context& context, Parse::NodeId parse_node)
+    -> bool {
   auto value_id = context.node_stack().PopExpr();
+  value_id = ConvertToBoolValue(context, parse_node, value_id);
+  context.AddInstAndPush(
+      parse_node,
+      SemIR::UnaryOperatorNot{
+          parse_node, context.insts().Get(value_id).type_id(), value_id});
+  return true;
+}
 
-  // Figure out the operator for the token.
-  auto token = context.parse_tree().node_token(parse_node);
-  switch (auto token_kind = context.tokens().GetKind(token)) {
-    case Lex::TokenKind::Amp: {
-      // Only durable reference expressions can have their address taken.
-      switch (SemIR::GetExprCategory(context.sem_ir(), value_id)) {
-        case SemIR::ExprCategory::DurableRef:
-        case SemIR::ExprCategory::Error:
-          break;
-        case SemIR::ExprCategory::EphemeralRef:
-          CARBON_DIAGNOSTIC(AddressOfEphemeralRef, Error,
-                            "Cannot take the address of a temporary object.");
-          context.emitter().Emit(TokenOnly(parse_node), AddressOfEphemeralRef);
-          break;
-        default:
-          CARBON_DIAGNOSTIC(
-              AddressOfNonRef, Error,
-              "Cannot take the address of non-reference expression.");
-          context.emitter().Emit(TokenOnly(parse_node), AddressOfNonRef);
-          break;
-      }
-      context.AddInstAndPush(
-          parse_node,
-          SemIR::AddressOf{
-              parse_node,
-              context.GetPointerType(parse_node,
-                                     context.insts().Get(value_id).type_id()),
-              value_id});
-      return true;
-    }
+auto HandlePrefixOperatorPlus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandlePrefixOperatorPlus");
+}
 
-    case Lex::TokenKind::Const: {
-      // `const (const T)` is probably not what the developer intended.
-      // TODO: Detect `const (const T)*` and suggest moving the `*` inside the
-      // parentheses.
-      if (context.insts().Get(value_id).kind() == SemIR::ConstType::Kind) {
-        CARBON_DIAGNOSTIC(RepeatedConst, Warning,
-                          "`const` applied repeatedly to the same type has no "
-                          "additional effect.");
-        context.emitter().Emit(parse_node, RepeatedConst);
-      }
-      auto inner_type_id = ExprAsType(context, parse_node, value_id);
-      context.AddInstAndPush(
-          parse_node,
-          SemIR::ConstType{parse_node, SemIR::TypeId::TypeType, inner_type_id});
-      return true;
-    }
+auto HandlePrefixOperatorPlusPlus(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  return context.TODO(parse_node, "HandlePrefixOperatorPlusPlus");
+}
 
-    case Lex::TokenKind::Not:
-      value_id = ConvertToBoolValue(context, parse_node, value_id);
-      context.AddInstAndPush(
-          parse_node,
-          SemIR::UnaryOperatorNot{
-              parse_node, context.insts().Get(value_id).type_id(), value_id});
-      return true;
-
-    case Lex::TokenKind::Star: {
-      value_id = ConvertToValueExpr(context, value_id);
-      auto type_id =
-          context.GetUnqualifiedType(context.insts().Get(value_id).type_id());
-      auto result_type_id = SemIR::TypeId::Error;
-      if (auto pointer_type =
-              context.types().TryGetAs<SemIR::PointerType>(type_id)) {
-        result_type_id = pointer_type->pointee_id;
-      } else if (type_id != SemIR::TypeId::Error) {
-        CARBON_DIAGNOSTIC(
-            DerefOfNonPointer, Error,
-            "Cannot dereference operand of non-pointer type `{0}`.",
-            std::string);
-        auto builder =
-            context.emitter().Build(TokenOnly(parse_node), DerefOfNonPointer,
-                                    context.sem_ir().StringifyType(type_id));
-        // TODO: Check for any facet here, rather than only a type.
-        if (type_id == SemIR::TypeId::TypeType) {
-          CARBON_DIAGNOSTIC(
-              DerefOfType, Note,
-              "To form a pointer type, write the `*` after the pointee type.");
-          builder.Note(TokenOnly(parse_node), DerefOfType);
-        }
-        builder.Emit();
-      }
-      context.AddInstAndPush(
-          parse_node, SemIR::Deref{parse_node, result_type_id, value_id});
-      return true;
+auto HandlePrefixOperatorStar(Context& context, Parse::NodeId parse_node)
+    -> bool {
+  auto value_id = context.node_stack().PopExpr();
+  value_id = ConvertToValueExpr(context, value_id);
+  auto type_id =
+      context.GetUnqualifiedType(context.insts().Get(value_id).type_id());
+  auto result_type_id = SemIR::TypeId::Error;
+  if (auto pointer_type =
+          context.types().TryGetAs<SemIR::PointerType>(type_id)) {
+    result_type_id = pointer_type->pointee_id;
+  } else if (type_id != SemIR::TypeId::Error) {
+    CARBON_DIAGNOSTIC(DerefOfNonPointer, Error,
+                      "Cannot dereference operand of non-pointer type `{0}`.",
+                      std::string);
+    auto builder =
+        context.emitter().Build(TokenOnly(parse_node), DerefOfNonPointer,
+                                context.sem_ir().StringifyType(type_id));
+    // TODO: Check for any facet here, rather than only a type.
+    if (type_id == SemIR::TypeId::TypeType) {
+      CARBON_DIAGNOSTIC(
+          DerefOfType, Note,
+          "To form a pointer type, write the `*` after the pointee type.");
+      builder.Note(TokenOnly(parse_node), DerefOfType);
     }
-
-    default:
-      return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
+    builder.Emit();
   }
+  context.AddInstAndPush(parse_node,
+                         SemIR::Deref{parse_node, result_type_id, value_id});
+  return true;
 }
 
 // Adds the branch for a short circuit operand.

+ 7 - 3
toolchain/check/node_stack.h

@@ -330,12 +330,10 @@ class NodeStack {
       case Parse::NodeKind::IfExprThen:
       case Parse::NodeKind::IfExprElse:
       case Parse::NodeKind::IndexExpr:
-      case Parse::NodeKind::InfixOperator:
       case Parse::NodeKind::MemberAccessExpr:
       case Parse::NodeKind::PackageExpr:
       case Parse::NodeKind::ParenExpr:
-      case Parse::NodeKind::PostfixOperator:
-      case Parse::NodeKind::PrefixOperator:
+      case Parse::NodeKind::PostfixOperatorStar:
       case Parse::NodeKind::ReturnType:
       case Parse::NodeKind::SelfTypeNameExpr:
       case Parse::NodeKind::SelfValueNameExpr:
@@ -386,6 +384,12 @@ class NodeStack {
         return IdKind::SoloParseNode;
 // Use x-macros to handle token cases.
 #define CARBON_PARSE_NODE_KIND(...)
+#define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name, ...) \
+  case Parse::NodeKind::InfixOperator##Name:             \
+    return IdKind::InstId;
+#define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name, ...) \
+  case Parse::NodeKind::PrefixOperator##Name:             \
+    return IdKind::InstId;
 #define CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(Name, ...) \
   case Parse::NodeKind::Name:                           \
     return IdKind::InstId;

+ 1 - 1
toolchain/check/testdata/index/fail_negative_indexing.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 
 var a: (i32, i32) = (12, 6);
-// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:16: ERROR: Semantics TODO: `Handle -`.
+// CHECK:STDERR: fail_negative_indexing.carbon:[[@LINE+3]]:16: ERROR: Semantics TODO: `HandlePrefixOperatorMinus`.
 // CHECK:STDERR: var b: i32 = a[-10];
 // CHECK:STDERR:                ^~~
 var b: i32 = a[-10];

+ 1 - 1
toolchain/check/testdata/operators/fail_type_mismatch_once.carbon

@@ -7,7 +7,7 @@
 fn Main() -> i32 {
   // The following line has two mismatches, but after the first, it shouldn't
   // keep erroring.
-  // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+3]]:10: ERROR: Semantics TODO: `Handle +`.
+  // CHECK:STDERR: fail_type_mismatch_once.carbon:[[@LINE+3]]:10: ERROR: Semantics TODO: `HandleInfixOperatorPlus`.
   // CHECK:STDERR:   return 12 + 3.4 + 12;
   // CHECK:STDERR:          ^~~~~~~~
   return 12 + 3.4 + 12;

+ 1 - 1
toolchain/check/testdata/operators/fail_unimplemented_op.carbon

@@ -5,7 +5,7 @@
 // AUTOUPDATE
 
 fn Main() -> i32 {
-  // CHECK:STDERR: fail_unimplemented_op.carbon:[[@LINE+3]]:10: ERROR: Semantics TODO: `Handle +`.
+  // CHECK:STDERR: fail_unimplemented_op.carbon:[[@LINE+3]]:10: ERROR: Semantics TODO: `HandleInfixOperatorPlus`.
   // CHECK:STDERR:   return 12 + 34;
   // CHECK:STDERR:          ^~~~~~~
   return 12 + 34;

+ 43 - 14
toolchain/parse/handle_expr.cpp

@@ -2,6 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "toolchain/lex/token_kind.h"
 #include "toolchain/parse/context.h"
 
 namespace Carbon::Parse {
@@ -48,7 +49,7 @@ auto HandleExpr(Context& context) -> void {
       context.PushState(State::IfExprFinish);
       context.PushState(State::IfExprFinishCondition);
     } else {
-      context.PushStateForExprLoop(State::ExprLoopForPrefix,
+      context.PushStateForExprLoop(State::ExprLoopForPrefixOperator,
                                    state.ambient_precedence,
                                    *operator_precedence);
     }
@@ -279,44 +280,72 @@ auto HandleExprLoop(Context& context) -> void {
         break;
 
       default:
-        state.state = State::ExprLoopForBinary;
+        state.state = State::ExprLoopForInfixOperator;
         break;
     }
 
     context.PushState(state);
     context.PushStateForExpr(operator_precedence);
   } else {
-    context.AddNode(NodeKind::PostfixOperator, state.token, state.subtree_start,
-                    state.has_error);
+    context.AddNode(NodeKind::PostfixOperatorStar, state.token,
+                    state.subtree_start, state.has_error);
     state.has_error = false;
     context.PushState(state);
   }
 }
 
 // Adds the operator node and returns the the main expression loop.
-static auto HandleExprLoopForOperator(Context& context, NodeKind node_kind)
-    -> void {
-  auto state = context.PopState();
-
+static auto HandleExprLoopForOperator(Context& context,
+                                      Context::StateStackEntry state,
+                                      NodeKind node_kind) -> void {
   context.AddNode(node_kind, state.token, state.subtree_start, state.has_error);
   state.has_error = false;
   context.PushState(state, State::ExprLoop);
 }
 
-auto HandleExprLoopForBinary(Context& context) -> void {
-  HandleExprLoopForOperator(context, NodeKind::InfixOperator);
+auto HandleExprLoopForInfixOperator(Context& context) -> void {
+  auto state = context.PopState();
+
+  switch (auto token_kind = context.tokens().GetKind(state.token)) {
+#define CARBON_PARSE_NODE_KIND(...)
+#define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name, ...)                      \
+  case Lex::TokenKind::Name:                                                  \
+    HandleExprLoopForOperator(context, state, NodeKind::InfixOperator##Name); \
+    break;
+#include "toolchain/parse/node_kind.def"
+
+    default:
+      CARBON_FATAL() << "Unexpected token kind for infix operator: "
+                     << token_kind;
+  }
 }
 
-auto HandleExprLoopForPrefix(Context& context) -> void {
-  HandleExprLoopForOperator(context, NodeKind::PrefixOperator);
+auto HandleExprLoopForPrefixOperator(Context& context) -> void {
+  auto state = context.PopState();
+
+  switch (auto token_kind = context.tokens().GetKind(state.token)) {
+#define CARBON_PARSE_NODE_KIND(...)
+#define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name, ...)                      \
+  case Lex::TokenKind::Name:                                                   \
+    HandleExprLoopForOperator(context, state, NodeKind::PrefixOperator##Name); \
+    break;
+#include "toolchain/parse/node_kind.def"
+
+    default:
+      CARBON_FATAL() << "Unexpected token kind for prefix operator: "
+                     << token_kind;
+  }
 }
 
 auto HandleExprLoopForShortCircuitOperatorAsAnd(Context& context) -> void {
-  HandleExprLoopForOperator(context, NodeKind::ShortCircuitOperatorAnd);
+  auto state = context.PopState();
+  HandleExprLoopForOperator(context, state, NodeKind::ShortCircuitOperatorAnd);
 }
 
 auto HandleExprLoopForShortCircuitOperatorAsOr(Context& context) -> void {
-  HandleExprLoopForOperator(context, NodeKind::ShortCircuitOperatorOr);
+  auto state = context.PopState();
+
+  HandleExprLoopForOperator(context, state, NodeKind::ShortCircuitOperatorOr);
 }
 
 auto HandleIfExprFinishCondition(Context& context) -> void {

+ 76 - 49
toolchain/parse/node_kind.def

@@ -17,6 +17,10 @@
 //   - CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, ChildCount, LexTokenKinds)
 //     Defines a parse node with a set number of children, often 0. This count
 //     must be correct even when the node contains errors.
+//   - CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name)
+//     Defines a parse node for an infix operator, with the Name as token.
+//   - CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name)
+//     Defines a parse node for a prefix operator, with the Name as token.
 //   - CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(Name, LexTokenKinds)
 //     Defines a parse node that corresponds to a token that is a single-token
 //     literal. The token is wrapped for LexTokenKinds.
@@ -53,6 +57,28 @@
   CARBON_PARSE_NODE_KIND(Name)
 #endif
 
+// This is expected to be used with something like:
+//
+//   // Use x-macros to handle modifier cases.
+//   #define CARBON_PARSE_NODE_KIND(...)
+//   #define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name, ...) <code>
+#ifndef CARBON_PARSE_NODE_KIND_INFIX_OPERATOR
+#define CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Name) \
+  CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator##Name, 2, \
+                                     CARBON_TOKEN(Name))
+#endif
+
+// This is expected to be used with something like:
+//
+//   // Use x-macros to handle modifier cases.
+//   #define CARBON_PARSE_NODE_KIND(...)
+//   #define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name, ...) <code>
+#ifndef CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR
+#define CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Name) \
+  CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator##Name, 1, \
+                                     CARBON_TOKEN(Name))
+#endif
+
 // This is expected to be used with something like:
 //
 //   // Use x-macros to handle literal cases.
@@ -511,70 +537,69 @@ CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(TypeTypeLiteral, CARBON_TOKEN(Type))
 
 // clang-format off
 
-// A prefix operator:
+// A prefix operator, such as `not`:
 //   _external_: expression
-// PrefixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator, 1,
-                                   CARBON_TOKEN(Star)
-                                   CARBON_TOKEN(Amp)
-                                   CARBON_TOKEN(Not)
-                                   CARBON_TOKEN(Minus)
-                                   CARBON_TOKEN(MinusMinus)
-                                   CARBON_TOKEN(PlusPlus)
-                                   CARBON_TOKEN(Caret)
-                                   CARBON_TOKEN(Const))
-
-// An infix operator:
+// PrefixOperator<name>
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Amp)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Caret)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Const)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Not)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Minus)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(MinusMinus)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(PlusPlus)
+CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR(Star)
+
+// An infix operator, such as `+`:
 //   _external_: lhs expression
 //   _external_: rhs expression
-// InfixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2,
-                                   CARBON_TOKEN(Amp)
-                                   CARBON_TOKEN(AmpEqual)
-                                   CARBON_TOKEN(As)
-                                   CARBON_TOKEN(Caret)
-                                   CARBON_TOKEN(CaretEqual)
-                                   CARBON_TOKEN(Equal)
-                                   CARBON_TOKEN(EqualEqual)
-                                   CARBON_TOKEN(ExclaimEqual)
-                                   CARBON_TOKEN(Greater)
-                                   CARBON_TOKEN(GreaterEqual)
-                                   CARBON_TOKEN(GreaterGreater)
-                                   CARBON_TOKEN(GreaterGreaterEqual)
-                                   CARBON_TOKEN(Less)
-                                   CARBON_TOKEN(LessEqual)
-                                   CARBON_TOKEN(LessEqualGreater)
-                                   CARBON_TOKEN(LessLess)
-                                   CARBON_TOKEN(LessLessEqual)
-                                   CARBON_TOKEN(Minus)
-                                   CARBON_TOKEN(MinusEqual)
-                                   CARBON_TOKEN(Percent)
-                                   CARBON_TOKEN(PercentEqual)
-                                   CARBON_TOKEN(Pipe)
-                                   CARBON_TOKEN(PipeEqual)
-                                   CARBON_TOKEN(Plus)
-                                   CARBON_TOKEN(PlusEqual)
-                                   CARBON_TOKEN(Slash)
-                                   CARBON_TOKEN(SlashEqual)
-                                   CARBON_TOKEN(Star)
-                                   CARBON_TOKEN(StarEqual))
+// InfixOperator<name>
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Amp)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(AmpEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(As)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Caret)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(CaretEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Equal)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(EqualEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(ExclaimEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Greater)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(GreaterEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(GreaterGreater)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(GreaterGreaterEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Less)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(LessEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(LessEqualGreater)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(LessLess)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(LessLessEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Minus)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(MinusEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Percent)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(PercentEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Pipe)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(PipeEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Plus)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(PlusEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Slash)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(SlashEqual)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(Star)
+CARBON_PARSE_NODE_KIND_INFIX_OPERATOR(StarEqual)
 
 // clang-format on
 
-// A short-circuiting infix operator:
+// A short-circuiting infix operator, such as `and`:
 //     _external_: expression
 //   ShortCircuitOperand(And|Or)
 //   _external_: expression
 // ShortCircuitOperand(And|Or)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperandAnd, 1, CARBON_TOKEN(And))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperandOr, 1, CARBON_TOKEN(Or))
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperatorAnd, 2, CARBON_TOKEN(And))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperatorAnd, 2,
+                                   CARBON_TOKEN(And))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperatorOr, 2, CARBON_TOKEN(Or))
 
-// A postfix operator:
+// A postfix operator, currently only `*`:
 //   _external_: expression
-// PostfixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1, CARBON_TOKEN(Star))
+// PostfixOperatorStar
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperatorStar, 1, CARBON_TOKEN(Star))
 
 // `if` expression + `then` + `else`:
 //     _external_: expression
@@ -747,6 +772,8 @@ CARBON_PARSE_NODE_KIND_BRACKET(NamedConstraintDecl, NamedConstraintIntroducer,
 #undef CARBON_PARSE_NODE_KIND
 #undef CARBON_PARSE_NODE_KIND_BRACKET
 #undef CARBON_PARSE_NODE_KIND_CHILD_COUNT
+#undef CARBON_PARSE_NODE_KIND_INFIX_OPERATOR
+#undef CARBON_PARSE_NODE_KIND_PREFIX_OPERATOR
 #undef CARBON_PARSE_NODE_KIND_TOKEN_LITERAL
 #undef CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER
 #undef CARBON_TOKEN

+ 2 - 2
toolchain/parse/state.def

@@ -501,7 +501,7 @@ CARBON_PARSE_STATE(ExprLoop)
 // expr <infix operator> expr ...
 //                           ^
 //   1. ExprLoop
-CARBON_PARSE_STATE(ExprLoopForBinary)
+CARBON_PARSE_STATE(ExprLoopForInfixOperator)
 
 // Completes an ExprLoop pass by adding a prefix operator, then goes back
 // to ExprLoop.
@@ -509,7 +509,7 @@ CARBON_PARSE_STATE(ExprLoopForBinary)
 // <prefix operator> expr ...
 //                       ^
 //   1. ExprLoop
-CARBON_PARSE_STATE(ExprLoopForPrefix)
+CARBON_PARSE_STATE(ExprLoopForPrefixOperator)
 
 // Completes an ExprLoop pass by adding a short circuit operator, then goes back
 // to ExprLoop.

+ 2 - 2
toolchain/parse/testdata/class/fn_definitions.carbon

@@ -57,7 +57,7 @@ class Foo {
 // CHECK:STDOUT:             {kind: 'ImplicitParamListStart', text: '['},
 // CHECK:STDOUT:                 {kind: 'SelfValueName', text: 'self'},
 // CHECK:STDOUT:                   {kind: 'SelfTypeNameExpr', text: 'Self'},
-// CHECK:STDOUT:                 {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:                 {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:             {kind: 'Address', text: 'addr', subtree_size: 5},
 // CHECK:STDOUT:           {kind: 'ImplicitParamList', text: ']', subtree_size: 7},
@@ -68,7 +68,7 @@ class Foo {
 // CHECK:STDOUT:               {kind: 'IdentifierName', text: 'x'},
 // CHECK:STDOUT:             {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '1'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '=', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'InfixOperatorEqual', text: '=', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'FunctionDefinition', text: '}', subtree_size: 19},
 // CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 57},

+ 1 - 1
toolchain/parse/testdata/function/declaration/addr.carbon

@@ -14,7 +14,7 @@ fn foo(addr a: i32*);
 // CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:             {kind: 'IdentifierName', text: 'a'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'Address', text: 'addr', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 7},

+ 1 - 1
toolchain/parse/testdata/function/definition/with_params.carbon

@@ -29,7 +29,7 @@ fn foo(bar: i64, baz: i64) {
 // CHECK:STDOUT:           {kind: 'CallExprComma', text: ','},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'bar'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'baz'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '+', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'CallExpr', text: ')', subtree_size: 8},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 22},

+ 1 - 1
toolchain/parse/testdata/generics/interface/default_fn.carbon

@@ -53,7 +53,7 @@ interface Foo {
 // CHECK:STDOUT:               {kind: 'MemberAccessExpr', text: '.', subtree_size: 3},
 // CHECK:STDOUT:             {kind: 'CallExprStart', text: '(', subtree_size: 4},
 // CHECK:STDOUT:               {kind: 'IntLiteral', text: '1'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'CallExpr', text: ')', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'ReturnStatement', text: ';', subtree_size: 9},
 // CHECK:STDOUT:       {kind: 'FunctionDefinition', text: '}', subtree_size: 23},

+ 2 - 2
toolchain/parse/testdata/generics/interface/self_pointer.carbon

@@ -20,7 +20,7 @@ interface Foo {
 // CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
 // CHECK:STDOUT:               {kind: 'SelfValueName', text: 'self'},
 // CHECK:STDOUT:                 {kind: 'SelfTypeNameExpr', text: 'Self'},
-// CHECK:STDOUT:               {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'Address', text: 'addr', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 7},
@@ -37,7 +37,7 @@ interface Foo {
 // CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
 // CHECK:STDOUT:               {kind: 'SelfValueName', text: 'self'},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'foo'},
-// CHECK:STDOUT:               {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'Address', text: 'addr', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 7},

+ 2 - 2
toolchain/parse/testdata/if_expr/in_type.carbon

@@ -27,7 +27,7 @@ fn F() -> if true then i32 else i32* {
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
 // CHECK:STDOUT:             {kind: 'IfExprThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IfExprElse', text: 'else', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 8},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 13},
@@ -37,7 +37,7 @@ fn F() -> if true then i32 else i32* {
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:           {kind: 'IfExprThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorAmp', text: '&', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'IfExprElse', text: 'else', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 23},

+ 13 - 13
toolchain/parse/testdata/operators/assign.carbon

@@ -46,53 +46,53 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 7},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '*=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorStarEqual', text: '*=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '/=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorSlashEqual', text: '/=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '+=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlusEqual', text: '+=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '-=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorMinusEqual', text: '-=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '%=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPercentEqual', text: '%=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '&=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAmpEqual', text: '&=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '|=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPipeEqual', text: '|=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '^=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorCaretEqual', text: '^=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '<<=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorLessLessEqual', text: '<<=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '>>=', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorGreaterGreaterEqual', text: '>>=', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '++', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorPlusPlus', text: '++', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '--', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorMinusMinus', text: '--', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 70},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 2 - 2
toolchain/parse/testdata/operators/fail_chained_assign.carbon

@@ -35,9 +35,9 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 5},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '=', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperatorEqual', text: '=', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'IntLiteral', text: '1'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 22},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parse/testdata/operators/fail_infix_uneven_space_after.carbon

@@ -20,7 +20,7 @@ var n: i8 = n* n;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', has_error: yes, subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 3 - 3
toolchain/parse/testdata/operators/fail_invalid_infix.carbon

@@ -30,7 +30,7 @@ var c: i32 = == ;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '==', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorEqualEqual', text: '==', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:         {kind: 'IdentifierName', text: 'b'},
@@ -39,7 +39,7 @@ var c: i32 = == ;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'InvalidParse', text: '==', has_error: yes},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '==', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorEqualEqual', text: '==', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:         {kind: 'IdentifierName', text: 'c'},
@@ -48,7 +48,7 @@ var c: i32 = == ;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'InvalidParse', text: '==', has_error: yes},
 // CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '==', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorEqualEqual', text: '==', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 6 - 6
toolchain/parse/testdata/operators/fail_precedence_as.carbon

@@ -34,21 +34,21 @@ fn F(n: i32) {
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8},
 // CHECK:STDOUT:             {kind: 'BoolLiteralTrue', text: 'true'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'BoolTypeLiteral', text: 'bool'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 5},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '1'},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '1'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '+', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '5'},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '2'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '%', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperatorPercent', text: '%', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 26},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 4 - 4
toolchain/parse/testdata/operators/fail_precedence_assign.carbon

@@ -46,7 +46,7 @@ fn F() {
 // CHECK:STDOUT:             {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '+', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlus', text: '+', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:           {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:               {kind: 'BoolLiteralTrue', text: 'true'},
@@ -59,8 +59,8 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 9},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'a'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '++', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '+', subtree_size: 4},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorPlusPlus', text: '++', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlus', text: '+', subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 5},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'c'},
 // CHECK:STDOUT:             {kind: 'IfExprIf', text: 'if', subtree_size: 2},
@@ -68,7 +68,7 @@ fn F() {
 // CHECK:STDOUT:             {kind: 'IfExprThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'b'},
 // CHECK:STDOUT:           {kind: 'IfExprElse', text: 'else', subtree_size: 6},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '++', subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorPlusPlus', text: '++', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 39},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 2 - 2
toolchain/parse/testdata/operators/fail_precedence_star_minus.carbon

@@ -18,9 +18,9 @@ var n: i8 = n* -n;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '-', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'InfixOperatorMinus', text: '-', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 2 - 2
toolchain/parse/testdata/operators/fail_precedence_star_star.carbon

@@ -18,9 +18,9 @@ var n: i8 = n* *p;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 2 - 2
toolchain/parse/testdata/operators/fail_star_star_no_space.carbon

@@ -22,8 +22,8 @@ var n: i8 = n**p;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', has_error: yes, subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 10 - 10
toolchain/parse/testdata/operators/fail_variety.carbon

@@ -34,23 +34,23 @@ fn F() {
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:               {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:               {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'c'},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'd'},
-// CHECK:STDOUT:               {kind: 'InfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:             {kind: 'InfixOperator', text: '+', subtree_size: 7},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '=', subtree_size: 9},
+// CHECK:STDOUT:               {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'InfixOperatorPlus', text: '+', subtree_size: 7},
+// CHECK:STDOUT:           {kind: 'InfixOperatorEqual', text: '=', subtree_size: 9},
 // CHECK:STDOUT:                   {kind: 'IdentifierNameExpr', text: 'd'},
 // CHECK:STDOUT:                   {kind: 'IdentifierNameExpr', text: 'd'},
-// CHECK:STDOUT:                 {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'e'},
-// CHECK:STDOUT:               {kind: 'InfixOperator', text: '<<', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'InfixOperatorLessLess', text: '<<', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'f'},
-// CHECK:STDOUT:             {kind: 'InfixOperator', text: '&', has_error: yes, subtree_size: 7},
+// CHECK:STDOUT:             {kind: 'InfixOperatorAmp', text: '&', has_error: yes, subtree_size: 7},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', 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: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'InfixOperatorMinus', text: '-', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', has_error: yes, subtree_size: 20},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 21},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 27},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 2 - 2
toolchain/parse/testdata/operators/fixity_in_call.carbon

@@ -19,11 +19,11 @@ fn F() {
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'G'},
 // CHECK:STDOUT:           {kind: 'CallExprStart', text: '(', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'CallExprComma', text: ','},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'CallExpr', text: ')', subtree_size: 9},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 16},

+ 1 - 1
toolchain/parse/testdata/operators/fixity_in_params.carbon

@@ -15,7 +15,7 @@ fn F(p: i32*, n: i32) {
 // CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:             {kind: 'IdentifierName', text: 'p'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'PatternListComma', text: ','},
 // CHECK:STDOUT:             {kind: 'IdentifierName', text: 'n'},

+ 2 - 2
toolchain/parse/testdata/operators/fixity_in_var.carbon

@@ -20,7 +20,7 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:           {kind: 'IdentifierName', text: 'q'},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'p'},
@@ -31,7 +31,7 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 22},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 10 - 10
toolchain/parse/testdata/operators/fixity_with_assign.carbon

@@ -21,28 +21,28 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 't'},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 't'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 5},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 4},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 6},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'InfixOperatorStar', text: '*', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 7},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'InfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 31},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parse/testdata/operators/infix.carbon

@@ -16,7 +16,7 @@ var n: i8 = n * n;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/infix_no_space.carbon

@@ -16,7 +16,7 @@ var n: i8 = n*n;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/infix_with_paren_after.carbon

@@ -18,7 +18,7 @@ var n: i8 = 3*(n);
 // CHECK:STDOUT:           {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'ParenExpr', text: ')', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 11},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/infix_with_paren_before.carbon

@@ -18,7 +18,7 @@ var n: i8 = (n)*3;
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'ParenExpr', text: ')', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'IntLiteral', text: '3'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 11},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/postfix.carbon

@@ -15,7 +15,7 @@ var v: type = i8*;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 4 - 4
toolchain/parse/testdata/operators/postfix_repeat.carbon

@@ -17,10 +17,10 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:                 {kind: 'IntTypeLiteral', 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: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parse/testdata/operators/postfix_space_after_op.carbon

@@ -15,7 +15,7 @@ var v: type = i8* ;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 11 - 11
toolchain/parse/testdata/operators/precedence_as.carbon

@@ -26,34 +26,34 @@ fn F(n: i32) {
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 8},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'const', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorAmp', text: '&', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: 'as', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'InfixOperatorAs', text: 'as', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:           {kind: 'IfConditionStart', text: '('},
 // CHECK:STDOUT:                       {kind: 'IntLiteral', text: '1'},
 // CHECK:STDOUT:                       {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:                     {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:                     {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
 // CHECK:STDOUT:                       {kind: 'IntLiteral', text: '2'},
 // CHECK:STDOUT:                       {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:                     {kind: 'InfixOperator', text: 'as', subtree_size: 3},
-// CHECK:STDOUT:                   {kind: 'InfixOperator', text: '<', subtree_size: 7},
+// CHECK:STDOUT:                     {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:                   {kind: 'InfixOperatorLess', text: '<', subtree_size: 7},
 // CHECK:STDOUT:                 {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 8},
 // CHECK:STDOUT:                   {kind: 'BoolLiteralTrue', text: 'true'},
 // CHECK:STDOUT:                   {kind: 'BoolTypeLiteral', text: 'bool'},
-// CHECK:STDOUT:                 {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
 // CHECK:STDOUT:               {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 12},
 // CHECK:STDOUT:             {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 13},
 // CHECK:STDOUT:               {kind: 'BoolLiteralFalse', text: 'false'},
 // CHECK:STDOUT:               {kind: 'BoolTypeLiteral', text: 'bool'},
-// CHECK:STDOUT:             {kind: 'InfixOperator', text: 'as', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'InfixOperatorAs', text: 'as', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 17},
 // CHECK:STDOUT:         {kind: 'IfCondition', text: ')', subtree_size: 19},
 // CHECK:STDOUT:           {kind: 'CodeBlockStart', text: '{'},

+ 4 - 4
toolchain/parse/testdata/operators/precedence_assign.carbon

@@ -37,18 +37,18 @@ fn F(c: bool) {
 // CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:           {kind: 'IdentifierName', text: 'p'},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 6},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'c'},
 // CHECK:STDOUT:             {kind: 'IfExprIf', text: 'if', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'IntLiteral', text: '1'},
 // CHECK:STDOUT:             {kind: 'IfExprThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '2'},
 // CHECK:STDOUT:           {kind: 'IfExprElse', text: 'else', subtree_size: 6},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '=', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'InfixOperatorEqual', text: '=', subtree_size: 9},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 10},
 // CHECK:STDOUT:             {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'c'},
@@ -64,7 +64,7 @@ fn F(c: bool) {
 // CHECK:STDOUT:             {kind: 'IfExprThen', text: 'then', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntLiteral', text: '2'},
 // CHECK:STDOUT:           {kind: 'IfExprElse', text: 'else', subtree_size: 6},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '+=', subtree_size: 15},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlusEqual', text: '+=', subtree_size: 15},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 16},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 51},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 3 - 3
toolchain/parse/testdata/operators/precedence_not.carbon

@@ -17,14 +17,14 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:                   {kind: 'IdentifierNameExpr', text: 'a'},
-// CHECK:STDOUT:                 {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:                 {kind: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 3},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:               {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 6},
 // CHECK:STDOUT:           {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 7},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'c'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 10},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 11},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 17},

+ 16 - 16
toolchain/parse/testdata/operators/precedence_unary.carbon

@@ -21,33 +21,33 @@ fn F(p: i32*) {
 // CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:             {kind: 'IdentifierName', text: 'p'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 9},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 3},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', 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: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorCaret', text: '^', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlus', text: '+', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 3},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', 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: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorCaret', text: '^', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPipe', text: '|', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '-', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 3},
 // CHECK:STDOUT:               {kind: 'IdentifierNameExpr', 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: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorCaret', text: '^', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorLessLess', text: '<<', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 34},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 2 - 2
toolchain/parse/testdata/operators/prefix.carbon

@@ -16,7 +16,7 @@ var b: bool = not true;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:         {kind: 'IdentifierName', text: 'b'},
@@ -24,7 +24,7 @@ var b: bool = not true;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'BoolLiteralTrue', text: 'true'},
-// CHECK:STDOUT:       {kind: 'PrefixOperator', text: 'not', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorNot', text: 'not', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/prefix_no_space.carbon

@@ -16,7 +16,7 @@ var n: i8 =-n;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 4 - 4
toolchain/parse/testdata/operators/prefix_repeat.carbon

@@ -17,10 +17,10 @@ fn F() {
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:                 {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:               {kind: 'PrefixOperator', text: '&', subtree_size: 2},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '*', subtree_size: 4},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorAmp', text: '&', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorStar', text: '*', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorStar', text: '*', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parse/testdata/operators/recover_infix_uneven_space_before.carbon

@@ -19,7 +19,7 @@ var n: i8 = n *n;
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'InfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/recover_postfix_space.carbon

@@ -18,7 +18,7 @@ var v: type = i8 *;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/recover_postfix_space_before_comma.carbon

@@ -20,7 +20,7 @@ var n: i8 = F(i8 *, 0);
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'F'},
 // CHECK:STDOUT:         {kind: 'CallExprStart', text: '(', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'CallExprComma', text: ','},
 // CHECK:STDOUT:         {kind: 'IntLiteral', text: '0'},
 // CHECK:STDOUT:       {kind: 'CallExpr', text: ')', subtree_size: 7},

+ 1 - 1
toolchain/parse/testdata/operators/recover_postfix_space_in_call.carbon

@@ -20,7 +20,7 @@ var n: i8 = F(i8 *);
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'F'},
 // CHECK:STDOUT:         {kind: 'CallExprStart', text: '(', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'CallExpr', text: ')', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 11},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parse/testdata/operators/recover_postfix_space_surrounding.carbon

@@ -18,7 +18,7 @@ var v: type = i8 * ;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IntTypeLiteral', text: 'i8'},
-// CHECK:STDOUT:       {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 3 - 3
toolchain/parse/testdata/operators/recover_prefix_repeat.carbon

@@ -18,9 +18,9 @@ fn F() -> const const i32* {
 // CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:                 {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:               {kind: 'PrefixOperator', text: 'const', subtree_size: 2},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: 'const', subtree_size: 3},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 4},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 11},

+ 1 - 1
toolchain/parse/testdata/operators/recover_prefix_space.carbon

@@ -18,7 +18,7 @@ var n: i8 = - n;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/recover_prefix_uneven_space_with_assign.carbon

@@ -18,7 +18,7 @@ var n: i8 =- n;
 // CHECK:STDOUT:       {kind: 'BindingPattern', text: ':', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:       {kind: 'PrefixOperator', text: '-', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PrefixOperatorMinus', text: '-', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/operators/spaceship.carbon

@@ -18,7 +18,7 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'a'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'b'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '<=>', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'InfixOperatorLessEqualGreater', text: '<=>', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExprStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 10},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 6 - 6
toolchain/parse/testdata/pointer/const_pointer.carbon

@@ -16,8 +16,8 @@ fn C() -> const (i32*) { return C(); }
 // CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PrefixOperator', text: 'const', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 9},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
@@ -32,9 +32,9 @@ fn C() -> const (i32*) { return C(); }
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:                 {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:               {kind: 'PrefixOperator', text: 'const', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'ParenExpr', text: ')', subtree_size: 4},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 11},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
@@ -49,9 +49,9 @@ fn C() -> const (i32*) { return C(); }
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
 // CHECK:STDOUT:               {kind: 'ExprOpenParen', text: '('},
 // CHECK:STDOUT:                 {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:               {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'ParenExpr', text: ')', subtree_size: 4},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: 'const', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorConst', text: 'const', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 11},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},

+ 7 - 7
toolchain/parse/testdata/pointer/fail_pointer_type_in_expr.carbon

@@ -45,10 +45,10 @@ fn H() -> i32 {
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
 // CHECK:STDOUT:               {kind: 'IntLiteral', text: '3'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'InfixOperator', text: '*', subtree_size: 3},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:             {kind: 'InfixOperatorStar', text: '*', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:           {kind: 'IntLiteral', text: '4'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '*', has_error: yes, subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'InfixOperatorStar', text: '*', has_error: yes, subtree_size: 6},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 16},
 // CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
@@ -60,9 +60,9 @@ fn H() -> i32 {
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IntLiteral', text: '4'},
-// CHECK:STDOUT:         {kind: 'InfixOperator', text: '+', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'InfixOperatorPlus', text: '+', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 14},
 // CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
@@ -74,8 +74,8 @@ fn H() -> i32 {
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 13},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 4 - 4
toolchain/parse/testdata/pointer/pointer_type.carbon

@@ -18,11 +18,11 @@ var T: type = if true then i32* else f64*;
 // CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
 // CHECK:STDOUT:             {kind: 'IdentifierName', text: 'p'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 6},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 12},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
@@ -37,10 +37,10 @@ var T: type = if true then i32* else f64*;
 // CHECK:STDOUT:           {kind: 'BoolLiteralTrue', text: 'true'},
 // CHECK:STDOUT:         {kind: 'IfExprIf', text: 'if', subtree_size: 2},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'IfExprThen', text: 'then', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'FloatTypeLiteral', text: 'f64'},
-// CHECK:STDOUT:         {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'IfExprElse', text: 'else', subtree_size: 8},
 // CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 14},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 7 - 7
toolchain/parse/testdata/pointer/pointer_value.carbon

@@ -31,26 +31,26 @@ fn F() -> i32 {
 // CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:           {kind: 'IdentifierName', text: 'p'},
 // CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'BindingPattern', text: ':', subtree_size: 4},
 // CHECK:STDOUT:         {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'n'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorAmp', text: '&', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 9},
 // CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
 // CHECK:STDOUT:           {kind: 'IdentifierName', text: 'q'},
 // CHECK:STDOUT:               {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:             {kind: 'PostfixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:           {kind: 'PostfixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'PostfixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'PostfixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:         {kind: 'BindingPattern', text: ':', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'VariableInitializer', text: '='},
 // CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'p'},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '&', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorAmp', text: '&', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 10},
 // CHECK:STDOUT:         {kind: 'ReturnStatementStart', text: 'return'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'q'},
-// CHECK:STDOUT:           {kind: 'PrefixOperator', text: '*', subtree_size: 2},
-// CHECK:STDOUT:         {kind: 'PrefixOperator', text: '*', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'PrefixOperatorStar', text: '*', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'PrefixOperatorStar', text: '*', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ReturnStatement', text: ';', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 39},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},