From 356df8b5608857b69bb9060089fd3f0f03ade087 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 21 Mar 2025 16:47:34 +0000 Subject: [PATCH] WIP: init seqWithCallPos Provide internal information to a function. Since the return value of the function is completely ignored It can be considered safe. This pattern could help to improve traces by providing access to internal state of the evaluator in a safe manner. --- example.nix | 20 +++++++++ src/libexpr/eval.cc | 5 +-- src/libexpr/primops.cc | 93 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 example.nix diff --git a/example.nix b/example.nix new file mode 100644 index 00000000000..23932081ea3 --- /dev/null +++ b/example.nix @@ -0,0 +1,20 @@ +let + showPos = + pos: if pos == null then "" else "${pos.file}:${toString pos.line}:${toString pos.column}"; + + # ({pos,isThunk, printValue} -> a -> b ) -> (a -> c) -> a -> c + tracePos = builtins.seqWithCallPos ( + { + pos, + printValue, + terminalWidth, + ... + }: + a: + let + msg = "${showPos pos} ${printValue a}"; + in + builtins.seq a builtins.trace msg null + ) (a: a); +in + tracePos 42 diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 369c4967240..dbaec4c8ee2 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1649,7 +1649,7 @@ void EvalState::callFunction(Value & fun, std::span args, Value & vRes, if (countCalls) primOpCalls[fn->name]++; try { - fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur); + fn->fun(*this, vCur.determinePos(pos), args.data(), vCur); } catch (Error & e) { if (fn->addTrace) addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); @@ -1697,7 +1697,7 @@ void EvalState::callFunction(Value & fun, std::span args, Value & vRes, // 1. Unify this and above code. Heavily redundant. // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc) // so the debugger allows to inspect the wrong parameters passed to the builtin. - fn->fun(*this, vCur.determinePos(noPos), vArgs, vCur); + fn->fun(*this, vCur.determinePos(pos), vArgs, vCur); } catch (Error & e) { if (fn->addTrace) addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); @@ -1760,7 +1760,6 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v) SmallValueVector<4> vArgs(args.size()); for (size_t i = 0; i < args.size(); ++i) vArgs[i] = args[i]->maybeThunk(state, env); - state.callFunction(vFun, vArgs, v, pos); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 54682ea318f..cdbfdd548e1 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -14,6 +14,8 @@ #include "value-to-xml.hh" #include "primops.hh" #include "fetch-to-store.hh" +#include "terminal.hh" +#include #include #include @@ -513,6 +515,97 @@ static RegisterPrimOp primop_isFunction({ .fun = prim_isFunction, }); +void prim_isThunk(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto &arg = *args[0]; + v.mkBool(arg.isThunk()); +} + +static RegisterPrimOp primop_isThunk({ + .name = "isThunk", + .args = {"v"}, + .doc = R"( + Returns `true` if the argument is a thunk, `false` otherwise. + + This is internal, because the thunk-ness of a value is not a stable property, while the Nix language is meant to be referentially transparent. + )", + .fun = prim_isThunk, + .internal = true +}); + +void prim_printValue(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto &arg = *args[0]; + std::stringstream ss; + printValue(state, ss, arg); + v.mkString(ss.str()); +} + +static RegisterPrimOp primop_printValue({ + .name = "printValue", + .args = {"v"}, + .doc = R"( + Print any value to an informative string. + + This is internal, because the format is not a stable behavior, while the Nix language is meant to be stable over time. + )", + .fun = prim_printValue, + .internal = true +}); + + +/* Determine whether the argument is an integer. */ +static void prim_seqWithCallPos(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + auto p = args[0]; + auto f = args[1]; + auto a = args[2]; + state.forceValue(*args[0], pos); + state.forceValue(*args[1], pos); + auto intro_args = state.buildBindings(4); + auto pos_attrs = state.allocValue(); + intro_args.insert(state.symbols.create("pos"), pos_attrs, noPos); + + auto vIsThunk = get(state.internalPrimOps, "isThunk"); + assert(vIsThunk); + intro_args.insert(state.symbols.create("isThunk"), *vIsThunk, noPos); + + auto vPrintValue = get(state.internalPrimOps, "printValue"); + assert(vPrintValue); + intro_args.insert(state.symbols.create("printValue"), *vPrintValue, noPos); + + auto [rows,cols] = getWindowSize(); + auto vCols = state.allocValue(); + vCols->mkInt(cols); + intro_args.insert(state.symbols.create("terminalWidth"), vCols , noPos); + + Value intro_args_value; + intro_args_value.mkAttrs(intro_args); + state.mkPos(*pos_attrs, pos); + + Value * pargs[2] = {&intro_args_value, a}; + + Value * _pRes = state.allocValue(); + try { + state.callFunction(*p, std::span(pargs, 2), *_pRes, pos); + } catch (Error & e) { + e.addTrace(state.positions[pos], "while calling the first function passed to builtins.seqWithCallPos"); + ignoreExceptionExceptInterrupt(lvlDebug); + } + + state.callFunction(*f, *a, v, pos); +} + +static RegisterPrimOp primop_seqWithCallPos({ + .name = "__seqWithCallPos", + .args = {"p", "f", "a"}, + .doc = R"( + Stuff + )", + .fun = prim_seqWithCallPos, +}); + + /* Determine whether the argument is an integer. */ static void prim_isInt(EvalState & state, const PosIdx pos, Value * * args, Value & v) {