libexpr: fix app chain extension

during the value rewrite we accidentally broke extension of incomplete
primop application. this only shows up when binding on incomplete call
to a primop to a name, binding an incomplete call to *that* to another
name, and then finally calling the second binding with enough args for
a complete primop application. since this only shows up when calling a
primop with three or more args it took a while to surface. we have few
builtins that match this: foldl', replaceStrings, and substring. these
are not used incompletely in this manner very often, so this lingered.

fixes #1102

Change-Id: I218dffc14ae876efc86a86c7eb6c895e2405201c
This commit is contained in:
eldritch horrors
2026-01-14 22:05:16 +00:00
parent 6cc2ef7c6d
commit 8285288540
7 changed files with 31 additions and 4 deletions
+13 -3
View File
@@ -20,11 +20,21 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
}
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args)
: Value(app_t{}, mem, lhs, args, {})
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
}
inline Value::Value(
app_t, EvalMemory & mem, const Value & lhs, std::span<Value> baseArgs, std::span<Value> moreArgs
)
{
auto app = static_cast<Value::App *>(
mem.allocBytes(sizeof(Value::App) + baseArgs.size_bytes() + moreArgs.size_bytes())
);
app->_left = lhs;
app->_n = args.size();
std::copy(args.begin(), args.end(), app->_args);
app->_n = baseArgs.size() + moreArgs.size();
std::copy(baseArgs.begin(), baseArgs.end(), app->_args);
std::copy(moreArgs.begin(), moreArgs.end(), app->_args + baseArgs.size());
raw = tag(tApp, app);
}
+8 -1
View File
@@ -1138,7 +1138,14 @@ void EvalState::callFunction(Value & fun, std::span<Value> args, Value & vRes, c
Value vCur(fun);
auto makeAppChain = [&]() { vRes = {NewValueAs::app, ctx.mem, vCur, args}; };
auto makeAppChain = [&]() {
if (vCur.isApp()) {
auto & app = vCur.app();
vRes = {NewValueAs::app, ctx.mem, app.left(), app.args(), args};
} else {
vRes = {NewValueAs::app, ctx.mem, vCur, args};
}
};
const Attr * functor;
+4
View File
@@ -544,6 +544,10 @@ public:
/// lazy and/or partial application of a function.
Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args);
/// Constructs a nix language value of type "lambda", which represents a
/// lazy and/or partial application of a function.
Value(app_t, EvalMemory & mem, const Value & lhs, std::span<Value> baseArgs, std::span<Value> moreArgs);
/// Constructs a nix language value of type "external", which is only used
/// by plugins. Do any existing plugins even use this mechanism?
Value(external_t, ExternalValueBase & external)
@@ -0,0 +1 @@
"234"
@@ -0,0 +1,5 @@
let
a = builtins.substring 1;
b = a 3;
in
b "1234567890"