From c6f3f3a0d381fa592073d9bdb9b4673ee2d3d53e Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Wed, 14 Jan 2026 22:29:49 +0100 Subject: [PATCH] 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 --- lix/libexpr/eval-inline.hh | 16 +++++++++++++--- lix/libexpr/eval.cc | 9 ++++++++- lix/libexpr/value.hh | 4 ++++ ...fail.err.exp => eval-fail-incomplete.err.exp} | 0 .../call-primop/eval-okay-app-extension.out.exp | 1 + .../lang/call-primop/in-app-extension.nix | 5 +++++ .../call-primop/{in.nix => in-incomplete.nix} | 0 7 files changed, 31 insertions(+), 4 deletions(-) rename tests/functional2/lang/call-primop/{eval-fail.err.exp => eval-fail-incomplete.err.exp} (100%) create mode 100644 tests/functional2/lang/call-primop/eval-okay-app-extension.out.exp create mode 100644 tests/functional2/lang/call-primop/in-app-extension.nix rename tests/functional2/lang/call-primop/{in.nix => in-incomplete.nix} (100%) diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 3d414928d..a11df926e 100644 --- a/lix/libexpr/eval-inline.hh +++ b/lix/libexpr/eval-inline.hh @@ -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 args) + : Value(app_t{}, mem, lhs, args, {}) { - auto app = static_cast(mem.allocBytes(sizeof(Value::App) + args.size_bytes())); +} + +inline Value::Value( + app_t, EvalMemory & mem, const Value & lhs, std::span baseArgs, std::span moreArgs +) +{ + auto app = static_cast( + 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); } diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index d532d5da9..4e12801c3 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -1639,7 +1639,14 @@ void EvalState::callFunction(Value & fun, std::span 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; diff --git a/lix/libexpr/value.hh b/lix/libexpr/value.hh index 1f3d80f8f..75a16fae6 100644 --- a/lix/libexpr/value.hh +++ b/lix/libexpr/value.hh @@ -544,6 +544,10 @@ public: /// lazy and/or partial application of a function. Value(app_t, EvalMemory & mem, Value & lhs, std::span 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 baseArgs, std::span 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) diff --git a/tests/functional2/lang/call-primop/eval-fail.err.exp b/tests/functional2/lang/call-primop/eval-fail-incomplete.err.exp similarity index 100% rename from tests/functional2/lang/call-primop/eval-fail.err.exp rename to tests/functional2/lang/call-primop/eval-fail-incomplete.err.exp diff --git a/tests/functional2/lang/call-primop/eval-okay-app-extension.out.exp b/tests/functional2/lang/call-primop/eval-okay-app-extension.out.exp new file mode 100644 index 000000000..ce291fa51 --- /dev/null +++ b/tests/functional2/lang/call-primop/eval-okay-app-extension.out.exp @@ -0,0 +1 @@ +"234" diff --git a/tests/functional2/lang/call-primop/in-app-extension.nix b/tests/functional2/lang/call-primop/in-app-extension.nix new file mode 100644 index 000000000..bc627c0f2 --- /dev/null +++ b/tests/functional2/lang/call-primop/in-app-extension.nix @@ -0,0 +1,5 @@ +let + a = builtins.substring 1; + b = a 3; +in +b "1234567890" diff --git a/tests/functional2/lang/call-primop/in.nix b/tests/functional2/lang/call-primop/in-incomplete.nix similarity index 100% rename from tests/functional2/lang/call-primop/in.nix rename to tests/functional2/lang/call-primop/in-incomplete.nix