C API: Add nix_init_apply

Thunks are relevant when initializing attrsets and lists, passing
arguments. This is an important way to produce them.
This commit is contained in:
Robert Hensing 2024-04-17 17:41:23 +02:00
parent 05b9dac754
commit ad643cde58
4 changed files with 157 additions and 2 deletions

View file

@ -93,6 +93,8 @@ nix_err nix_expr_eval_from_string(
* @param[in] arg The argument to pass to the function.
* @param[out] value The result of the function call.
* @return NIX_OK if the function call was successful, an error code otherwise.
* @see nix_init_apply() for a similar function that does not performs the call immediately, but stores it as a thunk.
* Note the different argument order.
*/
nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value);

View file

@ -404,6 +404,19 @@ nix_err nix_init_null(nix_c_context * context, Value * value)
NIXC_CATCH_ERRS
}
nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value * arg)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto & f = check_value_not_null(fn);
auto & a = check_value_not_null(arg);
v.mkApp(&f, &a);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val)
{
if (context)

View file

@ -342,8 +342,24 @@ nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i);
* @param[out] value Nix value to modify
* @return error code, NIX_OK on success.
*/
nix_err nix_init_null(nix_c_context * context, Value * value);
/** @brief Set the value to a thunk that will perform a function application when needed.
*
* Thunks may be put into attribute sets and lists to perform some computation lazily; on demand.
* However, note that in some places, a thunk must not be returned, such as in the return value of a PrimOp.
* In such cases, you may use nix_value_call() instead (but note the different argument order).
*
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] fn function to call
* @param[in] arg argument to pass
* @return error code, NIX_OK on successful initialization.
* @see nix_value_call() for a similar function that performs the call immediately and only stores the return value.
* Note the different argument order.
*/
nix_err nix_init_apply(nix_c_context * context, Value * value, Value * fn, Value * arg);
/** @brief Set an external value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
@ -421,7 +437,7 @@ BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState *
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] builder BindingsBuilder to insert into
* @param[in] name attribute name, copied into the symbol store
* @param[in] name attribute name, only used for the duration of the call.
* @param[in] value value to give the binding
* @return error code, NIX_OK on success.
*/

View file

@ -8,6 +8,7 @@
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"
#include "gmock/gmock.h"
#include <cstdlib>
#include <gtest/gtest.h>
@ -187,4 +188,127 @@ TEST_F(nix_api_expr_test, nix_build_and_init_attr)
free(out_name);
}
TEST_F(nix_api_expr_test, nix_value_init)
{
// Setup
// two = 2;
// f = a: a * a;
Value * two = nix_alloc_value(ctx, state);
nix_init_int(ctx, two, 2);
Value * f = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx, state, R"(
a: a * a
)",
"<test>", f);
// Test
// r = f two;
Value * r = nix_alloc_value(ctx, state);
nix_init_apply(ctx, r, f, two);
assert_ctx_ok();
ValueType t = nix_get_type(ctx, r);
assert_ctx_ok();
ASSERT_EQ(t, NIX_TYPE_THUNK);
nix_value_force(ctx, state, r);
t = nix_get_type(ctx, r);
assert_ctx_ok();
ASSERT_EQ(t, NIX_TYPE_INT);
int n = nix_get_int(ctx, r);
assert_ctx_ok();
ASSERT_EQ(n, 4);
// Clean up
nix_gc_decref(ctx, two);
nix_gc_decref(ctx, f);
nix_gc_decref(ctx, r);
}
TEST_F(nix_api_expr_test, nix_value_init_apply_error)
{
Value * some_string = nix_alloc_value(ctx, state);
nix_init_string(ctx, some_string, "some string");
assert_ctx_ok();
Value * v = nix_alloc_value(ctx, state);
nix_init_apply(ctx, v, some_string, some_string);
assert_ctx_ok();
// All ok. Call has not been evaluated yet.
// Evaluate it
nix_value_force(ctx, state, v);
ASSERT_EQ(ctx->last_err_code, NIX_ERR_NIX_ERROR);
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("attempt to call something which is not a function but"));
// Clean up
nix_gc_decref(ctx, some_string);
nix_gc_decref(ctx, v);
}
TEST_F(nix_api_expr_test, nix_value_init_apply_lazy_arg)
{
// f is a lazy function: it does not evaluate its argument before returning its return value
// g is a helper to produce e
// e is a thunk that throws an exception
//
// r = f e
// r should not throw an exception, because e is not evaluated
Value * f = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx, state, R"(
a: { foo = a; }
)",
"<test>", f);
assert_ctx_ok();
Value * e = nix_alloc_value(ctx, state);
{
Value * g = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx, state, R"(
_ignore: throw "error message for test case nix_value_init_apply_lazy_arg"
)",
"<test>", g);
assert_ctx_ok();
nix_init_apply(ctx, e, g, g);
assert_ctx_ok();
nix_gc_decref(ctx, g);
}
Value * r = nix_alloc_value(ctx, state);
nix_init_apply(ctx, r, f, e);
assert_ctx_ok();
nix_value_force(ctx, state, r);
assert_ctx_ok();
auto n = nix_get_attrs_size(ctx, r);
assert_ctx_ok();
ASSERT_EQ(1, n);
// nix_get_attr_byname isn't lazy (it could have been) so it will throw the exception
Value * foo = nix_get_attr_byname(ctx, r, state, "foo");
ASSERT_EQ(nullptr, foo);
ASSERT_THAT(ctx->last_err.value(), testing::HasSubstr("error message for test case nix_value_init_apply_lazy_arg"));
// Clean up
nix_gc_decref(ctx, f);
nix_gc_decref(ctx, e);
}
}