Sfoglia il codice sorgente

Add support for bitwise operators. (#1809)

Following #1191, add initial support for bitwise operators. Support for both
integer operations and for operator overloading via the interfaces specified in
#1191 is provided.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 3 anni fa
parent
commit
ef96f60425

+ 6 - 16
common/fuzzing/carbon.proto

@@ -52,9 +52,14 @@ message OperatorExpression {
     Or = 9;
     Sub = 10;
     Ptr = 11;
-    Combine = 12;
+    BitwiseAnd = 12;
     As = 13;
     Mod = 14;
+    Complement = 15;
+    BitwiseOr = 16;
+    BitwiseXor = 17;
+    BitShiftLeft = 18;
+    BitShiftRight = 19;
   }
   optional Operator op = 1;
   repeated Expression arguments = 2;
@@ -85,20 +90,6 @@ message DesignatorExpression {
   optional string name = 1;
 }
 
-message IntrinsicExpression {
-  enum Intrinsic {
-    UnknownIntrinsic = 0;
-    Print = 1;
-    Alloc = 2;
-    Dealloc = 3;
-    Rand = 4;
-    IntEq = 5;
-    StrEq = 6;
-  }
-  optional Intrinsic intrinsic = 1;
-  optional TupleLiteralExpression argument = 2;
-}
-
 message IfExpression {
   optional Expression condition = 1;
   optional Expression then_expression = 2;
@@ -165,7 +156,6 @@ message Expression {
     StructLiteralExpression struct_literal = 7;
     StructTypeLiteralExpression struct_type_literal = 8;
     IdentifierExpression identifier = 9;
-    IntrinsicExpression intrinsic = 10;
     IfExpression if_expression = 11;
     BoolTypeLiteral bool_type_literal = 12;
     BoolLiteral bool_literal = 13;

+ 21 - 31
common/fuzzing/proto_to_carbon.cpp

@@ -154,9 +154,29 @@ static auto OperatorToCarbon(const Fuzzing::OperatorExpression& operator_expr,
       BinaryOperatorToCarbon(arg0, " or ", arg1, out);
       break;
 
-    case Fuzzing::OperatorExpression::Combine:
+    case Fuzzing::OperatorExpression::Complement:
+      PrefixUnaryOperatorToCarbon("^", arg0, out);
+      break;
+
+    case Fuzzing::OperatorExpression::BitwiseAnd:
       BinaryOperatorToCarbon(arg0, " & ", arg1, out);
       break;
+
+    case Fuzzing::OperatorExpression::BitwiseOr:
+      BinaryOperatorToCarbon(arg0, " | ", arg1, out);
+      break;
+
+    case Fuzzing::OperatorExpression::BitwiseXor:
+      BinaryOperatorToCarbon(arg0, " ^ ", arg1, out);
+      break;
+
+    case Fuzzing::OperatorExpression::BitShiftLeft:
+      BinaryOperatorToCarbon(arg0, " << ", arg1, out);
+      break;
+
+    case Fuzzing::OperatorExpression::BitShiftRight:
+      BinaryOperatorToCarbon(arg0, " >> ", arg1, out);
+      break;
   }
   out << ")";
 }
@@ -289,36 +309,6 @@ static auto ExpressionToCarbon(const Fuzzing::Expression& expression,
       break;
     }
 
-    case Fuzzing::Expression::kIntrinsic: {
-      const auto& intrinsic = expression.intrinsic();
-      switch (intrinsic.intrinsic()) {
-        case Fuzzing::IntrinsicExpression::UnknownIntrinsic:
-          // Arbitrary default to avoid getting invalid syntax.
-          out << "__intrinsic_print";
-          break;
-
-        case Fuzzing::IntrinsicExpression::Print:
-          out << "__intrinsic_print";
-          break;
-        case Fuzzing::IntrinsicExpression::Alloc:
-          out << "__intrinsic_new";
-          break;
-        case Fuzzing::IntrinsicExpression::Dealloc:
-          out << "__intrinsic_delete";
-          break;
-        case Fuzzing::IntrinsicExpression::Rand:
-          out << "__intrinsic_rand";
-          break;
-        case Fuzzing::IntrinsicExpression::IntEq:
-          out << "__intrinsic_int_eq";
-          break;
-        case Fuzzing::IntrinsicExpression::StrEq:
-          out << "__intrinsic_str_eq";
-          break;
-      }
-      TupleLiteralExpressionToCarbon(intrinsic.argument(), out);
-    } break;
-
     case Fuzzing::Expression::kIfExpression: {
       const auto& if_expression = expression.if_expression();
       out << "if ";

+ 47 - 28
explorer/ast/expression.cpp

@@ -32,6 +32,12 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name,
        {"delete", Intrinsic::Dealloc},
        {"rand", Intrinsic::Rand},
        {"int_eq", Intrinsic::IntEq},
+       {"int_bit_complement", Intrinsic::IntBitComplement},
+       {"int_bit_and", Intrinsic::IntBitAnd},
+       {"int_bit_or", Intrinsic::IntBitOr},
+       {"int_bit_xor", Intrinsic::IntBitXor},
+       {"int_left_shift", Intrinsic::IntLeftShift},
+       {"int_right_shift", Intrinsic::IntRightShift},
        {"str_eq", Intrinsic::StrEq}});
   name.remove_prefix(std::strlen("__intrinsic_"));
   auto it = intrinsic_map.find(name);
@@ -41,6 +47,36 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name,
   return it->second;
 }
 
+auto IntrinsicExpression::name() const -> std::string_view {
+  switch (intrinsic()) {
+    case IntrinsicExpression::Intrinsic::Print:
+      // TODO: Remove Print special casing once we have variadics or overloads.
+      return "Print";
+    case IntrinsicExpression::Intrinsic::Alloc:
+      return "__intrinsic_new";
+    case IntrinsicExpression::Intrinsic::Dealloc:
+      return "__intrinsic_delete";
+    case IntrinsicExpression::Intrinsic::Rand:
+      return "__intrinsic_rand";
+    case IntrinsicExpression::Intrinsic::IntEq:
+      return "__intrinsic_int_eq";
+    case IntrinsicExpression::Intrinsic::IntBitComplement:
+      return "__intrinsic_int_bit_complement";
+    case IntrinsicExpression::Intrinsic::IntBitAnd:
+      return "__intrinsic_int_bit_and";
+    case IntrinsicExpression::Intrinsic::IntBitOr:
+      return "__intrinsic_int_bit_or";
+    case IntrinsicExpression::Intrinsic::IntBitXor:
+      return "__intrinsic_int_bit_xor";
+    case IntrinsicExpression::Intrinsic::IntLeftShift:
+      return "__intrinsic_int_left_shift";
+    case IntrinsicExpression::Intrinsic::IntRightShift:
+      return "__intrinsic_int_right_shift";
+    case IntrinsicExpression::Intrinsic::StrEq:
+      return "__intrinsic_str_eq";
+  }
+}
+
 auto ExpressionFromParenContents(
     Nonnull<Arena*> arena, SourceLocation source_loc,
     const ParenContents<Expression>& paren_contents) -> Nonnull<Expression*> {
@@ -67,8 +103,17 @@ auto ToString(Operator op) -> std::string_view {
     case Operator::As:
       return "as";
     case Operator::AddressOf:
-    case Operator::Combine:
+    case Operator::BitwiseAnd:
       return "&";
+    case Operator::BitwiseOr:
+      return "|";
+    case Operator::BitwiseXor:
+    case Operator::Complement:
+      return "^";
+    case Operator::BitShiftLeft:
+      return "<<";
+    case Operator::BitShiftRight:
+      return ">>";
     case Operator::Neg:
     case Operator::Sub:
       return "-";
@@ -173,33 +218,7 @@ void Expression::Print(llvm::raw_ostream& out) const {
     }
     case ExpressionKind::IntrinsicExpression: {
       const auto& iexp = cast<IntrinsicExpression>(*this);
-      // TODO: Remove Print special casing once we have variadics or overloads.
-      if (iexp.intrinsic() == IntrinsicExpression::Intrinsic::Print) {
-        out << "Print" << iexp.args();
-        break;
-      }
-
-      out << "intrinsic_";
-      switch (iexp.intrinsic()) {
-        case IntrinsicExpression::Intrinsic::Print:
-          out << "print";
-          break;
-        case IntrinsicExpression::Intrinsic::Alloc:
-          out << "new";
-          break;
-        case IntrinsicExpression::Intrinsic::Dealloc:
-          out << "delete";
-          break;
-        case IntrinsicExpression::Intrinsic::Rand:
-          out << "rand";
-          break;
-        case IntrinsicExpression::Intrinsic::IntEq:
-          out << "int_eq";
-          break;
-        case IntrinsicExpression::Intrinsic::StrEq:
-          out << "str_eq";
-      }
-      out << iexp.args();
+      out << iexp.name() << iexp.args();
       break;
     }
     case ExpressionKind::IfExpression: {

+ 21 - 2
explorer/ast/expression.h

@@ -116,7 +116,12 @@ enum class Operator {
   AddressOf,
   And,
   As,
-  Combine,
+  BitwiseAnd,
+  BitwiseOr,
+  BitwiseXor,
+  BitShiftLeft,
+  BitShiftRight,
+  Complement,
   Deref,
   Eq,
   Mul,
@@ -664,7 +669,20 @@ class ValueLiteral : public Expression {
 
 class IntrinsicExpression : public Expression {
  public:
-  enum class Intrinsic { Print, Alloc, Dealloc, Rand, IntEq, StrEq };
+  enum class Intrinsic {
+    Print,
+    Alloc,
+    Dealloc,
+    Rand,
+    IntEq,
+    StrEq,
+    IntBitAnd,
+    IntBitOr,
+    IntBitXor,
+    IntBitComplement,
+    IntLeftShift,
+    IntRightShift,
+  };
 
   // Returns the enumerator corresponding to the intrinsic named `name`,
   // or raises a fatal compile error if there is no such enumerator.
@@ -682,6 +700,7 @@ class IntrinsicExpression : public Expression {
   }
 
   auto intrinsic() const -> Intrinsic { return intrinsic_; }
+  auto name() const -> std::string_view;
   auto args() const -> const TupleLiteral& { return *args_; }
   auto args() -> TupleLiteral& { return *args_; }
 

+ 143 - 29
explorer/data/prelude.carbon

@@ -4,6 +4,10 @@
 
 package Carbon api;
 
+// ----------------------
+// Conversion interfaces.
+// ----------------------
+
 // Explicitly convert `Self` to `T`.
 interface As(T:! Type) {
   fn Convert[me: Self]() -> T;
@@ -49,6 +53,43 @@ impl forall [U1:! Type, U2:! Type, U3:! Type,
   }
 }
 
+// ----------------------
+// Comparison interfaces.
+// ----------------------
+
+interface EqWith(U:! Type) {
+  fn Equal[me: Self](other: U) -> Bool;
+  // TODO: NotEqual with default impl
+}
+// TODO: constraint Eq { ... }
+
+
+// TODO: Simplify this once we have variadics
+impl forall [T2:! Type, U2:! Type, T1:! EqWith(T2), U1:! EqWith(U2)]
+    (T1, U1) as EqWith((T2, U2)) {
+  fn Equal[me: Self](other: (T2, U2)) -> Bool {
+    let (l1: T1, l2: U1) = me;
+    let (r1: T2, r2: U2) = other;
+    return l1 == r1 and l2 == r2;
+  }
+}
+
+impl i32 as EqWith(Self) {
+  fn Equal[me: Self](other: Self) -> Bool {
+    return __intrinsic_int_eq(me, other);
+  }
+}
+
+impl String as EqWith(Self) {
+  fn Equal[me: Self](other: Self) -> Bool {
+    return __intrinsic_str_eq(me, other);
+  }
+}
+
+// ----------------------
+// Arithmetic interfaces.
+// ----------------------
+
 interface Negate {
   // TODO: = Self
   let Result:! Type;
@@ -99,6 +140,108 @@ external impl i32 as MulWith(i32) where .Result == i32 {
 external impl i32 as ModWith(i32) where .Result == i32 {
   fn Op[me: i32](other: i32) -> i32 { return me % other; }
 }
+
+// ---------------------------------
+// Bitwise and bit-shift interfaces.
+// ---------------------------------
+
+// Unary `^`.
+interface BitComplement {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self]() -> Result;
+}
+
+// Binary `&`.
+interface BitAndWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO:
+// constraint BitAnd {
+//   extends BitAndWith(Self) where .Result == Self;
+// }
+
+// Binary `|`.
+interface BitOrWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO:
+// constraint BitOr {
+//   extends BitOrWith(Self) where .Result == Self;
+// }
+
+// Binary `^`.
+interface BitXorWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO:
+// constraint BitXor {
+//   extends BitXorWith(Self) where .Result == Self;
+// }
+
+// Binary `<<`.
+interface LeftShiftWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO:
+// constraint LeftShift {
+//   extends LeftShiftWith(Self) where .Result == Self;
+// }
+
+// Binary `>>`.
+interface RightShiftWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO:
+// constraint RightShift {
+//   extends RightShiftWith(Self) where .Result == Self;
+// }
+
+external impl i32 as BitComplement where .Result == i32 {
+  fn Op[me: i32]() -> i32 {
+    return __intrinsic_int_bit_complement(me);
+  }
+}
+external impl i32 as BitAndWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 {
+    return __intrinsic_int_bit_and(me, other);
+  }
+}
+external impl i32 as BitOrWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 {
+    return __intrinsic_int_bit_or(me, other);
+  }
+}
+external impl i32 as BitXorWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 {
+    return __intrinsic_int_bit_xor(me, other);
+  }
+}
+external impl i32 as LeftShiftWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 {
+    return __intrinsic_int_left_shift(me, other);
+  }
+}
+external impl i32 as RightShiftWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 {
+    return __intrinsic_int_right_shift(me, other);
+  }
+}
+
+// ------------------------
+// Miscellaneous utilities.
+// ------------------------
+
 // Note that Print is experimental, and not part of an accepted proposal, but
 // is included here for printing state in tests.
 // TODO: Remove Print special casing once we have variadics or overloads.
@@ -120,32 +263,3 @@ class Heap {
 }
 
 var heap: Heap = {};
-
-interface EqWith(U:! Type) {
-  fn Equal[me: Self](other: U) -> Bool;
-  // TODO: NotEqual with default impl
-}
-// TODO: constraint Eq { ... }
-
-
-// TODO: Simplify this once we have variadics
-impl forall [T2:! Type, U2:! Type, T1:! EqWith(T2), U1:! EqWith(U2)]
-    (T1, U1) as EqWith((T2, U2)) {
-  fn Equal[me: Self](other: (T2, U2)) -> Bool {
-    let (l1: T1, l2: U1) = me;
-    let (r1: T2, r2: U2) = other;
-    return l1 == r1 and l2 == r2;
-  }
-}
-
-impl i32 as EqWith(Self) {
-  fn Equal[me: Self](other: Self) -> Bool {
-    return __intrinsic_int_eq(me, other);
-  }
-}
-
-impl String as EqWith(Self) {
-  fn Equal[me: Self](other: Self) -> Bool {
-    return __intrinsic_str_eq(me, other);
-  }
-}

+ 16 - 25
explorer/fuzzing/ast_to_proto.cpp

@@ -61,8 +61,18 @@ static auto OperatorToProtoEnum(const Operator op)
       return Fuzzing::OperatorExpression::Or;
     case Operator::Sub:
       return Fuzzing::OperatorExpression::Sub;
-    case Operator::Combine:
-      return Fuzzing::OperatorExpression::Combine;
+    case Operator::BitwiseAnd:
+      return Fuzzing::OperatorExpression::BitwiseAnd;
+    case Operator::BitwiseOr:
+      return Fuzzing::OperatorExpression::BitwiseOr;
+    case Operator::BitwiseXor:
+      return Fuzzing::OperatorExpression::BitwiseXor;
+    case Operator::BitShiftLeft:
+      return Fuzzing::OperatorExpression::BitShiftLeft;
+    case Operator::BitShiftRight:
+      return Fuzzing::OperatorExpression::BitShiftRight;
+    case Operator::Complement:
+      return Fuzzing::OperatorExpression::Complement;
   }
 }
 
@@ -228,29 +238,10 @@ static auto ExpressionToProto(const Expression& expression)
 
     case ExpressionKind::IntrinsicExpression: {
       const auto& intrinsic = cast<IntrinsicExpression>(expression);
-      auto* intrinsic_proto = expression_proto.mutable_intrinsic();
-      switch (intrinsic.intrinsic()) {
-        case IntrinsicExpression::Intrinsic::Print:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Print);
-          break;
-        case IntrinsicExpression::Intrinsic::Alloc:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Alloc);
-          break;
-        case IntrinsicExpression::Intrinsic::Dealloc:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Dealloc);
-          break;
-        case IntrinsicExpression::Intrinsic::Rand:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::Rand);
-          break;
-        case IntrinsicExpression::Intrinsic::IntEq:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::IntEq);
-          break;
-        case IntrinsicExpression::Intrinsic::StrEq:
-          intrinsic_proto->set_intrinsic(Fuzzing::IntrinsicExpression::StrEq);
-          break;
-      }
-      *intrinsic_proto->mutable_argument() =
-          TupleLiteralExpressionToProto(intrinsic.args());
+      auto* call_proto = expression_proto.mutable_call();
+      call_proto->mutable_function()->mutable_identifier()->set_name(
+          std::string(intrinsic.name()));
+      *call_proto->mutable_argument() = ExpressionToProto(intrinsic.args());
       break;
     }
 

+ 22 - 7
explorer/interpreter/builtins.h

@@ -27,6 +27,9 @@ class Builtins {
     As,
     ImplicitAs,
 
+    // Comparison.
+    EqWith,
+
     // Arithmetic.
     Negate,
     AddWith,
@@ -34,20 +37,31 @@ class Builtins {
     MulWith,
     ModWith,
 
-    // Comparison.
-    EqWith,
+    // Bitwise and shift.
+    BitComplement,
+    BitAndWith,
+    BitOrWith,
+    BitXorWith,
+    LeftShiftWith,
+    RightShiftWith,
 
-    Last = EqWith
+    Last = RightShiftWith
   };
   // TODO: In C++20, replace with `using enum Builtin;`.
   static constexpr Builtin As = Builtin::As;
   static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
+  static constexpr Builtin EqWith = Builtin::EqWith;
   static constexpr Builtin Negate = Builtin::Negate;
   static constexpr Builtin AddWith = Builtin::AddWith;
   static constexpr Builtin SubWith = Builtin::SubWith;
   static constexpr Builtin MulWith = Builtin::MulWith;
   static constexpr Builtin ModWith = Builtin::ModWith;
-  static constexpr Builtin EqWith = Builtin::EqWith;
+  static constexpr Builtin BitComplement = Builtin::BitComplement;
+  static constexpr Builtin BitAndWith = Builtin::BitAndWith;
+  static constexpr Builtin BitOrWith = Builtin::BitOrWith;
+  static constexpr Builtin BitXorWith = Builtin::BitXorWith;
+  static constexpr Builtin LeftShiftWith = Builtin::LeftShiftWith;
+  static constexpr Builtin RightShiftWith = Builtin::RightShiftWith;
 
   // Register a declaration that might be a builtin.
   void Register(Nonnull<const Declaration*> decl);
@@ -63,9 +77,10 @@ class Builtins {
 
  private:
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
-  static constexpr std::array<std::string_view, NumBuiltins> BuiltinNames = {
-      "As",      "ImplicitAs", "Negate",  "AddWith",
-      "SubWith", "MulWith",    "ModWith", "EqWith"};
+  static constexpr const char* BuiltinNames[NumBuiltins] = {
+      "As",        "ImplicitAs", "EqWith",        "Negate",        "AddWith",
+      "SubWith",   "MulWith",    "ModWith",       "BitComplement", "BitAndWith",
+      "BitOrWith", "BitXorWith", "LeftShiftWith", "RightShiftWith"};
 
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
 };

+ 59 - 22
explorer/interpreter/interpreter.cpp

@@ -209,12 +209,18 @@ auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
       return heap_.Read(cast<PointerValue>(*args[0]).address(), source_loc);
     case Operator::AddressOf:
       return arena_->New<PointerValue>(cast<LValue>(*args[0]).address());
-    case Operator::Combine:
+    case Operator::BitwiseAnd:
+      // If & wasn't rewritten, it's being used to form a constraint.
       return &cast<TypeOfConstraintType>(static_type)->constraint_type();
     case Operator::As:
     case Operator::Eq:
-      CARBON_FATAL() << "These operators should have been rewritten to "
-                        "interface method calls";
+    case Operator::BitwiseOr:
+    case Operator::BitwiseXor:
+    case Operator::BitShiftLeft:
+    case Operator::BitShiftRight:
+    case Operator::Complement:
+      CARBON_FATAL() << "operator " << ToString(op)
+                     << " should always be rewritten";
   }
 }
 
@@ -1166,19 +1172,9 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
             std::make_unique<ExpressionAction>(&intrinsic.args()));
       }
       // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H}
+      const auto& args = cast<TupleValue>(*act.results()[0]).elements();
       switch (cast<IntrinsicExpression>(exp).intrinsic()) {
-        case IntrinsicExpression::Intrinsic::Rand: {
-          const auto& args = cast<TupleValue>(*act.results()[0]).elements();
-          CARBON_CHECK(args.size() == 2);
-
-          const auto& low = cast<IntValue>(*args[0]).value();
-          const auto& high = cast<IntValue>(*args[1]).value();
-          std::uniform_int_distribution<> distr(low, high);
-          int r = distr(generator);
-          return todo_.FinishAction(arena_->New<IntValue>(r));
-        }
         case IntrinsicExpression::Intrinsic::Print: {
-          const auto& args = cast<TupleValue>(*act.results()[0]).elements();
           CARBON_ASSIGN_OR_RETURN(
               Nonnull<const Value*> format_string_value,
               Convert(args[0], arena_->New<StringType>(), exp.source_loc()));
@@ -1200,19 +1196,24 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           return todo_.FinishAction(TupleValue::Empty());
         }
         case IntrinsicExpression::Intrinsic::Alloc: {
-          const auto& args = cast<TupleValue>(*act.results()[0]);
-          CARBON_CHECK(args.elements().size() == 1);
-          Address addr(heap_.AllocateValue(args.elements()[0]));
+          CARBON_CHECK(args.size() == 1);
+          Address addr(heap_.AllocateValue(args[0]));
           return todo_.FinishAction(arena_->New<PointerValue>(addr));
         }
         case IntrinsicExpression::Intrinsic::Dealloc: {
-          const auto& args = cast<TupleValue>(*act.results()[0]);
-          CARBON_CHECK(args.elements().size() == 1);
-          heap_.Deallocate(cast<PointerValue>(args.elements()[0])->address());
+          CARBON_CHECK(args.size() == 1);
+          heap_.Deallocate(cast<PointerValue>(args[0])->address());
           return todo_.FinishAction(TupleValue::Empty());
         }
+        case IntrinsicExpression::Intrinsic::Rand: {
+          CARBON_CHECK(args.size() == 2);
+          const auto& low = cast<IntValue>(*args[0]).value();
+          const auto& high = cast<IntValue>(*args[1]).value();
+          std::uniform_int_distribution<> distr(low, high);
+          int r = distr(generator);
+          return todo_.FinishAction(arena_->New<IntValue>(r));
+        }
         case IntrinsicExpression::Intrinsic::IntEq: {
-          const auto& args = cast<TupleValue>(*act.results()[0]).elements();
           CARBON_CHECK(args.size() == 2);
           auto lhs = cast<IntValue>(*args[0]).value();
           auto rhs = cast<IntValue>(*args[1]).value();
@@ -1220,13 +1221,49 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           return todo_.FinishAction(result);
         }
         case IntrinsicExpression::Intrinsic::StrEq: {
-          const auto& args = cast<TupleValue>(*act.results()[0]).elements();
           CARBON_CHECK(args.size() == 2);
           auto& lhs = cast<StringValue>(*args[0]).value();
           auto& rhs = cast<StringValue>(*args[1]).value();
           auto result = arena_->New<BoolValue>(lhs == rhs);
           return todo_.FinishAction(result);
         }
+        case IntrinsicExpression::Intrinsic::IntBitComplement: {
+          CARBON_CHECK(args.size() == 1);
+          return todo_.FinishAction(
+              arena_->New<IntValue>(~cast<IntValue>(*args[0]).value()));
+        }
+        case IntrinsicExpression::Intrinsic::IntBitAnd: {
+          CARBON_CHECK(args.size() == 2);
+          return todo_.FinishAction(
+              arena_->New<IntValue>(cast<IntValue>(*args[0]).value() &
+                                    cast<IntValue>(*args[1]).value()));
+        }
+        case IntrinsicExpression::Intrinsic::IntBitOr: {
+          CARBON_CHECK(args.size() == 2);
+          return todo_.FinishAction(
+              arena_->New<IntValue>(cast<IntValue>(*args[0]).value() |
+                                    cast<IntValue>(*args[1]).value()));
+        }
+        case IntrinsicExpression::Intrinsic::IntBitXor: {
+          CARBON_CHECK(args.size() == 2);
+          return todo_.FinishAction(
+              arena_->New<IntValue>(cast<IntValue>(*args[0]).value() ^
+                                    cast<IntValue>(*args[1]).value()));
+        }
+        case IntrinsicExpression::Intrinsic::IntLeftShift: {
+          CARBON_CHECK(args.size() == 2);
+          // TODO: Runtime error if RHS is too large.
+          return todo_.FinishAction(arena_->New<IntValue>(
+              static_cast<uint32_t>(cast<IntValue>(*args[0]).value())
+              << cast<IntValue>(*args[1]).value()));
+        }
+        case IntrinsicExpression::Intrinsic::IntRightShift: {
+          CARBON_CHECK(args.size() == 2);
+          // TODO: Runtime error if RHS is too large.
+          return todo_.FinishAction(
+              arena_->New<IntValue>(cast<IntValue>(*args[0]).value() >>
+                                    cast<IntValue>(*args[1]).value()));
+        }
       }
     }
     case ExpressionKind::IntTypeLiteral: {

+ 113 - 57
explorer/interpreter/type_checker.cpp

@@ -1944,17 +1944,23 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         ts.push_back(&argument->static_type());
       }
 
-      auto handle_binary_arithmetic =
+      auto handle_unary_operator =
           [&](Builtins::Builtin builtin) -> ErrorOr<Success> {
-        // Handle a built-in operator first.
-        if (isa<IntType>(ts[0]) && isa<IntType>(ts[1]) &&
-            IsSameType(ts[0], ts[1], impl_scope)) {
-          op.set_static_type(ts[0]);
-          op.set_value_category(ValueCategory::Let);
-          return Success();
+        ErrorOr<Nonnull<Expression*>> result = BuildBuiltinMethodCall(
+            impl_scope, op.arguments()[0], BuiltinInterfaceName{builtin},
+            BuiltinMethodCall{"Op"});
+        if (!result.ok()) {
+          // We couldn't find a matching `impl`.
+          return CompilationError(e->source_loc())
+                 << "type error in `" << ToString(op.op()) << "`:\n"
+                 << result.error().message();
         }
+        op.set_rewritten_form(*result);
+        return Success();
+      };
 
-        // Now try an overloaded operator.
+      auto handle_binary_operator =
+          [&](Builtins::Builtin builtin) -> ErrorOr<Success> {
         ErrorOr<Nonnull<Expression*>> result = BuildBuiltinMethodCall(
             impl_scope, op.arguments()[0], BuiltinInterfaceName{builtin, ts[1]},
             BuiltinMethodCall{"Op", {op.arguments()[1]}});
@@ -1968,26 +1974,32 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         return Success();
       };
 
+      auto handle_binary_arithmetic =
+          [&](Builtins::Builtin builtin) -> ErrorOr<Success> {
+        // Handle a built-in operator first.
+        // TODO: Replace this with an intrinsic.
+        if (isa<IntType>(ts[0]) && isa<IntType>(ts[1]) &&
+            IsSameType(ts[0], ts[1], impl_scope)) {
+          op.set_static_type(ts[0]);
+          op.set_value_category(ValueCategory::Let);
+          return Success();
+        }
+
+        // Now try an overloaded operator.
+        return handle_binary_operator(builtin);
+      };
+
       switch (op.op()) {
         case Operator::Neg: {
           // Handle a built-in negation first.
+          // TODO: Replace this with an intrinsic.
           if (isa<IntType>(ts[0])) {
             op.set_static_type(arena_->New<IntType>());
             op.set_value_category(ValueCategory::Let);
             return Success();
           }
           // Now try an overloaded negation.
-          ErrorOr<Nonnull<Expression*>> result = BuildBuiltinMethodCall(
-              impl_scope, op.arguments()[0],
-              BuiltinInterfaceName{Builtins::Negate}, BuiltinMethodCall{"Op"});
-          if (!result.ok()) {
-            // We couldn't find a matching `impl`.
-            return CompilationError(e->source_loc())
-                   << "type error in `" << ToString(op.op()) << "`:\n"
-                   << result.error().message();
-          }
-          op.set_rewritten_form(*result);
-          return Success();
+          return handle_unary_operator(Builtins::Negate);
         }
         case Operator::Add:
           return handle_binary_arithmetic(Builtins::AddWith);
@@ -1997,6 +2009,42 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           return handle_binary_arithmetic(Builtins::MulWith);
         case Operator::Mod:
           return handle_binary_arithmetic(Builtins::ModWith);
+        case Operator::BitwiseAnd:
+          // `&` between type-of-types performs constraint combination.
+          // TODO: Should this be done via an intrinsic?
+          if (IsTypeOfType(ts[0]) && IsTypeOfType(ts[1])) {
+            std::optional<Nonnull<const ConstraintType*>> constraints[2];
+            for (int i : {0, 1}) {
+              if (auto* iface_type_type =
+                      dyn_cast<TypeOfInterfaceType>(ts[i])) {
+                constraints[i] = MakeConstraintForInterface(
+                    e->source_loc(), &iface_type_type->interface_type());
+              } else if (auto* constraint_type_type =
+                             dyn_cast<TypeOfConstraintType>(ts[i])) {
+                constraints[i] = &constraint_type_type->constraint_type();
+              } else {
+                return CompilationError(op.arguments()[i]->source_loc())
+                       << "argument to " << ToString(op.op())
+                       << " should be a constraint, found `" << *ts[i] << "`";
+              }
+            }
+            op.set_static_type(
+                arena_->New<TypeOfConstraintType>(CombineConstraints(
+                    e->source_loc(), {*constraints[0], *constraints[1]})));
+            op.set_value_category(ValueCategory::Let);
+            return Success();
+          }
+          return handle_binary_operator(Builtins::BitAndWith);
+        case Operator::BitwiseOr:
+          return handle_binary_operator(Builtins::BitOrWith);
+        case Operator::BitwiseXor:
+          return handle_binary_operator(Builtins::BitXorWith);
+        case Operator::BitShiftLeft:
+          return handle_binary_operator(Builtins::LeftShiftWith);
+        case Operator::BitShiftRight:
+          return handle_binary_operator(Builtins::RightShiftWith);
+        case Operator::Complement:
+          return handle_unary_operator(Builtins::BitComplement);
         case Operator::And:
           CARBON_RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "&&(1)",
                                                  arena_->New<BoolType>(), ts[0],
@@ -2060,27 +2108,6 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           op.set_static_type(arena_->New<PointerType>(ts[0]));
           op.set_value_category(ValueCategory::Let);
           return Success();
-        case Operator::Combine: {
-          std::optional<Nonnull<const ConstraintType*>> constraints[2];
-          for (int i : {0, 1}) {
-            if (auto* iface_type_type = dyn_cast<TypeOfInterfaceType>(ts[i])) {
-              constraints[i] = MakeConstraintForInterface(
-                  e->source_loc(), &iface_type_type->interface_type());
-            } else if (auto* constraint_type_type =
-                           dyn_cast<TypeOfConstraintType>(ts[i])) {
-              constraints[i] = &constraint_type_type->constraint_type();
-            } else {
-              return CompilationError(op.arguments()[i]->source_loc())
-                     << "argument to " << ToString(op.op())
-                     << " should be a constraint, found `" << *ts[i] << "`";
-            }
-          }
-          op.set_static_type(
-              arena_->New<TypeOfConstraintType>(CombineConstraints(
-                  e->source_loc(), {*constraints[0], *constraints[1]})));
-          op.set_value_category(ValueCategory::Let);
-          return Success();
-        }
         case Operator::As: {
           CARBON_ASSIGN_OR_RETURN(
               Nonnull<const Value*> type,
@@ -2211,23 +2238,6 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       CARBON_RETURN_IF_ERROR(TypeCheckExp(&intrinsic_exp.args(), impl_scope));
       const auto& args = intrinsic_exp.args().fields();
       switch (cast<IntrinsicExpression>(*e).intrinsic()) {
-        case IntrinsicExpression::Intrinsic::Rand: {
-          if (args.size() != 2) {
-            return CompilationError(e->source_loc())
-                   << "Rand takes 2 arguments, received " << args.size();
-          }
-          CARBON_RETURN_IF_ERROR(ExpectExactType(
-              e->source_loc(), "Rand argument 0", arena_->New<IntType>(),
-              &args[0]->static_type(), impl_scope));
-
-          CARBON_RETURN_IF_ERROR(ExpectExactType(
-              e->source_loc(), "Rand argument 1", arena_->New<IntType>(),
-              &args[1]->static_type(), impl_scope));
-
-          e->set_static_type(arena_->New<IntType>());
-
-          return Success();
-        }
         case IntrinsicExpression::Intrinsic::Print:
           // TODO: Remove Print special casing once we have variadics or
           // overloads. Here, that's the name Print instead of __intrinsic_print
@@ -2269,6 +2279,23 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
+        case IntrinsicExpression::Intrinsic::Rand: {
+          if (args.size() != 2) {
+            return CompilationError(e->source_loc())
+                   << "Rand takes 2 arguments, received " << args.size();
+          }
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "Rand argument 0", arena_->New<IntType>(),
+              &args[0]->static_type(), impl_scope));
+
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "Rand argument 1", arena_->New<IntType>(),
+              &args[1]->static_type(), impl_scope));
+
+          e->set_static_type(arena_->New<IntType>());
+
+          return Success();
+        }
         case IntrinsicExpression::Intrinsic::IntEq: {
           if (args.size() != 2) {
             return CompilationError(e->source_loc())
@@ -2299,6 +2326,35 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
+        case IntrinsicExpression::Intrinsic::IntBitComplement:
+          if (args.size() != 1) {
+            return CompilationError(e->source_loc())
+                   << intrinsic_exp.name() << " takes 1 argument";
+          }
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "complement argument", arena_->New<IntType>(),
+              &args[0]->static_type(), impl_scope));
+          e->set_static_type(arena_->New<IntType>());
+          e->set_value_category(ValueCategory::Let);
+          return Success();
+        case IntrinsicExpression::Intrinsic::IntBitAnd:
+        case IntrinsicExpression::Intrinsic::IntBitOr:
+        case IntrinsicExpression::Intrinsic::IntBitXor:
+        case IntrinsicExpression::Intrinsic::IntLeftShift:
+        case IntrinsicExpression::Intrinsic::IntRightShift:
+          if (args.size() != 2) {
+            return CompilationError(e->source_loc())
+                   << intrinsic_exp.name() << " takes 2 arguments";
+          }
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "argument 1", arena_->New<IntType>(),
+              &args[0]->static_type(), impl_scope));
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "argument 2", arena_->New<IntType>(),
+              &args[1]->static_type(), impl_scope));
+          e->set_static_type(arena_->New<IntType>());
+          e->set_value_category(ValueCategory::Let);
+          return Success();
       }
     }
     case ExpressionKind::IntTypeLiteral:

+ 10 - 2
explorer/syntax/lexer.lpp

@@ -42,6 +42,7 @@ AUTO                 "auto"
 AWAIT                "__await"
 BOOL                 "Bool"
 BREAK                "break"
+CARET                "^"
 CASE                 "case"
 CHOICE               "choice"
 CLASS                "class"
@@ -61,6 +62,7 @@ FALSE                "false"
 FN                   "fn"
 FN_TYPE              "__Fn"
 FORALL               "forall"
+GREATER_GREATER      ">>"
 IF                   "if"
 IMPL                 "impl"
 IMPORT               "import"
@@ -69,15 +71,17 @@ IS                   "is"
 LEFT_CURLY_BRACE     "{"
 LEFT_PARENTHESIS     "("
 LEFT_SQUARE_BRACKET  "["
+LESS_LESS            "<<"
 LET                  "let"
 LIBRARY              "library"
 MATCH                "match"
 MINUS                "-"
-MODULO               "%"
 NOT                  "not"
 OR                   "or"
 PACKAGE              "package"
+PERCENT              "%"
 PERIOD               "."
+PIPE                 "|"
 PLUS                 "+"
 RETURN               "return"
 RETURNED             "returned"
@@ -131,6 +135,7 @@ operand_start         [(A-Za-z0-9_\"]
 {AWAIT}               { return CARBON_SIMPLE_TOKEN(AWAIT);               }
 {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);               }
@@ -150,6 +155,7 @@ operand_start         [(A-Za-z0-9_\"]
 {FN_TYPE}             { return CARBON_SIMPLE_TOKEN(FN_TYPE);             }
 {FN}                  { return CARBON_SIMPLE_TOKEN(FN);                  }
 {FORALL}              { return CARBON_SIMPLE_TOKEN(FORALL);              }
+{GREATER_GREATER}     { return CARBON_SIMPLE_TOKEN(GREATER_GREATER);     }
 {IF}                  { return CARBON_SIMPLE_TOKEN(IF);                  }
 {IMPL}                { return CARBON_SIMPLE_TOKEN(IMPL);                }
 {IMPORT}              { return CARBON_SIMPLE_TOKEN(IMPORT);              }
@@ -158,15 +164,17 @@ operand_start         [(A-Za-z0-9_\"]
 {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_LESS}           { return CARBON_SIMPLE_TOKEN(LESS_LESS);           }
 {LET}                 { return CARBON_SIMPLE_TOKEN(LET);                 }
 {LIBRARY}             { return CARBON_SIMPLE_TOKEN(LIBRARY);             }
 {MATCH}               { return CARBON_SIMPLE_TOKEN(MATCH);               }
 {MINUS}               { return CARBON_SIMPLE_TOKEN(MINUS);               }
-{MODULO}              { return CARBON_SIMPLE_TOKEN(MODULO);              }
 {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);              }

+ 106 - 44
explorer/syntax/parser.ypp

@@ -119,20 +119,26 @@
 %type <Nonnull<Expression*>> primary_expression
 %type <Nonnull<Expression*>> postfix_expression
 %type <Nonnull<Expression*>> ref_deref_expression
-%type <Nonnull<Expression*>> combine_lhs
-%type <Nonnull<Expression*>> combine_expression
 %type <Nonnull<Expression*>> type_expression
 %type <Nonnull<Expression*>> fn_type_expression
 %type <Nonnull<Expression*>> minus_expression
-%type <Nonnull<Expression*>> multiplicative_operand
+%type <Nonnull<Expression*>> complement_expression
+%type <Nonnull<Expression*>> unary_expression
+%type <Nonnull<Expression*>> simple_binary_operand
 %type <Nonnull<Expression*>> multiplicative_lhs
 %type <Nonnull<Expression*>> multiplicative_expression
 %type <Nonnull<Expression*>> additive_operand
 %type <Nonnull<Expression*>> additive_lhs
 %type <Nonnull<Expression*>> additive_expression
-%type <Nonnull<Expression*>> modulo_operand
 %type <Nonnull<Expression*>> modulo_expression
-%type <Nonnull<Expression*>> as_operand
+%type <Nonnull<Expression*>> bitwise_and_lhs
+%type <Nonnull<Expression*>> bitwise_and_expression
+%type <Nonnull<Expression*>> bitwise_or_lhs
+%type <Nonnull<Expression*>> bitwise_or_expression
+%type <Nonnull<Expression*>> bitwise_xor_lhs
+%type <Nonnull<Expression*>> bitwise_xor_expression
+%type <Nonnull<Expression*>> bitwise_expression
+%type <Nonnull<Expression*>> bit_shift_expression
 %type <Nonnull<Expression*>> as_expression
 %type <Nonnull<Expression*>> unimpl_expression
 %type <Nonnull<Expression*>> value_expression
@@ -197,6 +203,7 @@
   AWAIT
   BOOL
   BREAK
+  CARET
   CASE
   CHOICE
   CLASS
@@ -216,6 +223,7 @@
   FN
   FN_TYPE
   FORALL
+  GREATER_GREATER
   IF
   IMPL
   IMPORT
@@ -224,15 +232,17 @@
   LEFT_CURLY_BRACE
   LEFT_PARENTHESIS
   LEFT_SQUARE_BRACKET
+  LESS_LESS
   LET
   LIBRARY
   MATCH
   MINUS
-  MODULO
   NOT
   OR
   PACKAGE
+  PERCENT
   PERIOD
+  PIPE
   PLUS
   RETURN
   RETURNED
@@ -406,21 +416,9 @@ fn_type_expression:
   FN_TYPE tuple ARROW type_expression
     { $$ = arena->New<FunctionTypeLiteral>(context.source_loc(), $2, $4); }
 ;
-combine_lhs:
-  ref_deref_expression
-| combine_expression
-;
-combine_expression:
-  combine_lhs AMPERSAND ref_deref_expression
-    {
-      $$ = arena->New<OperatorExpression>(
-          context.source_loc(), Operator::Combine,
-          std::vector<Nonnull<Expression*>>({$1, $3}));
-    }
-;
 type_expression:
   ref_deref_expression
-| combine_expression
+| bitwise_and_expression
 | fn_type_expression
 ;
 minus_expression:
@@ -432,41 +430,44 @@ minus_expression:
           std::vector<Nonnull<Expression*>>({$2}));
     }
 ;
-multiplicative_operand:
+complement_expression:
+  // ref_deref_expression excluded due to precedence diamond.
+  CARET ref_deref_expression
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::Complement,
+          std::vector<Nonnull<Expression*>>({$2}));
+    }
+;
+unary_expression:
+  // ref_deref_expression excluded due to precedence diamond.
+  minus_expression
+| complement_expression
+;
+// A simple_binary_operand is an operand of a binary operator
+// that is not itself a binary operator expression.
+simple_binary_operand:
   ref_deref_expression
-| minus_expression
+| unary_expression
 ;
 multiplicative_lhs:
-  ref_deref_expression
+  simple_binary_operand
 | multiplicative_expression
 ;
 multiplicative_expression:
-  minus_expression
-| multiplicative_lhs BINARY_STAR multiplicative_operand
+  multiplicative_lhs BINARY_STAR simple_binary_operand
     {
       $$ = arena->New<OperatorExpression>(
           context.source_loc(), Operator::Mul,
           std::vector<Nonnull<Expression*>>({$1, $3}));
     }
 ;
-modulo_operand:
-  ref_deref_expression
-| minus_expression
-;
-modulo_expression:
-  modulo_operand MODULO modulo_operand
-    {
-      $$ = arena->New<OperatorExpression>(
-          context.source_loc(), Operator::Mod,
-          std::vector<Nonnull<Expression*>>({$1, $3}));
-    }
-;
 additive_operand:
-  ref_deref_expression
+  simple_binary_operand
 | multiplicative_expression
 ;
 additive_lhs:
-  ref_deref_expression
+  simple_binary_operand
 | additive_expression
 ;
 additive_expression:
@@ -484,12 +485,71 @@ additive_expression:
           std::vector<Nonnull<Expression*>>({$1, $3}));
     }
 ;
-as_operand:
-  minus_expression
-| ref_deref_expression
+modulo_expression:
+  simple_binary_operand PERCENT simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::Mod,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+;
+bitwise_and_lhs:
+  simple_binary_operand
+| bitwise_and_expression
+;
+bitwise_and_expression:
+  bitwise_and_lhs AMPERSAND simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::BitwiseAnd,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+;
+bitwise_or_lhs:
+  simple_binary_operand
+| bitwise_or_expression
+;
+bitwise_or_expression:
+  bitwise_or_lhs PIPE simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::BitwiseOr,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+;
+bitwise_xor_lhs:
+  simple_binary_operand
+| bitwise_xor_expression
+;
+bitwise_xor_expression:
+  bitwise_xor_lhs CARET simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::BitwiseXor,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+;
+bitwise_expression:
+  bitwise_and_expression
+| bitwise_or_expression
+| bitwise_xor_expression
+;
+bit_shift_expression:
+  simple_binary_operand LESS_LESS simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::BitShiftLeft,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
+| simple_binary_operand GREATER_GREATER simple_binary_operand
+    {
+      $$ = arena->New<OperatorExpression>(
+          context.source_loc(), Operator::BitShiftRight,
+          std::vector<Nonnull<Expression*>>({$1, $3}));
+    }
 ;
 as_expression:
-  as_operand AS as_operand
+  simple_binary_operand AS simple_binary_operand
     {
       $$ = arena->New<OperatorExpression>(
           context.source_loc(), Operator::As,
@@ -507,10 +567,12 @@ unimpl_expression:
 value_expression:
   // ref_deref_expression excluded due to precedence diamond.
   additive_expression
-| modulo_expression
 | as_expression
-| combine_expression
+| bitwise_expression
+| bit_shift_expression
 | fn_type_expression
+| modulo_expression
+| unary_expression
 | unimpl_expression
 ;
 comparison_operand:

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

@@ -12,7 +12,7 @@ package ExplorerTest api;
 
 fn Main() -> i32 {
   // error
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL or SEMICOLON
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting MINUS or PLUS
   x : i32;
   return 1;
 }

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

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

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

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

+ 24 - 0
explorer/testdata/operators/bit_and.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as BitAndWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n & rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a & 1;
+  return a.n;
+}

+ 24 - 0
explorer/testdata/operators/bit_complement.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: -6
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as BitComplement where .Result == A {
+  fn Op[me: Self]() -> A { return {.n = ^me.n}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = ^a;
+  return a.n;
+}

+ 24 - 0
explorer/testdata/operators/bit_or.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 5
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as BitOrWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n | rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 4};
+  a = a | 1;
+  return a.n;
+}

+ 24 - 0
explorer/testdata/operators/bit_xor.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 4
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as BitXorWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n ^ rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a ^ 1;
+  return a.n;
+}

+ 32 - 0
explorer/testdata/operators/bitwise.carbon

@@ -0,0 +1,32 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 0
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  if (not (^0 == -1)) { return 11; }
+  if (not (^1 == -2)) { return 12; }
+  if (not (^(-3) == 2)) { return 13; }
+
+  if (not (3 & 6 == 2)) { return 21; }
+  if (not (-1 & 4 == 4)) { return 22; }
+  if (not (-1 & -2 == -2)) { return 23; }
+
+  if (not (1 | 4 == 5)) { return 31; }
+  if (not (5 | 3 == 7)) { return 32; }
+  if (not (-2 | 1 == -1)) { return 33; }
+
+  if (not (1 ^ 4 == 5)) { return 41; }
+  if (not (5 ^ 3 == 6)) { return 42; }
+  if (not (-2 ^ -3 == 3)) { return 43; }
+
+  return 0;
+}

+ 24 - 0
explorer/testdata/operators/left_shift.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 10
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as LeftShiftWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n << rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a << 1;
+  return a.n;
+}

+ 24 - 0
explorer/testdata/operators/right_shift.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 2
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as RightShiftWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n >> rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a >> 1;
+  return a.n;
+}

+ 27 - 0
explorer/testdata/operators/shift.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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 0
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  if (not (1 << 0 == 1)) { return 1; }
+  if (not (1 << 3 == 8)) { return 2; }
+  if (not (0 << 3 == 0)) { return 3; }
+  if (not (3 << 1 == 6)) { return 4; }
+  if (not (-1 << 2 == -4)) { return 5; }
+  if (not (1 >> 0 == 1)) { return 6; }
+  if (not (1 >> 1 == 0)) { return 7; }
+  if (not (3 >> 1 == 1)) { return 8; }
+  if (not (-1 >> 1 == -1)) { return 9; }
+  if (not (-2 >> 1 == -1)) { return 10; }
+
+  return 0;
+}