Skip to content

Commit baa39d8

Browse files
committed
Add filterAttrs primop, plucked from NixOS#14753.
1 parent ccba158 commit baa39d8

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/libexpr-tests/primops.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,31 @@ TEST_F(PrimOpTest, elemtAtOutOfBounds)
344344
ASSERT_THROW(eval("builtins.elemAt [0] 4294967296"), Error);
345345
}
346346

347+
TEST_F(PrimOpTest, filterAttrs)
348+
{
349+
auto v = eval("builtins.filterAttrs (name: value: value > 5) { a = 3; b = 10; c = 7; }");
350+
ASSERT_THAT(v, IsAttrsOfSize(2));
351+
352+
auto a = v.attrs()->get(createSymbol("a"));
353+
ASSERT_EQ(a, nullptr);
354+
355+
auto b = v.attrs()->get(createSymbol("b"));
356+
ASSERT_NE(b, nullptr);
357+
state.forceValue(*b->value, noPos);
358+
ASSERT_THAT(*b->value, IsIntEq(10));
359+
360+
auto c = v.attrs()->get(createSymbol("c"));
361+
ASSERT_NE(c, nullptr);
362+
state.forceValue(*c->value, noPos);
363+
ASSERT_THAT(*c->value, IsIntEq(7));
364+
}
365+
366+
TEST_F(PrimOpTest, filterAttrsEmpty)
367+
{
368+
auto v = eval("builtins.filterAttrs (name: value: false) { a = 1; b = 2; }");
369+
ASSERT_THAT(v, IsAttrsOfSize(0));
370+
}
371+
347372
TEST_F(PrimOpTest, head)
348373
{
349374
auto v = eval("builtins.head [ 3 2 1 0 ]");

src/libexpr/primops.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3529,6 +3529,48 @@ static RegisterPrimOp primop_mapAttrs({
35293529
.fun = prim_mapAttrs,
35303530
});
35313531

3532+
static void prim_filterAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
3533+
{
3534+
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.filterAttrs");
3535+
3536+
if (args[1]->attrs()->empty()) {
3537+
v = *args[1];
3538+
return;
3539+
}
3540+
3541+
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterAttrs");
3542+
3543+
auto attrs = state.buildBindings(args[1]->attrs()->size());
3544+
3545+
for (auto & i : *args[1]->attrs()) {
3546+
Value * vName = Value::toPtr(state.symbols[i.name]);
3547+
Value * vFun2 = state.allocValue();
3548+
vFun2->mkApp(args[0], vName);
3549+
Value res;
3550+
state.callFunction(*vFun2, *i.value, res, noPos);
3551+
if (state.forceBool(
3552+
res, pos, "while evaluating the return value of the filtering function passed to builtins.filterAttrs"))
3553+
attrs.insert(i.name, i.value);
3554+
}
3555+
3556+
v.mkAttrs(attrs.alreadySorted());
3557+
}
3558+
3559+
static RegisterPrimOp primop_filterAttrs({
3560+
.name = "__filterAttrs",
3561+
.args = {"f", "attrset"},
3562+
.doc = R"(
3563+
Return an attribute set consisting of the attributes in *attrset* for which
3564+
the function *f* returns `true`. The function *f* is called with two arguments:
3565+
the name of the attribute and the value of the attribute. For example,
3566+
```nix
3567+
builtins.filterAttrs (name: value: name == "foo") { foo = 1; bar = 2; }
3568+
```
3569+
evaluates to `{ foo = 1; }`.
3570+
)",
3571+
.fun = prim_filterAttrs,
3572+
});
3573+
35323574
static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value ** args, Value & v)
35333575
{
35343576
// we will first count how many values are present for each given key.

0 commit comments

Comments
 (0)