From 4eed1b32d93ab4d5423028c28189098d3c0a65c2 Mon Sep 17 00:00:00 2001
From: "Sophie Taylor (spacekitteh)" <sophie@spacekitteh.moe>
Date: Fri, 11 Apr 2025 09:43:20 +1000
Subject: [PATCH] Add bit shifting built-ins

---
 doc/manual/rl-next/bit-shift-builtins.md |  6 +++++
 src/libexpr-tests/primops.cc             | 10 +++++++
 src/libexpr/primops.cc                   | 34 ++++++++++++++++++++++++
 3 files changed, 50 insertions(+)
 create mode 100644 doc/manual/rl-next/bit-shift-builtins.md

diff --git a/doc/manual/rl-next/bit-shift-builtins.md b/doc/manual/rl-next/bit-shift-builtins.md
new file mode 100644
index 000000000..ede755bb1
--- /dev/null
+++ b/doc/manual/rl-next/bit-shift-builtins.md
@@ -0,0 +1,6 @@
+---
+synopsis: Add bit shifting built-ins
+issues: [13000]
+---
+
+Added left and right bit shifting built-ins.
diff --git a/src/libexpr-tests/primops.cc b/src/libexpr-tests/primops.cc
index c3e50863d..d3fb3b678 100644
--- a/src/libexpr-tests/primops.cc
+++ b/src/libexpr-tests/primops.cc
@@ -513,6 +513,16 @@ namespace nix {
         ASSERT_THAT(v, IsIntEq(1));
     }
 
+    TEST_F(PrimOpTest, bitShiftLeft) {
+        auto v = eval("builtins.bitShiftLeft 3 2");
+        ASSERT_THAT(v, IsIntEq(12));
+    }
+
+    TEST_F(PrimOpTest, bitShiftRight) {
+        auto v = eval("builtins.bitShiftRight 17 3");
+        ASSERT_THAT(v, IsIntEq(2));
+    }
+
     TEST_F(PrimOpTest, lessThanFalse) {
         auto v = eval("builtins.lessThan 3 1");
         ASSERT_THAT(v, IsFalse());
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 5e331f84d..d19d2e43e 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -4007,6 +4007,40 @@ static RegisterPrimOp primop_bitXor({
     .fun = prim_bitXor,
 });
 
+static void prim_bitShiftLeft(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+    auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitShiftLeft");
+    auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitShiftLeft");
+
+    v.mkInt(i1.value << i2.value);
+}
+
+static RegisterPrimOp primop_bitShiftLeft({
+    .name = "__bitShiftLeft",
+    .args = {"e1", "e2"},
+    .doc = R"(
+      Return the integer *e1* shifted left by *e2*.
+    )",
+    .fun = prim_bitShiftLeft,
+});
+
+static void prim_bitShiftRight(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+    auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitShiftRight");
+    auto i2 = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitShiftRight");
+
+    v.mkInt(i1.value >> i2.value);
+}
+
+static RegisterPrimOp primop_bitShiftRight({
+    .name = "__bitShiftRight",
+    .args = {"e1", "e2"},
+    .doc = R"(
+      Return the integer *e1* shifted right by *e2*.
+    )",
+    .fun = prim_bitShiftRight,
+});
+
 static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v)
 {
     state.forceValue(*args[0], pos);