Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,52 @@ static RegisterPrimOp primop_functionArgs({
.fun = prim_functionArgs,
});

static void prim_functionInfo(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) {
v.mkAttrs(&state.emptyBindings);
return;
}
if (!args[0]->isLambda())
state.debugThrowLastTrace(TypeError({
.msg = hintfmt("'functionArgs' requires a function"),
.errPos = state.positions[pos]
}));

if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
return;
}

auto formals = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals)
// !!! should optimise booleans (allocate only once)
formals.alloc(i.name, i.pos).mkBool(i.def);
Comment on lines +2812 to +2814
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be common with the functionArgs primop and should be factored out.

auto attrs = state.buildBindings(3);
if(args[0]->lambda.fun->arg) attrs.alloc("arg").mkString(state.symbols[args[0]->lambda.fun->arg]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we should not expose this information. It is not needed, and it will introduce unnecessary coupling that users will have to be aware of when renaming what is otherwise a completely internal name.

attrs.alloc("ellipsis").mkBool(args[0]->lambda.fun->formals->ellipsis);
attrs.alloc("formals").mkAttrs(formals);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe formals is an unfortunate name, and it has so far only been part of documentation, and not the language itself. This would be the last chance to fix that mistake.
args would match functionArgs. Will come back to this.

v.mkAttrs(attrs);
}

static RegisterPrimOp primop_functionInfo({
.name = "__functionInfo",
.args = {"f"},
.doc = R"(
Return information about the function including the following:

* arg - The name of the argument, this value is present for functions
that don't use attrset matching(i.e. `x: true`) and functions
that use an @-pattern(i.e. `{...}@argName`). Otherwise this
attr is absent.
* ellipsis - Set to true for functions of the form `{ a, b, ... }`.
* formals - Returns formal arguments, identical to
`builtins.functionArgs`.
)",
.fun = prim_functionInfo,
});

/* */
static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
Expand Down
49 changes: 49 additions & 0 deletions src/libexpr/tests/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,55 @@ namespace nix {
ASSERT_THAT(*y->value, IsTrue());
}

TEST_F(PrimOpTest, functionInfoWithEllipsisAndArg) {
auto v = eval("builtins.functionInfo ({ x, y ? 123, ...}@inputs: 1)");
ASSERT_THAT(v, IsAttrsOfSize(3));

auto arg = v.attrs->find(createSymbol("arg"));
ASSERT_NE(arg, nullptr);
ASSERT_THAT(*arg->value, IsStringEq("inputs"));

auto ellipsis = v.attrs->find(createSymbol("ellipsis"));
ASSERT_NE(ellipsis, nullptr);
ASSERT_THAT(*ellipsis->value, IsTrue());

auto formals = v.attrs->find(createSymbol("formals"));
ASSERT_NE(formals, nullptr);
ASSERT_THAT(*formals->value, IsAttrsOfSize(2));

auto x = formals->value->attrs->find(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());

auto y = formals->value->attrs->find(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
}

TEST_F(PrimOpTest, functionInfoNoEllipsisNoArg) {
auto v = eval("builtins.functionInfo ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2));

auto arg = v.attrs->find(createSymbol("arg"));
ASSERT_EQ(arg, v.attrs->end());

auto ellipsis = v.attrs->find(createSymbol("ellipsis"));
ASSERT_NE(ellipsis, nullptr);
ASSERT_THAT(*ellipsis->value, IsFalse());

auto formals = v.attrs->find(createSymbol("formals"));
ASSERT_NE(formals, nullptr);
ASSERT_THAT(*formals->value, IsAttrsOfSize(2));

auto x = formals->value->attrs->find(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());

auto y = formals->value->attrs->find(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
}

TEST_F(PrimOpTest, mapAttrs) {
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
Expand Down