Skip to content

Commit d6ed2cc

Browse files
committed
Add filterAttrs builtin
1 parent 52ea293 commit d6ed2cc

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

src/libexpr-tests/primops.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,31 @@ TEST_F(PrimOpTest, mapAttrs)
320320
ASSERT_THAT(*b->value, IsIntEq(20));
321321
}
322322

323+
TEST_F(PrimOpTest, filterAttrs)
324+
{
325+
auto v = eval("builtins.filterAttrs (name: value: value > 5) { a = 3; b = 10; c = 7; }");
326+
ASSERT_THAT(v, IsAttrsOfSize(2));
327+
328+
auto a = v.attrs()->get(createSymbol("a"));
329+
ASSERT_EQ(a, nullptr);
330+
331+
auto b = v.attrs()->get(createSymbol("b"));
332+
ASSERT_NE(b, nullptr);
333+
state.forceValue(*b->value, noPos);
334+
ASSERT_THAT(*b->value, IsIntEq(10));
335+
336+
auto c = v.attrs()->get(createSymbol("c"));
337+
ASSERT_NE(c, nullptr);
338+
state.forceValue(*c->value, noPos);
339+
ASSERT_THAT(*c->value, IsIntEq(7));
340+
}
341+
342+
TEST_F(PrimOpTest, filterAttrsEmpty)
343+
{
344+
auto v = eval("builtins.filterAttrs (name: value: false) { a = 1; b = 2; }");
345+
ASSERT_THAT(v, IsAttrsOfSize(0));
346+
}
347+
323348
TEST_F(PrimOpTest, isList)
324349
{
325350
auto v = eval("builtins.isList []");

src/libexpr/primops.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,50 @@ static RegisterPrimOp primop_mapAttrs({
35053505
.fun = prim_mapAttrs,
35063506
});
35073507

3508+
static void prim_filterAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
3509+
{
3510+
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.filterAttrs");
3511+
3512+
if (args[1]->attrs()->empty()) {
3513+
v = *args[1];
3514+
return;
3515+
}
3516+
3517+
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterAttrs");
3518+
3519+
auto attrs = state.buildBindings(args[1]->attrs()->size());
3520+
3521+
for (auto & i : *args[1]->attrs()) {
3522+
Value * vName = Value::toPtr(state.symbols[i.name]);
3523+
Value * vFun2 = state.allocValue();
3524+
vFun2->mkApp(args[0], vName);
3525+
Value res;
3526+
state.callFunction(*vFun2, *i.value, res, noPos);
3527+
if (state.forceBool(
3528+
res, pos, "while evaluating the return value of the filtering function passed to builtins.filterAttrs"))
3529+
attrs.insert(i.name, i.value);
3530+
}
3531+
3532+
v.mkAttrs(attrs.alreadySorted());
3533+
}
3534+
3535+
static RegisterPrimOp primop_filterAttrs({
3536+
.name = "__filterAttrs",
3537+
.args = {"f", "attrset"},
3538+
.doc = R"(
3539+
Return an attribute set consisting of the attributes in *attrset* for which
3540+
the function *f* returns `true`. The function *f* is called with two arguments:
3541+
the name of the attribute and the value of the attribute. For example,
3542+
3543+
```nix
3544+
builtins.filterAttrs (name: value: name == "foo") { foo = 1; bar = 2; }
3545+
```
3546+
3547+
evaluates to `{ foo = 1; }`.
3548+
)",
3549+
.fun = prim_filterAttrs,
3550+
});
3551+
35083552
static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value ** args, Value & v)
35093553
{
35103554
// we will first count how many values are present for each given key.

0 commit comments

Comments
 (0)