Просмотр исходного кода

Add support for compound assignment and increment (#2526)

Add support for user-defined assignment, as well as compound assignment and increment, following the design direction in pending proposal #2511.

Some of this isn't fully testable yet: because explorer doesn't properly support `impl` specialization, the blanket `impl`s in the prelude prevent types from customizing assignment.
Richard Smith 3 лет назад
Родитель
Сommit
2fd7e2b65d
32 измененных файлов с 975 добавлено и 215 удалено
  1. 20 0
      common/fuzzing/carbon.proto
  2. 43 1
      common/fuzzing/proto_to_carbon.cpp
  3. 1 0
      explorer/ast/ast_rtti.txt
  4. 34 1
      explorer/ast/statement.cpp
  5. 76 2
      explorer/ast/statement.h
  6. 162 0
      explorer/data/prelude.carbon
  7. 37 0
      explorer/fuzzing/ast_to_proto.cpp
  8. 67 7
      explorer/interpreter/builtins.h
  9. 16 0
      explorer/interpreter/interpreter.cpp
  10. 1 0
      explorer/interpreter/resolve_control_flow.cpp
  11. 5 0
      explorer/interpreter/resolve_names.cpp
  12. 10 1
      explorer/interpreter/resolve_unformed.cpp
  13. 62 4
      explorer/interpreter/type_checker.cpp
  14. 193 169
      explorer/syntax/lexer.lpp
  15. 48 2
      explorer/syntax/parser.ypp
  16. 1 1
      explorer/testdata/basic_syntax/fail_missing_var.carbon
  17. 1 1
      explorer/testdata/basic_syntax/fail_var_named_self.carbon
  18. 1 1
      explorer/testdata/let/fail_local_named_self.carbon
  19. 10 2
      explorer/testdata/operators/add.carbon
  20. 27 0
      explorer/testdata/operators/assign_builtin.carbon
  21. 11 4
      explorer/testdata/operators/bit_and.carbon
  22. 11 4
      explorer/testdata/operators/bit_or.carbon
  23. 10 3
      explorer/testdata/operators/bit_xor.carbon
  24. 10 3
      explorer/testdata/operators/div.carbon
  25. 48 0
      explorer/testdata/operators/inc_dec.carbon
  26. 11 4
      explorer/testdata/operators/mod.carbon
  27. 9 2
      explorer/testdata/operators/mul.carbon
  28. 8 0
      explorer/testdata/operators/shift.carbon
  29. 9 2
      explorer/testdata/operators/sub.carbon
  30. 1 1
      explorer/testdata/struct/fail_assign_different_types.carbon
  31. 16 0
      explorer/testdata/unformed/static/fail_compound_assign.carbon
  32. 16 0
      explorer/testdata/unformed/static/fail_increment.carbon

+ 20 - 0
common/fuzzing/carbon.proto

@@ -241,8 +241,27 @@ message ExpressionStatement {
 }
 }
 
 
 message AssignStatement {
 message AssignStatement {
+  enum Operator {
+    Plain = 0;
+    Add = 1;
+    And = 2;
+    Div = 3;
+    Mul = 4;
+    Or = 5;
+    Sub = 6;
+    Mod = 7;
+    Xor = 8;
+    ShiftLeft = 9;
+    ShiftRight = 10;
+  }
   optional Expression lhs = 1;
   optional Expression lhs = 1;
   optional Expression rhs = 2;
   optional Expression rhs = 2;
+  optional Operator op = 3;
+}
+
+message IncrementDecrementStatement {
+  optional Expression operand = 1;
+  optional bool is_increment = 2;
 }
 }
 
 
 message VariableDefinitionStatement {
 message VariableDefinitionStatement {
@@ -321,6 +340,7 @@ message Statement {
     BreakStatement break_statement = 13;
     BreakStatement break_statement = 13;
     ContinueStatement continue_statement = 14;
     ContinueStatement continue_statement = 14;
     ForStatement for_statement = 15;
     ForStatement for_statement = 15;
+    IncrementDecrementStatement inc_dec = 16;
   }
   }
 }
 }
 
 

+ 43 - 1
common/fuzzing/proto_to_carbon.cpp

@@ -534,12 +534,54 @@ static auto StatementToCarbon(const Fuzzing::Statement& statement,
     case Fuzzing::Statement::kAssign: {
     case Fuzzing::Statement::kAssign: {
       const auto& assign_statement = statement.assign();
       const auto& assign_statement = statement.assign();
       ExpressionToCarbon(assign_statement.lhs(), out);
       ExpressionToCarbon(assign_statement.lhs(), out);
-      out << " = ";
+      switch (assign_statement.op()) {
+        case Fuzzing::AssignStatement::Plain:
+          out << " = ";
+          break;
+        case Fuzzing::AssignStatement::Add:
+          out << " += ";
+          break;
+        case Fuzzing::AssignStatement::And:
+          out << " &= ";
+          break;
+        case Fuzzing::AssignStatement::Div:
+          out << " /= ";
+          break;
+        case Fuzzing::AssignStatement::Mod:
+          out << " %= ";
+          break;
+        case Fuzzing::AssignStatement::Mul:
+          out << " *= ";
+          break;
+        case Fuzzing::AssignStatement::Or:
+          out << " |= ";
+          break;
+        case Fuzzing::AssignStatement::ShiftLeft:
+          out << " <<= ";
+          break;
+        case Fuzzing::AssignStatement::ShiftRight:
+          out << " >>= ";
+          break;
+        case Fuzzing::AssignStatement::Sub:
+          out << " -= ";
+          break;
+        case Fuzzing::AssignStatement::Xor:
+          out << " ^= ";
+          break;
+      }
       ExpressionToCarbon(assign_statement.rhs(), out);
       ExpressionToCarbon(assign_statement.rhs(), out);
       out << ";";
       out << ";";
       break;
       break;
     }
     }
 
 
+    case Fuzzing::Statement::kIncDec: {
+      const auto& inc_dec_statement = statement.inc_dec();
+      out << (inc_dec_statement.is_increment() ? "++" : "--");
+      ExpressionToCarbon(inc_dec_statement.operand(), out);
+      out << ";";
+      break;
+    }
+
     case Fuzzing::Statement::kVariableDefinition: {
     case Fuzzing::Statement::kVariableDefinition: {
       const auto& def = statement.variable_definition();
       const auto& def = statement.variable_definition();
       if (def.is_returned()) {
       if (def.is_returned()) {

+ 1 - 0
explorer/ast/ast_rtti.txt

@@ -36,6 +36,7 @@ class AlternativeSignature : AstNode;
 abstract class Statement : AstNode;
 abstract class Statement : AstNode;
   class ExpressionStatement : Statement;
   class ExpressionStatement : Statement;
   class Assign : Statement;
   class Assign : Statement;
+  class IncrementDecrement : Statement;
   class VariableDefinition : Statement;
   class VariableDefinition : Statement;
   class If : Statement;
   class If : Statement;
   abstract class Return : Statement;
   abstract class Return : Statement;

+ 34 - 1
explorer/ast/statement.cpp

@@ -72,7 +72,13 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const {
       break;
       break;
     case StatementKind::Assign: {
     case StatementKind::Assign: {
       const auto& assign = cast<Assign>(*this);
       const auto& assign = cast<Assign>(*this);
-      out << assign.lhs() << " = " << assign.rhs() << ";";
+      out << assign.lhs() << " " << AssignOperatorToString(assign.op()) << " "
+          << assign.rhs() << ";";
+      break;
+    }
+    case StatementKind::IncrementDecrement: {
+      const auto& inc_dec = cast<IncrementDecrement>(*this);
+      out << (inc_dec.is_increment() ? "++" : "--") << inc_dec.argument();
       break;
       break;
     }
     }
     case StatementKind::If: {
     case StatementKind::If: {
@@ -137,4 +143,31 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const {
   }
   }
 }
 }
 
 
+auto AssignOperatorToString(AssignOperator op) -> std::string_view {
+  switch (op) {
+    case AssignOperator::Plain:
+      return "=";
+    case AssignOperator::Add:
+      return "+=";
+    case AssignOperator::Div:
+      return "/=";
+    case AssignOperator::Mul:
+      return "*=";
+    case AssignOperator::Mod:
+      return "%=";
+    case AssignOperator::Sub:
+      return "-=";
+    case AssignOperator::And:
+      return "&=";
+    case AssignOperator::Or:
+      return "|=";
+    case AssignOperator::Xor:
+      return "^=";
+    case AssignOperator::ShiftLeft:
+      return "<<=";
+    case AssignOperator::ShiftRight:
+      return ">>=";
+  }
+}
+
 }  // namespace Carbon
 }  // namespace Carbon

+ 76 - 2
explorer/ast/statement.h

@@ -86,11 +86,31 @@ class ExpressionStatement : public Statement {
   Nonnull<Expression*> expression_;
   Nonnull<Expression*> expression_;
 };
 };
 
 
+enum class AssignOperator {
+  Plain,
+  Add,
+  Div,
+  Mul,
+  Mod,
+  Sub,
+  And,
+  Or,
+  Xor,
+  ShiftLeft,
+  ShiftRight,
+};
+
+// Returns the spelling of this assignment operator token.
+auto AssignOperatorToString(AssignOperator op) -> std::string_view;
+
 class Assign : public Statement {
 class Assign : public Statement {
  public:
  public:
-  Assign(SourceLocation source_loc, Nonnull<Expression*> lhs,
+  Assign(SourceLocation source_loc, Nonnull<Expression*> lhs, AssignOperator op,
          Nonnull<Expression*> rhs)
          Nonnull<Expression*> rhs)
-      : Statement(AstNodeKind::Assign, source_loc), lhs_(lhs), rhs_(rhs) {}
+      : Statement(AstNodeKind::Assign, source_loc),
+        lhs_(lhs),
+        rhs_(rhs),
+        op_(op) {}
 
 
   static auto classof(const AstNode* node) -> bool {
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromAssign(node->kind());
     return InheritsFromAssign(node->kind());
@@ -101,12 +121,66 @@ class Assign : public Statement {
   auto rhs() const -> const Expression& { return *rhs_; }
   auto rhs() const -> const Expression& { return *rhs_; }
   auto rhs() -> Expression& { return *rhs_; }
   auto rhs() -> Expression& { return *rhs_; }
 
 
+  auto op() const -> AssignOperator { return op_; }
+
   // Can only be called by type-checking, if a conversion was required.
   // Can only be called by type-checking, if a conversion was required.
   void set_rhs(Nonnull<Expression*> rhs) { rhs_ = rhs; }
   void set_rhs(Nonnull<Expression*> rhs) { rhs_ = rhs; }
 
 
+  // Set the rewritten form of this statement. Can only be called during type
+  // checking.
+  auto set_rewritten_form(Nonnull<const Expression*> rewritten_form) -> void {
+    CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice";
+    rewritten_form_ = rewritten_form;
+  }
+
+  // Get the rewritten form of this statement. A rewritten form is used when
+  // the statement is rewritten as a function call on an interface. A
+  // rewritten form is not used when providing built-in operator semantics for
+  // a plain assignment.
+  auto rewritten_form() const -> std::optional<Nonnull<const Expression*>> {
+    return rewritten_form_;
+  }
+
  private:
  private:
   Nonnull<Expression*> lhs_;
   Nonnull<Expression*> lhs_;
   Nonnull<Expression*> rhs_;
   Nonnull<Expression*> rhs_;
+  AssignOperator op_;
+  std::optional<Nonnull<const Expression*>> rewritten_form_;
+};
+
+class IncrementDecrement : public Statement {
+ public:
+  IncrementDecrement(SourceLocation source_loc, Nonnull<Expression*> argument,
+                     bool is_increment)
+      : Statement(AstNodeKind::IncrementDecrement, source_loc),
+        argument_(argument),
+        is_increment_(is_increment) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromIncrementDecrement(node->kind());
+  }
+
+  auto argument() const -> const Expression& { return *argument_; }
+  auto argument() -> Expression& { return *argument_; }
+
+  bool is_increment() const { return is_increment_; }
+
+  // Set the rewritten form of this statement. Can only be called during type
+  // checking.
+  auto set_rewritten_form(Nonnull<const Expression*> rewritten_form) -> void {
+    CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice";
+    rewritten_form_ = rewritten_form;
+  }
+
+  // Get the rewritten form of this statement.
+  auto rewritten_form() const -> std::optional<Nonnull<const Expression*>> {
+    return rewritten_form_;
+  }
+
+ private:
+  Nonnull<Expression*> argument_;
+  bool is_increment_;
+  std::optional<Nonnull<const Expression*>> rewritten_form_;
 };
 };
 
 
 class VariableDefinition : public Statement {
 class VariableDefinition : public Statement {

+ 162 - 0
explorer/data/prelude.carbon

@@ -501,6 +501,168 @@ external impl i32 as RightShiftWith(i32) where .Result = i32 {
   }
   }
 }
 }
 
 
+// -----------------------------------
+// Assignment and compound assignment.
+// -----------------------------------
+
+interface AssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint Assign { extends AssignWith(Self); }
+
+interface AddAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint AddAssign { extends AddAssignWith(Self); }
+
+interface SubAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint SubAssign { extends SubAssignWith(Self); }
+
+interface MulAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint MulAssign { extends MulAssignWith(Self); }
+
+interface DivAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint DivAssign { extends DivAssignWith(Self); }
+
+interface ModAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint ModAssign { extends ModAssignWith(Self); }
+
+interface BitAndAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint BitAssignAnd { extends BitAndAssignWith(Self); }
+
+interface BitOrAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint BitAssignOr { extends BitOrAssignWith(Self); }
+
+interface BitXorAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint BitAssignXor { extends BitXorAssignWith(Self); }
+
+interface LeftShiftAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint LeftShiftAssign { extends LeftShiftAssignWith(Self); }
+
+interface RightShiftAssignWith(U:! type) {
+  fn Op[addr self: Self*](other: U);
+}
+constraint RightShiftAssign { extends RightShiftAssignWith(Self); }
+
+// TODO: This is temporary, and should eventually be replaced by
+// something more fine-grained. Not all class types should be
+// assignable.
+impl forall [T:! type, U:! ImplicitAs(T)]
+    T as AssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = other.Convert();
+  }
+}
+
+// TODO: Should `AddWith(U) & AssignWith(.Self.(AddWith(U).Result))` work?
+impl forall [U:! type, T:! AddWith(U) where .Self is AssignWith(.Self.Result)]
+     T as AddAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self + other;
+  }
+}
+
+impl forall [U:! type, T:! SubWith(U) where .Self is AssignWith(.Self.Result)]
+     T as SubAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self - other;
+  }
+}
+
+impl forall [U:! type, T:! MulWith(U) where .Self is AssignWith(.Self.Result)]
+     T as MulAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self * other;
+  }
+}
+
+impl forall [U:! type, T:! DivWith(U) where .Self is AssignWith(.Self.Result)]
+     T as DivAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self / other;
+  }
+}
+
+impl forall [U:! type, T:! ModWith(U) where .Self is AssignWith(.Self.Result)]
+     T as ModAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self % other;
+  }
+}
+
+impl forall [U:! type, T:! BitAndWith(U) where .Self is AssignWith(.Self.Result)]
+     T as BitAndAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self & other;
+  }
+}
+
+impl forall [U:! type, T:! BitOrWith(U) where .Self is AssignWith(.Self.Result)]
+     T as BitOrAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self | other;
+  }
+}
+
+impl forall [U:! type, T:! BitXorWith(U) where .Self is AssignWith(.Self.Result)]
+     T as BitXorAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self ^ other;
+  }
+}
+
+impl forall [U:! type, T:! LeftShiftWith(U) where .Self is AssignWith(.Self.Result)]
+     T as LeftShiftAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self << other;
+  }
+}
+
+impl forall [U:! type, T:! RightShiftWith(U) where .Self is AssignWith(.Self.Result)]
+     T as RightShiftAssignWith(U) {
+  fn Op[addr self: Self*](other: U) {
+    *self = *self >> other;
+  }
+}
+
+// ------------------------
+// Increment and decrement.
+// ------------------------
+
+interface Inc {
+  fn Op[addr self: Self*]();
+}
+interface Dec {
+  fn Op[addr self: Self*]();
+}
+
+impl i32 as Inc {
+  fn Op[addr self: Self*]() {
+    *self = *self + 1;
+  }
+}
+impl i32 as Dec {
+  fn Op[addr self: Self*]() {
+    *self = *self - 1;
+  }
+}
+
 //-------------------------
 //-------------------------
 // Optional
 // Optional
 //-------------------------
 //-------------------------

+ 37 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -89,6 +89,34 @@ static auto OperatorToProtoEnum(const Operator op)
   }
   }
 }
 }
 
 
+static auto AssignOperatorToProtoEnum(const AssignOperator op)
+    -> Fuzzing::AssignStatement::Operator {
+  switch (op) {
+    case AssignOperator::Plain:
+      return Fuzzing::AssignStatement::Plain;
+    case AssignOperator::Add:
+      return Fuzzing::AssignStatement::Add;
+    case AssignOperator::And:
+      return Fuzzing::AssignStatement::And;
+    case AssignOperator::Mul:
+      return Fuzzing::AssignStatement::Mul;
+    case AssignOperator::Div:
+      return Fuzzing::AssignStatement::Div;
+    case AssignOperator::Mod:
+      return Fuzzing::AssignStatement::Mod;
+    case AssignOperator::Or:
+      return Fuzzing::AssignStatement::Or;
+    case AssignOperator::ShiftLeft:
+      return Fuzzing::AssignStatement::ShiftLeft;
+    case AssignOperator::ShiftRight:
+      return Fuzzing::AssignStatement::ShiftRight;
+    case AssignOperator::Sub:
+      return Fuzzing::AssignStatement::Sub;
+    case AssignOperator::Xor:
+      return Fuzzing::AssignStatement::Xor;
+  }
+}
+
 static auto FieldInitializerToProto(const FieldInitializer& field)
 static auto FieldInitializerToProto(const FieldInitializer& field)
     -> Fuzzing::FieldInitializer {
     -> Fuzzing::FieldInitializer {
   Fuzzing::FieldInitializer field_proto;
   Fuzzing::FieldInitializer field_proto;
@@ -435,6 +463,15 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement {
       auto* assign_proto = statement_proto.mutable_assign();
       auto* assign_proto = statement_proto.mutable_assign();
       *assign_proto->mutable_lhs() = ExpressionToProto(assign.lhs());
       *assign_proto->mutable_lhs() = ExpressionToProto(assign.lhs());
       *assign_proto->mutable_rhs() = ExpressionToProto(assign.rhs());
       *assign_proto->mutable_rhs() = ExpressionToProto(assign.rhs());
+      assign_proto->set_op(AssignOperatorToProtoEnum(assign.op()));
+      break;
+    }
+
+    case StatementKind::IncrementDecrement: {
+      const auto& inc_dec = cast<IncrementDecrement>(statement);
+      auto* inc_dec_proto = statement_proto.mutable_inc_dec();
+      *inc_dec_proto->mutable_operand() = ExpressionToProto(inc_dec.argument());
+      inc_dec_proto->set_is_increment(inc_dec.is_increment());
       break;
       break;
     }
     }
 
 

+ 67 - 7
explorer/interpreter/builtins.h

@@ -51,7 +51,26 @@ class Builtins {
     LeftShiftWith,
     LeftShiftWith,
     RightShiftWith,
     RightShiftWith,
 
 
-    Last = RightShiftWith
+    // Simple assignment.
+    AssignWith,
+
+    // Compound assignment.
+    AddAssignWith,
+    SubAssignWith,
+    MulAssignWith,
+    DivAssignWith,
+    ModAssignWith,
+    BitAndAssignWith,
+    BitOrAssignWith,
+    BitXorAssignWith,
+    LeftShiftAssignWith,
+    RightShiftAssignWith,
+
+    // Increment and decrement.
+    Inc,
+    Dec,
+
+    Last = Dec
   };
   };
   // TODO: In C++20, replace with `using enum Builtin;`.
   // TODO: In C++20, replace with `using enum Builtin;`.
   static constexpr Builtin As = Builtin::As;
   static constexpr Builtin As = Builtin::As;
@@ -61,6 +80,7 @@ class Builtins {
   static constexpr Builtin LessEqWith = Builtin::LessEqWith;
   static constexpr Builtin LessEqWith = Builtin::LessEqWith;
   static constexpr Builtin GreaterWith = Builtin::GreaterWith;
   static constexpr Builtin GreaterWith = Builtin::GreaterWith;
   static constexpr Builtin GreaterEqWith = Builtin::GreaterEqWith;
   static constexpr Builtin GreaterEqWith = Builtin::GreaterEqWith;
+  static constexpr Builtin CompareWith = Builtin::CompareWith;
   static constexpr Builtin Negate = Builtin::Negate;
   static constexpr Builtin Negate = Builtin::Negate;
   static constexpr Builtin AddWith = Builtin::AddWith;
   static constexpr Builtin AddWith = Builtin::AddWith;
   static constexpr Builtin SubWith = Builtin::SubWith;
   static constexpr Builtin SubWith = Builtin::SubWith;
@@ -73,7 +93,19 @@ class Builtins {
   static constexpr Builtin BitXorWith = Builtin::BitXorWith;
   static constexpr Builtin BitXorWith = Builtin::BitXorWith;
   static constexpr Builtin LeftShiftWith = Builtin::LeftShiftWith;
   static constexpr Builtin LeftShiftWith = Builtin::LeftShiftWith;
   static constexpr Builtin RightShiftWith = Builtin::RightShiftWith;
   static constexpr Builtin RightShiftWith = Builtin::RightShiftWith;
-  static constexpr Builtin CompareWith = Builtin::CompareWith;
+  static constexpr Builtin AssignWith = Builtin::AssignWith;
+  static constexpr Builtin AddAssignWith = Builtin::AddAssignWith;
+  static constexpr Builtin SubAssignWith = Builtin::SubAssignWith;
+  static constexpr Builtin MulAssignWith = Builtin::MulAssignWith;
+  static constexpr Builtin DivAssignWith = Builtin::DivAssignWith;
+  static constexpr Builtin ModAssignWith = Builtin::ModAssignWith;
+  static constexpr Builtin BitAndAssignWith = Builtin::BitAndAssignWith;
+  static constexpr Builtin BitOrAssignWith = Builtin::BitOrAssignWith;
+  static constexpr Builtin BitXorAssignWith = Builtin::BitXorAssignWith;
+  static constexpr Builtin LeftShiftAssignWith = Builtin::LeftShiftAssignWith;
+  static constexpr Builtin RightShiftAssignWith = Builtin::RightShiftAssignWith;
+  static constexpr Builtin Inc = Builtin::Inc;
+  static constexpr Builtin Dec = Builtin::Dec;
 
 
   // Register a declaration that might be a builtin.
   // Register a declaration that might be a builtin.
   void Register(Nonnull<const Declaration*> decl);
   void Register(Nonnull<const Declaration*> decl);
@@ -90,11 +122,39 @@ class Builtins {
  private:
  private:
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
   static constexpr const char* BuiltinNames[NumBuiltins] = {
   static constexpr const char* BuiltinNames[NumBuiltins] = {
-      "As",         "ImplicitAs",  "EqWith",        "LessWith",
-      "LessEqWith", "GreaterWith", "GreaterEqWith", "CompareWith",
-      "Negate",     "AddWith",     "SubWith",       "MulWith",
-      "DivWith",    "ModWith",     "BitComplement", "BitAndWith",
-      "BitOrWith",  "BitXorWith",  "LeftShiftWith", "RightShiftWith"};
+      "As",
+      "ImplicitAs",
+      "EqWith",
+      "LessWith",
+      "LessEqWith",
+      "GreaterWith",
+      "GreaterEqWith",
+      "CompareWith",
+      "Negate",
+      "AddWith",
+      "SubWith",
+      "MulWith",
+      "DivWith",
+      "ModWith",
+      "BitComplement",
+      "BitAndWith",
+      "BitOrWith",
+      "BitXorWith",
+      "LeftShiftWith",
+      "RightShiftWith",
+      "AssignWith",
+      "AddAssignWith",
+      "SubAssignWith",
+      "MulAssignWith",
+      "DivAssignWith",
+      "ModAssignWith",
+      "BitAndAssignWith",
+      "BitOrAssignWith",
+      "BitXorAssignWith",
+      "LeftShiftAssignWith",
+      "RightShiftAssignWith",
+      "Inc",
+      "Dec"};
 
 
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
 };
 };

+ 16 - 0
explorer/interpreter/interpreter.cpp

@@ -1937,6 +1937,13 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
       }
       }
     case StatementKind::Assign: {
     case StatementKind::Assign: {
       const auto& assign = cast<Assign>(stmt);
       const auto& assign = cast<Assign>(stmt);
+      if (auto rewrite = assign.rewritten_form()) {
+        if (act.pos() == 0) {
+          return todo_.Spawn(std::make_unique<ExpressionAction>(*rewrite));
+        } else {
+          return todo_.FinishAction();
+        }
+      }
       if (act.pos() == 0) {
       if (act.pos() == 0) {
         //    { {(lv = e) :: C, E, F} :: S, H}
         //    { {(lv = e) :: C, E, F} :: S, H}
         // -> { {lv :: ([] = e) :: C, E, F} :: S, H}
         // -> { {lv :: ([] = e) :: C, E, F} :: S, H}
@@ -1958,6 +1965,15 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
         return todo_.FinishAction();
         return todo_.FinishAction();
       }
       }
     }
     }
+    case StatementKind::IncrementDecrement: {
+      const auto& inc_dec = cast<IncrementDecrement>(stmt);
+      if (act.pos() == 0) {
+        return todo_.Spawn(
+            std::make_unique<ExpressionAction>(*inc_dec.rewritten_form()));
+      } else {
+        return todo_.FinishAction();
+      }
+    }
     case StatementKind::If:
     case StatementKind::If:
       if (act.pos() == 0) {
       if (act.pos() == 0) {
         //    { {(if (e) then_stmt else else_stmt) :: C, E, F} :: S, H}
         //    { {(if (e) then_stmt else else_stmt) :: C, E, F} :: S, H}

+ 1 - 0
explorer/interpreter/resolve_control_flow.cpp

@@ -130,6 +130,7 @@ static auto ResolveControlFlow(Nonnull<Statement*> statement,
       return Success();
       return Success();
     case StatementKind::ExpressionStatement:
     case StatementKind::ExpressionStatement:
     case StatementKind::Assign:
     case StatementKind::Assign:
+    case StatementKind::IncrementDecrement:
     case StatementKind::VariableDefinition:
     case StatementKind::VariableDefinition:
     case StatementKind::Run:
     case StatementKind::Run:
     case StatementKind::Await:
     case StatementKind::Await:

+ 5 - 0
explorer/interpreter/resolve_names.cpp

@@ -380,6 +380,11 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
       CARBON_RETURN_IF_ERROR(ResolveNames(assign.rhs(), enclosing_scope));
       CARBON_RETURN_IF_ERROR(ResolveNames(assign.rhs(), enclosing_scope));
       break;
       break;
     }
     }
+    case StatementKind::IncrementDecrement: {
+      auto& inc_dec = cast<IncrementDecrement>(statement);
+      CARBON_RETURN_IF_ERROR(ResolveNames(inc_dec.argument(), enclosing_scope));
+      break;
+    }
     case StatementKind::VariableDefinition: {
     case StatementKind::VariableDefinition: {
       auto& def = cast<VariableDefinition>(statement);
       auto& def = cast<VariableDefinition>(statement);
       if (def.has_init()) {
       if (def.has_init()) {

+ 10 - 1
explorer/interpreter/resolve_unformed.cpp

@@ -222,7 +222,10 @@ static auto ResolveUnformed(Nonnull<const Statement*> statement,
     }
     }
     case StatementKind::Assign: {
     case StatementKind::Assign: {
       const auto& assign = cast<Assign>(*statement);
       const auto& assign = cast<Assign>(*statement);
-      if (assign.lhs().kind() == ExpressionKind::IdentifierExpression) {
+      if (assign.op() != AssignOperator::Plain) {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts,
+                                               FlowFacts::ActionType::Check));
+      } else if (assign.lhs().kind() == ExpressionKind::IdentifierExpression) {
         CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts,
         CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts,
                                                FlowFacts::ActionType::Form));
                                                FlowFacts::ActionType::Form));
       } else {
       } else {
@@ -234,6 +237,12 @@ static auto ResolveUnformed(Nonnull<const Statement*> statement,
                                              FlowFacts::ActionType::Check));
                                              FlowFacts::ActionType::Check));
       break;
       break;
     }
     }
+    case StatementKind::IncrementDecrement: {
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&cast<IncrementDecrement>(statement)->argument(),
+                          flow_facts, FlowFacts::ActionType::Check));
+      break;
+    }
     case StatementKind::ExpressionStatement: {
     case StatementKind::ExpressionStatement: {
       const auto& exp_stmt = cast<ExpressionStatement>(*statement);
       const auto& exp_stmt = cast<ExpressionStatement>(*statement);
       CARBON_RETURN_IF_ERROR(
       CARBON_RETURN_IF_ERROR(

+ 62 - 4
explorer/interpreter/type_checker.cpp

@@ -4090,6 +4090,36 @@ auto TypeChecker::TypeCheckGenericBinding(GenericBinding& binding,
   return Success();
   return Success();
 }
 }
 
 
+// Get the builtin interface that should be used for the given kind of
+// assignment operator.
+static Builtins::Builtin GetBuiltinInterfaceForAssignOperator(
+    AssignOperator op) {
+  switch (op) {
+    case AssignOperator::Plain:
+      return Builtins::AssignWith;
+    case AssignOperator::Add:
+      return Builtins::AddAssignWith;
+    case AssignOperator::Sub:
+      return Builtins::SubAssignWith;
+    case AssignOperator::Mul:
+      return Builtins::MulAssignWith;
+    case AssignOperator::Div:
+      return Builtins::DivAssignWith;
+    case AssignOperator::Mod:
+      return Builtins::ModAssignWith;
+    case AssignOperator::And:
+      return Builtins::BitAndAssignWith;
+    case AssignOperator::Or:
+      return Builtins::BitOrAssignWith;
+    case AssignOperator::Xor:
+      return Builtins::BitXorAssignWith;
+    case AssignOperator::ShiftLeft:
+      return Builtins::LeftShiftAssignWith;
+    case AssignOperator::ShiftRight:
+      return Builtins::RightShiftAssignWith;
+  }
+}
+
 auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
 auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
                                 const ImplScope& impl_scope)
                                 const ImplScope& impl_scope)
     -> ErrorOr<Success> {
     -> ErrorOr<Success> {
@@ -4232,11 +4262,38 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
         return ProgramError(assign.source_loc())
         return ProgramError(assign.source_loc())
                << "Cannot assign to rvalue '" << assign.lhs() << "'";
                << "Cannot assign to rvalue '" << assign.lhs() << "'";
       }
       }
+      if (assign.op() == AssignOperator::Plain &&
+          IsSameType(&assign.lhs().static_type(), &assign.rhs().static_type(),
+                     impl_scope)) {
+        // TODO: Interface lookup.
+        CARBON_ASSIGN_OR_RETURN(
+            Nonnull<Expression*> converted_rhs,
+            ImplicitlyConvert("assignment", impl_scope, &assign.rhs(),
+                              &assign.lhs().static_type()));
+        assign.set_rhs(converted_rhs);
+      } else {
+        CARBON_ASSIGN_OR_RETURN(
+            Nonnull<Expression*> rewritten,
+            BuildBuiltinMethodCall(
+                impl_scope, &assign.lhs(),
+                BuiltinInterfaceName{
+                    GetBuiltinInterfaceForAssignOperator(assign.op()),
+                    {&assign.rhs().static_type()}},
+                BuiltinMethodCall{"Op", {&assign.rhs()}}));
+        assign.set_rewritten_form(rewritten);
+      }
+      return Success();
+    }
+    case StatementKind::IncrementDecrement: {
+      auto& inc_dec = cast<IncrementDecrement>(*s);
       CARBON_ASSIGN_OR_RETURN(
       CARBON_ASSIGN_OR_RETURN(
-          Nonnull<Expression*> converted_rhs,
-          ImplicitlyConvert("assignment", impl_scope, &assign.rhs(),
-                            &assign.lhs().static_type()));
-      assign.set_rhs(converted_rhs);
+          Nonnull<Expression*> rewritten,
+          BuildBuiltinMethodCall(
+              impl_scope, &inc_dec.argument(),
+              BuiltinInterfaceName{
+                  inc_dec.is_increment() ? Builtins::Inc : Builtins::Dec, {}},
+              BuiltinMethodCall{"Op"}));
+      inc_dec.set_rewritten_form(rewritten);
       return Success();
       return Success();
     }
     }
     case StatementKind::ExpressionStatement: {
     case StatementKind::ExpressionStatement: {
@@ -4380,6 +4437,7 @@ auto TypeChecker::ExpectReturnOnAllPaths(
     case StatementKind::Run:
     case StatementKind::Run:
     case StatementKind::Await:
     case StatementKind::Await:
     case StatementKind::Assign:
     case StatementKind::Assign:
+    case StatementKind::IncrementDecrement:
     case StatementKind::ExpressionStatement:
     case StatementKind::ExpressionStatement:
     case StatementKind::While:
     case StatementKind::While:
     case StatementKind::For:
     case StatementKind::For:

+ 193 - 169
explorer/syntax/lexer.lpp

@@ -31,92 +31,104 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 %s AFTER_OPERAND
 %s AFTER_OPERAND
 
 
 /* table-begin */
 /* table-begin */
-ABSTRACT             "abstract"
-ADDR                 "addr"
-ALIAS                "alias"
-AMPERSAND            "&"
-AND                  "and"
-API                  "api"
-ARROW                "->"
-AS                   "as"
-AUTO                 "auto"
-AWAIT                "__await"
-BASE                 "base"
-BOOL                 "bool"
-BREAK                "break"
-CARET                "^"
-CASE                 "case"
-CHOICE               "choice"
-CLASS                "class"
-COLON                ":"
-COLON_BANG           ":!"
-COMMA                ","
-CONSTRAINT           "constraint"
-CONTINUATION         "__continuation"
-CONTINUATION_TYPE    "__Continuation"
-CONTINUE             "continue"
-DEFAULT              "default"
-DESTRUCTOR           "destructor"
-DOUBLE_ARROW         "=>"
-ELSE                 "else"
-EQUAL                "="
-EQUAL_EQUAL          "=="
-EXTENDS              "extends"
-EXTERNAL             "external"
-FALSE                "false"
-FN                   "fn"
-FN_TYPE              "__Fn"
-FOR                  "for"
-FORALL               "forall"
-GREATER              ">"
-GREATER_EQUAL        ">="
-GREATER_GREATER      ">>"
-IF                   "if"
-IMPL                 "impl"
-IMPORT               "import"
-IN                   "in"
-INTERFACE            "interface"
-IS                   "is"
-LEFT_CURLY_BRACE     "{"
-LEFT_PARENTHESIS     "("
-LEFT_SQUARE_BRACKET  "["
-LESS                 "<"
-LESS_EQUAL           "<="
-LESS_LESS            "<<"
-LET                  "let"
-LIBRARY              "library"
-MATCH                "match"
-MATCH_FIRST          "__match_first"
-MINUS                "-"
-MIX                  "__mix"
-MIXIN                "__mixin"
-NOT                  "not"
-NOT_EQUAL            "!="
-OR                   "or"
-PACKAGE              "package"
-PERCENT              "%"
-PERIOD               "."
-PIPE                 "|"
-PLUS                 "+"
-RETURN               "return"
-RETURNED             "returned"
-RIGHT_CURLY_BRACE    "}"
-RIGHT_PARENTHESIS    ")"
-RIGHT_SQUARE_BRACKET "]"
-RUN                  "__run"
-SELF                 "Self"
-SEMICOLON            ";"
-SLASH                "/"
-STRING               "String"
-THEN                 "then"
-TRUE                 "true"
-TYPE                 "type"
-UNDERSCORE           "_"
-UNIMPL_EXAMPLE       "__unimplemented_example_infix"
-VAR                  "var"
-VIRTUAL              "virtual"
-WHERE                "where"
-WHILE                "while"
+ABSTRACT              "abstract"
+ADDR                  "addr"
+ALIAS                 "alias"
+AMPERSAND             "&"
+AMPERSAND_EQUAL       "&="
+AND                   "and"
+API                   "api"
+ARROW                 "->"
+AS                    "as"
+AUTO                  "auto"
+AWAIT                 "__await"
+BASE                  "base"
+BOOL                  "bool"
+BREAK                 "break"
+CARET                 "^"
+CARET_EQUAL           "^="
+CASE                  "case"
+CHOICE                "choice"
+CLASS                 "class"
+COLON                 ":"
+COLON_BANG            ":!"
+COMMA                 ","
+CONSTRAINT            "constraint"
+CONTINUATION          "__continuation"
+CONTINUATION_TYPE     "__Continuation"
+CONTINUE              "continue"
+DEFAULT               "default"
+DESTRUCTOR            "destructor"
+DOUBLE_ARROW          "=>"
+ELSE                  "else"
+EQUAL                 "="
+EQUAL_EQUAL           "=="
+EXTENDS               "extends"
+EXTERNAL              "external"
+FALSE                 "false"
+FN                    "fn"
+FN_TYPE               "__Fn"
+FOR                   "for"
+FORALL                "forall"
+GREATER               ">"
+GREATER_EQUAL         ">="
+GREATER_GREATER       ">>"
+GREATER_GREATER_EQUAL ">>="
+IF                    "if"
+IMPL                  "impl"
+IMPORT                "import"
+IN                    "in"
+INTERFACE             "interface"
+IS                    "is"
+LEFT_CURLY_BRACE      "{"
+LEFT_PARENTHESIS      "("
+LEFT_SQUARE_BRACKET   "["
+LESS                  "<"
+LESS_EQUAL            "<="
+LESS_LESS             "<<"
+LESS_LESS_EQUAL       "<<="
+LET                   "let"
+LIBRARY               "library"
+MATCH                 "match"
+MATCH_FIRST           "__match_first"
+MINUS                 "-"
+MINUS_EQUAL           "-="
+MINUS_MINUS           "--"
+MIX                   "__mix"
+MIXIN                 "__mixin"
+NOT                   "not"
+NOT_EQUAL             "!="
+OR                    "or"
+PACKAGE               "package"
+PERCENT               "%"
+PERCENT_EQUAL         "%="
+PERIOD                "."
+PIPE                  "|"
+PIPE_EQUAL            "|="
+PLUS                  "+"
+PLUS_EQUAL            "+="
+PLUS_PLUS             "++"
+RETURN                "return"
+RETURNED              "returned"
+RIGHT_CURLY_BRACE     "}"
+RIGHT_PARENTHESIS     ")"
+RIGHT_SQUARE_BRACKET  "]"
+RUN                   "__run"
+SELF                  "Self"
+SEMICOLON             ";"
+SLASH                 "/"
+SLASH_EQUAL           "/="
+STAR_EQUAL            "*="
+STRING                "String"
+THEN                  "then"
+TRUE                  "true"
+TYPE                  "type"
+UNDERSCORE            "_"
+UNIMPL_EXAMPLE        "__unimplemented_example_infix"
+VAR                   "var"
+VIRTUAL               "virtual"
+WHERE                 "where"
+WHILE                 "while"
 /* table-end */
 /* table-end */
 
 
 /* This should be kept table-like, but isn't automatic due to spaces. */
 /* This should be kept table-like, but isn't automatic due to spaces. */
@@ -140,89 +152,101 @@ operand_start         [(A-Za-z0-9_\"]
 %}
 %}
 
 
  /* table-begin */
  /* table-begin */
-{ABSTRACT}            { return CARBON_SIMPLE_TOKEN(ABSTRACT);            }
-{ADDR}                { return CARBON_SIMPLE_TOKEN(ADDR);                }
-{ALIAS}               { return CARBON_SIMPLE_TOKEN(ALIAS);               }
-{AMPERSAND}           { return CARBON_SIMPLE_TOKEN(AMPERSAND);           }
-{AND}                 { return CARBON_SIMPLE_TOKEN(AND);                 }
-{API}                 { return CARBON_SIMPLE_TOKEN(API);                 }
-{ARROW}               { return CARBON_SIMPLE_TOKEN(ARROW);               }
-{AS}                  { return CARBON_SIMPLE_TOKEN(AS);                  }
-{AUTO}                { return CARBON_SIMPLE_TOKEN(AUTO);                }
-{AWAIT}               { return CARBON_SIMPLE_TOKEN(AWAIT);               }
-{BASE}                { return CARBON_SIMPLE_TOKEN(BASE);                }
-{BOOL}                { return CARBON_SIMPLE_TOKEN(BOOL);                }
-{BREAK}               { return CARBON_SIMPLE_TOKEN(BREAK);               }
-{CARET}               { return CARBON_SIMPLE_TOKEN(CARET);               }
-{CASE}                { return CARBON_SIMPLE_TOKEN(CASE);                }
-{CHOICE}              { return CARBON_SIMPLE_TOKEN(CHOICE);              }
-{CLASS}               { return CARBON_SIMPLE_TOKEN(CLASS);               }
-{COLON_BANG}          { return CARBON_SIMPLE_TOKEN(COLON_BANG);          }
-{COLON}               { return CARBON_SIMPLE_TOKEN(COLON);               }
-{COMMA}               { return CARBON_SIMPLE_TOKEN(COMMA);               }
-{CONSTRAINT}          { return CARBON_SIMPLE_TOKEN(CONSTRAINT);          }
-{CONTINUATION_TYPE}   { return CARBON_SIMPLE_TOKEN(CONTINUATION_TYPE);   }
-{CONTINUATION}        { return CARBON_SIMPLE_TOKEN(CONTINUATION);        }
-{CONTINUE}            { return CARBON_SIMPLE_TOKEN(CONTINUE);            }
-{DEFAULT}             { return CARBON_SIMPLE_TOKEN(DEFAULT);             }
-{DESTRUCTOR}          { return CARBON_SIMPLE_TOKEN(DESTRUCTOR);          }
-{DOUBLE_ARROW}        { return CARBON_SIMPLE_TOKEN(DOUBLE_ARROW);        }
-{ELSE}                { return CARBON_SIMPLE_TOKEN(ELSE);                }
-{EQUAL_EQUAL}         { return CARBON_SIMPLE_TOKEN(EQUAL_EQUAL);         }
-{EQUAL}               { return CARBON_SIMPLE_TOKEN(EQUAL);               }
-{EXTENDS}             { return CARBON_SIMPLE_TOKEN(EXTENDS);             }
-{EXTERNAL}            { return CARBON_SIMPLE_TOKEN(EXTERNAL);            }
-{FALSE}               { return CARBON_SIMPLE_TOKEN(FALSE);               }
-{FN_TYPE}             { return CARBON_SIMPLE_TOKEN(FN_TYPE);             }
-{FN}                  { return CARBON_SIMPLE_TOKEN(FN);                  }
-{FORALL}              { return CARBON_SIMPLE_TOKEN(FORALL);              }
-{FOR}                 { return CARBON_SIMPLE_TOKEN(FOR);                 }
-{GREATER_EQUAL}       { return CARBON_SIMPLE_TOKEN(GREATER_EQUAL);       }
-{GREATER_GREATER}     { return CARBON_SIMPLE_TOKEN(GREATER_GREATER);     }
-{GREATER}             { return CARBON_SIMPLE_TOKEN(GREATER);             }
-{IF}                  { return CARBON_SIMPLE_TOKEN(IF);                  }
-{IMPL}                { return CARBON_SIMPLE_TOKEN(IMPL);                }
-{IMPORT}              { return CARBON_SIMPLE_TOKEN(IMPORT);              }
-{INTERFACE}           { return CARBON_SIMPLE_TOKEN(INTERFACE);           }
-{IN}                  { return CARBON_SIMPLE_TOKEN(IN);                  }
-{IS}                  { return CARBON_SIMPLE_TOKEN(IS);                  }
-{LEFT_CURLY_BRACE}    { return CARBON_SIMPLE_TOKEN(LEFT_CURLY_BRACE);    }
-{LEFT_PARENTHESIS}    { return CARBON_SIMPLE_TOKEN(LEFT_PARENTHESIS);    }
-{LEFT_SQUARE_BRACKET} { return CARBON_SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); }
-{LESS_EQUAL}          { return CARBON_SIMPLE_TOKEN(LESS_EQUAL);          }
-{LESS_LESS}           { return CARBON_SIMPLE_TOKEN(LESS_LESS);           }
-{LESS}                { return CARBON_SIMPLE_TOKEN(LESS);                }
-{LET}                 { return CARBON_SIMPLE_TOKEN(LET);                 }
-{LIBRARY}             { return CARBON_SIMPLE_TOKEN(LIBRARY);             }
-{MATCH_FIRST}         { return CARBON_SIMPLE_TOKEN(MATCH_FIRST);         }
-{MATCH}               { return CARBON_SIMPLE_TOKEN(MATCH);               }
-{MINUS}               { return CARBON_SIMPLE_TOKEN(MINUS);               }
-{MIXIN}               { return CARBON_SIMPLE_TOKEN(MIXIN);               }
-{MIX}                 { return CARBON_SIMPLE_TOKEN(MIX);                 }
-{NOT_EQUAL}           { return CARBON_SIMPLE_TOKEN(NOT_EQUAL);           }
-{NOT}                 { return CARBON_SIMPLE_TOKEN(NOT);                 }
-{OR}                  { return CARBON_SIMPLE_TOKEN(OR);                  }
-{PACKAGE}             { return CARBON_SIMPLE_TOKEN(PACKAGE);             }
-{PERCENT}             { return CARBON_SIMPLE_TOKEN(PERCENT);             }
-{PERIOD}              { return CARBON_SIMPLE_TOKEN(PERIOD);              }
-{PIPE}                { return CARBON_SIMPLE_TOKEN(PIPE);                }
-{PLUS}                { return CARBON_SIMPLE_TOKEN(PLUS);                }
-{RETURNED}            { return CARBON_SIMPLE_TOKEN(RETURNED);            }
-{RETURN}              { return CARBON_SIMPLE_TOKEN(RETURN);              }
-{RUN}                 { return CARBON_SIMPLE_TOKEN(RUN);                 }
-{SELF}                { return CARBON_SIMPLE_TOKEN(SELF);                }
-{SEMICOLON}           { return CARBON_SIMPLE_TOKEN(SEMICOLON);           }
-{SLASH}               { return CARBON_SIMPLE_TOKEN(SLASH);               }
-{STRING}              { return CARBON_SIMPLE_TOKEN(STRING);              }
-{THEN}                { return CARBON_SIMPLE_TOKEN(THEN);                }
-{TRUE}                { return CARBON_SIMPLE_TOKEN(TRUE);                }
-{TYPE}                { return CARBON_SIMPLE_TOKEN(TYPE);                }
-{UNDERSCORE}          { return CARBON_SIMPLE_TOKEN(UNDERSCORE);          }
-{UNIMPL_EXAMPLE}      { return CARBON_SIMPLE_TOKEN(UNIMPL_EXAMPLE);      }
-{VAR}                 { return CARBON_SIMPLE_TOKEN(VAR);                 }
-{VIRTUAL}             { return CARBON_SIMPLE_TOKEN(VIRTUAL);             }
-{WHERE}               { return CARBON_SIMPLE_TOKEN(WHERE);               }
-{WHILE}               { return CARBON_SIMPLE_TOKEN(WHILE);               }
+{ABSTRACT}              { return CARBON_SIMPLE_TOKEN(ABSTRACT);              }
+{ADDR}                  { return CARBON_SIMPLE_TOKEN(ADDR);                  }
+{ALIAS}                 { return CARBON_SIMPLE_TOKEN(ALIAS);                 }
+{AMPERSAND_EQUAL}       { return CARBON_SIMPLE_TOKEN(AMPERSAND_EQUAL);       }
+{AMPERSAND}             { return CARBON_SIMPLE_TOKEN(AMPERSAND);             }
+{AND}                   { return CARBON_SIMPLE_TOKEN(AND);                   }
+{API}                   { return CARBON_SIMPLE_TOKEN(API);                   }
+{ARROW}                 { return CARBON_SIMPLE_TOKEN(ARROW);                 }
+{AS}                    { return CARBON_SIMPLE_TOKEN(AS);                    }
+{AUTO}                  { return CARBON_SIMPLE_TOKEN(AUTO);                  }
+{AWAIT}                 { return CARBON_SIMPLE_TOKEN(AWAIT);                 }
+{BASE}                  { return CARBON_SIMPLE_TOKEN(BASE);                  }
+{BOOL}                  { return CARBON_SIMPLE_TOKEN(BOOL);                  }
+{BREAK}                 { return CARBON_SIMPLE_TOKEN(BREAK);                 }
+{CARET_EQUAL}           { return CARBON_SIMPLE_TOKEN(CARET_EQUAL);           }
+{CARET}                 { return CARBON_SIMPLE_TOKEN(CARET);                 }
+{CASE}                  { return CARBON_SIMPLE_TOKEN(CASE);                  }
+{CHOICE}                { return CARBON_SIMPLE_TOKEN(CHOICE);                }
+{CLASS}                 { return CARBON_SIMPLE_TOKEN(CLASS);                 }
+{COLON_BANG}            { return CARBON_SIMPLE_TOKEN(COLON_BANG);            }
+{COLON}                 { return CARBON_SIMPLE_TOKEN(COLON);                 }
+{COMMA}                 { return CARBON_SIMPLE_TOKEN(COMMA);                 }
+{CONSTRAINT}            { return CARBON_SIMPLE_TOKEN(CONSTRAINT);            }
+{CONTINUATION_TYPE}     { return CARBON_SIMPLE_TOKEN(CONTINUATION_TYPE);     }
+{CONTINUATION}          { return CARBON_SIMPLE_TOKEN(CONTINUATION);          }
+{CONTINUE}              { return CARBON_SIMPLE_TOKEN(CONTINUE);              }
+{DEFAULT}               { return CARBON_SIMPLE_TOKEN(DEFAULT);               }
+{DESTRUCTOR}            { return CARBON_SIMPLE_TOKEN(DESTRUCTOR);            }
+{DOUBLE_ARROW}          { return CARBON_SIMPLE_TOKEN(DOUBLE_ARROW);          }
+{ELSE}                  { return CARBON_SIMPLE_TOKEN(ELSE);                  }
+{EQUAL_EQUAL}           { return CARBON_SIMPLE_TOKEN(EQUAL_EQUAL);           }
+{EQUAL}                 { return CARBON_SIMPLE_TOKEN(EQUAL);                 }
+{EXTENDS}               { return CARBON_SIMPLE_TOKEN(EXTENDS);               }
+{EXTERNAL}              { return CARBON_SIMPLE_TOKEN(EXTERNAL);              }
+{FALSE}                 { return CARBON_SIMPLE_TOKEN(FALSE);                 }
+{FN_TYPE}               { return CARBON_SIMPLE_TOKEN(FN_TYPE);               }
+{FN}                    { return CARBON_SIMPLE_TOKEN(FN);                    }
+{FORALL}                { return CARBON_SIMPLE_TOKEN(FORALL);                }
+{FOR}                   { return CARBON_SIMPLE_TOKEN(FOR);                   }
+{GREATER_EQUAL}         { return CARBON_SIMPLE_TOKEN(GREATER_EQUAL);         }
+{GREATER_GREATER_EQUAL} { return CARBON_SIMPLE_TOKEN(GREATER_GREATER_EQUAL); }
+{GREATER_GREATER}       { return CARBON_SIMPLE_TOKEN(GREATER_GREATER);       }
+{GREATER}               { return CARBON_SIMPLE_TOKEN(GREATER);               }
+{IF}                    { return CARBON_SIMPLE_TOKEN(IF);                    }
+{IMPL}                  { return CARBON_SIMPLE_TOKEN(IMPL);                  }
+{IMPORT}                { return CARBON_SIMPLE_TOKEN(IMPORT);                }
+{INTERFACE}             { return CARBON_SIMPLE_TOKEN(INTERFACE);             }
+{IN}                    { return CARBON_SIMPLE_TOKEN(IN);                    }
+{IS}                    { return CARBON_SIMPLE_TOKEN(IS);                    }
+{LEFT_CURLY_BRACE}      { return CARBON_SIMPLE_TOKEN(LEFT_CURLY_BRACE);      }
+{LEFT_PARENTHESIS}      { return CARBON_SIMPLE_TOKEN(LEFT_PARENTHESIS);      }
+{LEFT_SQUARE_BRACKET}   { return CARBON_SIMPLE_TOKEN(LEFT_SQUARE_BRACKET);   }
+{LESS_EQUAL}            { return CARBON_SIMPLE_TOKEN(LESS_EQUAL);            }
+{LESS_LESS_EQUAL}       { return CARBON_SIMPLE_TOKEN(LESS_LESS_EQUAL);       }
+{LESS_LESS}             { return CARBON_SIMPLE_TOKEN(LESS_LESS);             }
+{LESS}                  { return CARBON_SIMPLE_TOKEN(LESS);                  }
+{LET}                   { return CARBON_SIMPLE_TOKEN(LET);                   }
+{LIBRARY}               { return CARBON_SIMPLE_TOKEN(LIBRARY);               }
+{MATCH_FIRST}           { return CARBON_SIMPLE_TOKEN(MATCH_FIRST);           }
+{MATCH}                 { return CARBON_SIMPLE_TOKEN(MATCH);                 }
+{MINUS_EQUAL}           { return CARBON_SIMPLE_TOKEN(MINUS_EQUAL);           }
+{MINUS_MINUS}           { return CARBON_SIMPLE_TOKEN(MINUS_MINUS);           }
+{MINUS}                 { return CARBON_SIMPLE_TOKEN(MINUS);                 }
+{MIXIN}                 { return CARBON_SIMPLE_TOKEN(MIXIN);                 }
+{MIX}                   { return CARBON_SIMPLE_TOKEN(MIX);                   }
+{NOT_EQUAL}             { return CARBON_SIMPLE_TOKEN(NOT_EQUAL);             }
+{NOT}                   { return CARBON_SIMPLE_TOKEN(NOT);                   }
+{OR}                    { return CARBON_SIMPLE_TOKEN(OR);                    }
+{PACKAGE}               { return CARBON_SIMPLE_TOKEN(PACKAGE);               }
+{PERCENT_EQUAL}         { return CARBON_SIMPLE_TOKEN(PERCENT_EQUAL);         }
+{PERCENT}               { return CARBON_SIMPLE_TOKEN(PERCENT);               }
+{PERIOD}                { return CARBON_SIMPLE_TOKEN(PERIOD);                }
+{PIPE_EQUAL}            { return CARBON_SIMPLE_TOKEN(PIPE_EQUAL);            }
+{PIPE}                  { return CARBON_SIMPLE_TOKEN(PIPE);                  }
+{PLUS_EQUAL}            { return CARBON_SIMPLE_TOKEN(PLUS_EQUAL);            }
+{PLUS_PLUS}             { return CARBON_SIMPLE_TOKEN(PLUS_PLUS);             }
+{PLUS}                  { return CARBON_SIMPLE_TOKEN(PLUS);                  }
+{RETURNED}              { return CARBON_SIMPLE_TOKEN(RETURNED);              }
+{RETURN}                { return CARBON_SIMPLE_TOKEN(RETURN);                }
+{RUN}                   { return CARBON_SIMPLE_TOKEN(RUN);                   }
+{SELF}                  { return CARBON_SIMPLE_TOKEN(SELF);                  }
+{SEMICOLON}             { return CARBON_SIMPLE_TOKEN(SEMICOLON);             }
+{SLASH_EQUAL}           { return CARBON_SIMPLE_TOKEN(SLASH_EQUAL);           }
+{SLASH}                 { return CARBON_SIMPLE_TOKEN(SLASH);                 }
+{STAR_EQUAL}            { return CARBON_SIMPLE_TOKEN(STAR_EQUAL);            }
+{STRING}                { return CARBON_SIMPLE_TOKEN(STRING);                }
+{THEN}                  { return CARBON_SIMPLE_TOKEN(THEN);                  }
+{TRUE}                  { return CARBON_SIMPLE_TOKEN(TRUE);                  }
+{TYPE}                  { return CARBON_SIMPLE_TOKEN(TYPE);                  }
+{UNDERSCORE}            { return CARBON_SIMPLE_TOKEN(UNDERSCORE);            }
+{UNIMPL_EXAMPLE}        { return CARBON_SIMPLE_TOKEN(UNIMPL_EXAMPLE);        }
+{VAR}                   { return CARBON_SIMPLE_TOKEN(VAR);                   }
+{VIRTUAL}               { return CARBON_SIMPLE_TOKEN(VIRTUAL);               }
+{WHERE}                 { return CARBON_SIMPLE_TOKEN(WHERE);                 }
+{WHILE}                 { return CARBON_SIMPLE_TOKEN(WHILE);                 }
  /* table-end */
  /* table-end */
 
 
  /* More modern Bisons provide make_EOF. */
  /* More modern Bisons provide make_EOF. */

+ 48 - 2
explorer/syntax/parser.ypp

@@ -121,6 +121,8 @@
 %type <std::vector<Nonnull<Declaration*>>> interface_body
 %type <std::vector<Nonnull<Declaration*>>> interface_body
 %type <std::vector<Nonnull<Declaration*>>> impl_body
 %type <std::vector<Nonnull<Declaration*>>> impl_body
 %type <Nonnull<Statement*>> statement
 %type <Nonnull<Statement*>> statement
+%type <Nonnull<Statement*>> assign_statement
+%type <AssignOperator> assign_operator
 %type <Nonnull<If*>> if_statement
 %type <Nonnull<If*>> if_statement
 %type <std::optional<Nonnull<Block*>>> optional_else
 %type <std::optional<Nonnull<Block*>>> optional_else
 %type <std::pair<Nonnull<Expression*>, bool>> return_expression
 %type <std::pair<Nonnull<Expression*>, bool>> return_expression
@@ -207,6 +209,7 @@
   ADDR
   ADDR
   ALIAS
   ALIAS
   AMPERSAND
   AMPERSAND
+  AMPERSAND_EQUAL
   AND
   AND
   API
   API
   ARROW
   ARROW
@@ -217,6 +220,7 @@
   BOOL
   BOOL
   BREAK
   BREAK
   CARET
   CARET
+  CARET_EQUAL
   CASE
   CASE
   CHOICE
   CHOICE
   CLASS
   CLASS
@@ -243,6 +247,7 @@
   GREATER
   GREATER
   GREATER_EQUAL
   GREATER_EQUAL
   GREATER_GREATER
   GREATER_GREATER
+  GREATER_GREATER_EQUAL
   IF
   IF
   IMPL
   IMPL
   IMPORT
   IMPORT
@@ -255,21 +260,29 @@
   LESS
   LESS
   LESS_EQUAL
   LESS_EQUAL
   LESS_LESS
   LESS_LESS
+  LESS_LESS_EQUAL
   LET
   LET
   LIBRARY
   LIBRARY
   MATCH
   MATCH
   MATCH_FIRST
   MATCH_FIRST
   MINUS
   MINUS
+  MINUS_EQUAL
+  MINUS_MINUS
   MIX
   MIX
   MIXIN
   MIXIN
   NOT
   NOT
   NOT_EQUAL
   NOT_EQUAL
   OR
   OR
+  OR_EQUAL
   PACKAGE
   PACKAGE
   PERCENT
   PERCENT
+  PERCENT_EQUAL
   PERIOD
   PERIOD
   PIPE
   PIPE
+  PIPE_EQUAL
   PLUS
   PLUS
+  PLUS_EQUAL
+  PLUS_PLUS
   RETURN
   RETURN
   RETURNED
   RETURNED
   RIGHT_CURLY_BRACE
   RIGHT_CURLY_BRACE
@@ -279,6 +292,8 @@
   SELF
   SELF
   SEMICOLON
   SEMICOLON
   SLASH
   SLASH
+  SLASH_EQUAL
+  STAR_EQUAL
   STRING
   STRING
   THEN
   THEN
   TRUE
   TRUE
@@ -927,8 +942,7 @@ clause_list:
     }
     }
 ;
 ;
 statement:
 statement:
-  statement_expression EQUAL expression SEMICOLON
-    { $$ = arena->New<Assign>(context.source_loc(), $1, $3); }
+  assign_statement
 | VAR pattern SEMICOLON
 | VAR pattern SEMICOLON
     {
     {
       $$ = arena->New<VariableDefinition>(
       $$ = arena->New<VariableDefinition>(
@@ -989,6 +1003,38 @@ statement:
 | FOR LEFT_PARENTHESIS variable_declaration IN type_expression RIGHT_PARENTHESIS block
 | FOR LEFT_PARENTHESIS variable_declaration IN type_expression RIGHT_PARENTHESIS block
     { $$ = arena->New<For>(context.source_loc(), $3, $5, $7); }
     { $$ = arena->New<For>(context.source_loc(), $3, $5, $7); }
 ;
 ;
+assign_statement:
+  statement_expression assign_operator expression SEMICOLON
+    { $$ = arena->New<Assign>(context.source_loc(), $1, $2, $3); }
+| PLUS_PLUS expression SEMICOLON
+    { $$ = arena->New<IncrementDecrement>(context.source_loc(), $2, true); }
+| MINUS_MINUS expression SEMICOLON
+    { $$ = arena->New<IncrementDecrement>(context.source_loc(), $2, false); }
+;
+assign_operator:
+  EQUAL
+    { $$ = AssignOperator::Plain; }
+| PLUS_EQUAL
+    { $$ = AssignOperator::Add; }
+| SLASH_EQUAL
+    { $$ = AssignOperator::Div; }
+| STAR_EQUAL
+    { $$ = AssignOperator::Mul; }
+| PERCENT_EQUAL
+    { $$ = AssignOperator::Mod; }
+| MINUS_EQUAL
+    { $$ = AssignOperator::Sub; }
+| AMPERSAND_EQUAL
+    { $$ = AssignOperator::And; }
+| PIPE_EQUAL
+    { $$ = AssignOperator::Or; }
+| CARET_EQUAL
+    { $$ = AssignOperator::Xor; }
+| LESS_LESS_EQUAL
+    { $$ = AssignOperator::ShiftLeft; }
+| GREATER_GREATER_EQUAL
+    { $$ = AssignOperator::ShiftRight; }
+;
 if_statement:
 if_statement:
   IF LEFT_PARENTHESIS expression RIGHT_PARENTHESIS block optional_else
   IF LEFT_PARENTHESIS expression RIGHT_PARENTHESIS block optional_else
     { $$ = arena->New<If>(context.source_loc(), $3, $5, $6); }
     { $$ = arena->New<If>(context.source_loc(), $3, $5, $6); }

+ 1 - 1
explorer/testdata/basic_syntax/fail_missing_var.carbon

@@ -10,7 +10,7 @@ package ExplorerTest api;
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   // error
   // error
-  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary *
+  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON
   x : i32;
   x : i32;
   return 1;
   return 1;
 }
 }

+ 1 - 1
explorer/testdata/basic_syntax/fail_var_named_self.carbon

@@ -12,7 +12,7 @@ fn Main() -> i32 {
   // Error: can't use keyword `Self` as the name of a variable.
   // Error: can't use keyword `Self` as the name of a variable.
   // TODO: Current error message is unclear, better would be to say
   // TODO: Current error message is unclear, better would be to say
   // something like: unexpected `Self`, expecting identifier
   // something like: unexpected `Self`, expecting identifier
-  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary *
+  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL or SEMICOLON
   var Self : i32 = 0;
   var Self : i32 = 0;
   return Self;
   return Self;
 }
 }

+ 1 - 1
explorer/testdata/let/fail_local_named_self.carbon

@@ -10,7 +10,7 @@ package ExplorerTest api;
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   // Error: Can't use keyword `Self` as the name of a local.
   // Error: Can't use keyword `Self` as the name of a local.
-  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/let/fail_local_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary *
+  // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/let/fail_local_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL
   let Self: auto = 10;
   let Self: auto = 10;
   return 0;
   return 0;
 }
 }

+ 10 - 2
explorer/testdata/operators/add.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 12
+// CHECK:STDOUT: 12
+// CHECK:STDOUT: 19
+// CHECK:STDOUT: 26
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -18,5 +21,10 @@ external impl A as Add {
 fn Main() -> i32 {
 fn Main() -> i32 {
   var a: A = {.n = 5};
   var a: A = {.n = 5};
   var b: A = {.n = 7};
   var b: A = {.n = 7};
-  return (a + b).n;
+  a = a + b;
+  Print("{0}", a.n);
+  a += b;
+  Print("{0}", a.n);
+  Print("{0}", (a + b).n);
+  return 0;
 }
 }

+ 27 - 0
explorer/testdata/operators/assign_builtin.carbon

@@ -0,0 +1,27 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: Before: 1
+// CHECK:STDOUT: Interface: 2
+// CHECK:STDOUT: Op: 3
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class C {
+  var n: i32;
+}
+
+fn Main() -> i32 {
+  var c: C = {.n = 1};
+  Print("Before: {0}", c.n);
+  c.(AssignWith({.n: i32}).Op)({.n = 2});
+  Print("Interface: {0}", c.n);
+  c = {.n = 3};
+  Print("Op: {0}", c.n);
+  return 0;
+}

+ 11 - 4
explorer/testdata/operators/bit_and.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 1
+// CHECK:STDOUT: 5
+// CHECK:STDOUT: 4
+// CHECK:STDOUT: 0
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -16,7 +19,11 @@ external impl A as BitAndWith(i32) where .Result = A {
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var a: A = {.n = 5};
-  a = a & 1;
-  return a.n;
+  var a: A = {.n = 13};
+  a = a & 7;
+  Print("{0}", a.n);
+  a &= -2;
+  Print("{0}", a.n);
+  Print("{0}", (a & 3).n);
+  return 0;
 }
 }

+ 11 - 4
explorer/testdata/operators/bit_or.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 5
+// CHECK:STDOUT: 3
+// CHECK:STDOUT: 7
+// CHECK:STDOUT: 15
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -16,7 +19,11 @@ external impl A as BitOrWith(i32) where .Result = A {
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var a: A = {.n = 4};
-  a = a | 1;
-  return a.n;
+  var a: A = {.n = 1};
+  a = a | 2;
+  Print("{0}", a.n);
+  a |= 5;
+  Print("{0}", a.n);
+  Print("{0}", (a | 12).n);
+  return 0;
 }
 }

+ 10 - 3
explorer/testdata/operators/bit_xor.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 4
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 0
+// CHECK:STDOUT: -1
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -17,6 +20,10 @@ external impl A as BitXorWith(i32) where .Result = A {
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   var a: A = {.n = 5};
   var a: A = {.n = 5};
-  a = a ^ 1;
-  return a.n;
+  a = a ^ 3;
+  Print("{0}", a.n);
+  a ^= 6;
+  Print("{0}", a.n);
+  Print("{0}", (a ^ -1).n);
+  return 0;
 }
 }

+ 10 - 3
explorer/testdata/operators/div.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 2
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 3
+// CHECK:STDOUT: 1
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -16,7 +19,11 @@ external impl A as DivWith(i32) where .Result = A {
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var a: A = {.n = 8};
+  var a: A = {.n = 19};
   a = a / 3;
   a = a / 3;
-  return a.n;
+  Print("{0}", a.n);
+  a /= 2;
+  Print("{0}", a.n);
+  Print("{0}", (a / 2).n);
+  return 0;
 }
 }

+ 48 - 0
explorer/testdata/operators/inc_dec.carbon

@@ -0,0 +1,48 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 5
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 5
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 5
+// CHECK:STDOUT: 6
+// CHECK:STDOUT: 5
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as Inc {
+  fn Op[addr self: Self*]() { ++self->n; }
+}
+external impl A as Dec {
+  fn Op[addr self: Self*]() { --self->n; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  ++a.n;
+  Print("{0}", a.n);
+  --a.n;
+  Print("{0}", a.n);
+  ++a;
+  Print("{0}", a.n);
+  --a;
+  Print("{0}", a.n);
+  a.n.(Inc.Op)();
+  Print("{0}", a.n);
+  a.n.(Dec.Op)();
+  Print("{0}", a.n);
+  a.(Inc.Op)();
+  Print("{0}", a.n);
+  a.(Dec.Op)();
+  Print("{0}", a.n);
+  return 0;
+}

+ 11 - 4
explorer/testdata/operators/mod.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 1
+// CHECK:STDOUT: 7
+// CHECK:STDOUT: 3
+// CHECK:STDOUT: 0
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -16,7 +19,11 @@ external impl A as ModWith(i32) where .Result = A {
 }
 }
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
-  var a: A = {.n = 5};
-  a = a % 2;
-  return a.n;
+  var a: A = {.n = 15};
+  a = a % 8;
+  Print("{0}", a.n);
+  a %= 4;
+  Print("{0}", a.n);
+  Print("{0}", (a % 3).n);
+  return 0;
 }
 }

+ 9 - 2
explorer/testdata/operators/mul.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 10
+// CHECK:STDOUT: 10
+// CHECK:STDOUT: 30
+// CHECK:STDOUT: 210
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -18,5 +21,9 @@ external impl A as MulWith(i32) where .Result = A {
 fn Main() -> i32 {
 fn Main() -> i32 {
   var a: A = {.n = 5};
   var a: A = {.n = 5};
   a = a * 2;
   a = a * 2;
-  return a.n;
+  Print("{0}", a.n);
+  a *= 3;
+  Print("{0}", a.n);
+  Print("{0}", (a * 7).n);
+  return 0;
 }
 }

+ 8 - 0
explorer/testdata/operators/shift.carbon

@@ -5,6 +5,8 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
+// CHECK:STDOUT: 8
+// CHECK:STDOUT: 4
 // CHECK:STDOUT: result: 0
 // CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
@@ -21,5 +23,11 @@ fn Main() -> i32 {
   if (not (-1 >> 1 == -1)) { return 9; }
   if (not (-1 >> 1 == -1)) { return 9; }
   if (not (-2 >> 1 == -1)) { return 10; }
   if (not (-2 >> 1 == -1)) { return 10; }
 
 
+  var n: i32 = 1;
+  n <<= 3;
+  Print("{0}", n);
+  n >>= 1;
+  Print("{0}", n);
+
   return 0;
   return 0;
 }
 }

+ 9 - 2
explorer/testdata/operators/sub.carbon

@@ -5,7 +5,10 @@
 // AUTOUPDATE
 // AUTOUPDATE
 // RUN: %{explorer-run}
 // RUN: %{explorer-run}
 // RUN: %{explorer-run-trace}
 // RUN: %{explorer-run-trace}
-// CHECK:STDOUT: result: 4
+// CHECK:STDOUT: 4
+// CHECK:STDOUT: 3
+// CHECK:STDOUT: 2
+// CHECK:STDOUT: result: 0
 
 
 package ExplorerTest api;
 package ExplorerTest api;
 
 
@@ -18,5 +21,9 @@ external impl A as SubWith(i32) where .Result = A {
 fn Main() -> i32 {
 fn Main() -> i32 {
   var a: A = {.n = 5};
   var a: A = {.n = 5};
   a = a - 1;
   a = a - 1;
-  return a.n;
+  Print("{0}", a.n);
+  a -= 1;
+  Print("{0}", a.n);
+  Print("{0}", (a - 1).n);
+  return 0;
 }
 }

+ 1 - 1
explorer/testdata/struct/fail_assign_different_types.carbon

@@ -10,7 +10,7 @@ package ExplorerTest api;
 
 
 fn Main() -> i32 {
 fn Main() -> i32 {
   var p: auto = {.x = 0, .y = 0};
   var p: auto = {.x = 0, .y = 0};
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/struct/fail_assign_different_types.carbon:[[@LINE+1]]: type error in assignment: '{.y: i32}' is not implicitly convertible to '{.x: i32, .y: i32}'
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/struct/fail_assign_different_types.carbon:[[@LINE+1]]: could not find implementation of interface AssignWith(U = {.y: i32}) for {.x: i32, .y: i32}
   p = {.y = 0};
   p = {.y = 0};
   return 0;
   return 0;
 }
 }

+ 16 - 0
explorer/testdata/unformed/static/fail_compound_assign.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_compound_assign.carbon:[[@LINE+1]]: use of uninitialized variable x
+  x += 1;
+  return x;
+}

+ 16 - 0
explorer/testdata/unformed/static/fail_increment.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_increment.carbon:[[@LINE+1]]: use of uninitialized variable x
+  ++x;
+  return x;
+}