diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index cd9a05bb29d..96b0bf1f5ca 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -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); + auto attrs = state.buildBindings(3); + if(args[0]->lambda.fun->arg) attrs.alloc("arg").mkString(state.symbols[args[0]->lambda.fun->arg]); + attrs.alloc("ellipsis").mkBool(args[0]->lambda.fun->formals->ellipsis); + attrs.alloc("formals").mkAttrs(formals); + 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) { diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index ce3b5d11fae..eac9ac81383 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -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));