Skip to content

Commit

Permalink
Add builtins.toStringDebug
Browse files Browse the repository at this point in the history
Added `builtins.toStringDebug`, which formats a value as a string for
debugging purposes. Unlike `builtins.toString`, `builtins.toStringDebug`
will never error and will always produce human-readable, pretty-printed
output (including for expressions that error). This makes it ideal for
interpolation into `builtins.trace` calls and `assert` messages.
  • Loading branch information
9999years committed Mar 10, 2024
1 parent ac73062 commit 3af61fe
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 4 deletions.
10 changes: 10 additions & 0 deletions doc/manual/rl-next/to-string-debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
synopsis: Add `builtins.toStringDebug`
prs:
---

Added `builtins.toStringDebug`, which formats a value as a string for debugging
purposes. Unlike `builtins.toString`, `builtins.toStringDebug` will never error
and will always produce human-readable, pretty-printed output (including for
expressions that error). This makes it ideal for interpolation into
`builtins.trace` calls and `assert` messages.
3 changes: 2 additions & 1 deletion src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "json-to-value.hh"
#include "names.hh"
#include "path-references.hh"
#include "print-options.hh"
#include "store-api.hh"
#include "util.hh"
#include "processes.hh"
Expand Down Expand Up @@ -1000,7 +1001,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
if (args[0]->type() == nString)
printError("trace: %1%", args[0]->string_view());
else
printError("trace: %1%", ValuePrinter(state, *args[0]));
printError("trace: %1%", ValuePrinter(state, *args[0], debugPrintOptions));
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
const DebugTrace & last = state.debugTraces.front();
state.runDebugRepl(nullptr, last.env, last.expr);
Expand Down
32 changes: 32 additions & 0 deletions src/libexpr/primops/toStringDebug.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "primops.hh"
#include "print-options.hh"

namespace nix {

static void prim_toStringDebug(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
v.mkString(printValue(state, *args[0], debugPrintOptions));
}

static RegisterPrimOp primop_toStringDebug({
.name = "toStringDebug",
.args = {"value"},
.doc = R"(
Format a value as a string for debugging purposes.
Unlike [`toString`](@docroot@/language/builtins.md#builtins-toString),
`toStringDebug` will never error and will always produce human-readable
output (including for values that throw errors). For this reason,
`toStringDebug` is ideal for interpolation into messages in
[`trace`](@docroot@/language/builtins.md#builtins-trace)
calls and [`assert`](@docroot@/language/constructs.html#assertions)
statements.
Output will be pretty-printed and include ANSI escape sequences.
If the value contains too many values (for instance, more than 32
attributes or list items), some values will be elided.
)",
.fun = prim_toStringDebug,
});

}
17 changes: 17 additions & 0 deletions src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,21 @@ static PrintOptions errorPrintOptions = PrintOptions {
.maxStringLength = 1024
};

/**
* `PrintOptions` for unknown and therefore potentially large values in
* debugging contexts, to avoid printing "too much" output.
*
* This is like `errorPrintOptions`, but prints more values.
*/
static PrintOptions debugPrintOptions = PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true,
.maxDepth = 15,
.maxAttrs = 32,
.maxListItems = 32,
.maxStringLength = 1024,
.prettyIndent = 2
};

}
13 changes: 13 additions & 0 deletions src/libexpr/print.cc
Original file line number Diff line number Diff line change
Expand Up @@ -592,11 +592,24 @@ class Printer
}
};

/**
* Print the given value to `output`.
*/
void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
{
Printer(output, state, options).print(v);
}

/**
* Print the given value to a new string.
*/
std::string printValue(EvalState & state, Value & v, PrintOptions options)
{
std::ostringstream output;
printValue(state, output, v, options);
return output.str();
}

std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer)
{
printValue(printer.state, output, printer.value, printer.options);
Expand Down
2 changes: 2 additions & 0 deletions src/libexpr/print.hh
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ std::ostream & printIdentifier(std::ostream & o, std::string_view s);

void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {});

std::string printValue(EvalState & state, Value & v, PrintOptions options = PrintOptions {});

/**
* A partially-applied form of `printValue` which can be formatted using `<<`
* without allocating an intermediate string.
Expand Down
9 changes: 7 additions & 2 deletions tests/functional/lang.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ expectStderr 1 nix-instantiate --show-trace --eval -E 'builtins.addErrorContext
expectStderr 1 nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello %" (throw "Foo")' | grepQuiet 'Hello %'

nix-instantiate --eval -E 'let x = builtins.trace { x = x; } true; in x' \
2>&1 | grepQuiet -E 'trace: { x = «potential infinite recursion»; }'
2>&1 | grepQuiet -F "trace: {
x = «potential infinite recursion»;
}"

nix-instantiate --eval -E 'let x = { repeating = x; tracing = builtins.trace x true; }; in x.tracing'\
2>&1 | grepQuiet -F 'trace: { repeating = «repeated»; tracing = «potential infinite recursion»; }'
2>&1 | grepQuiet -F "trace: {
repeating = «repeated»;
tracing = «potential infinite recursion»;
}"

set +x

Expand Down
4 changes: 3 additions & 1 deletion tests/functional/lang/eval-okay-print.err.exp
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
trace: [ «thunk» ]
trace: [
2
]

0 comments on commit 3af61fe

Please sign in to comment.