forked from lix-project/lix
Add builtins.toStringDebug
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.
(cherry picked from commit 3af61fec55b1bf882d67cc81d874c76c555d058a)
Upstream-PR: https://github.com/NixOS/nix/pull/10206
Change-Id: I2c778d3dea3c797a2eda8a4be5cf0e944ab54225
This commit is contained in:
parent
76b45b4861
commit
8a666d24aa
10
doc/manual/rl-next/to-string-debug.md
Normal file
10
doc/manual/rl-next/to-string-debug.md
Normal file
|
@ -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.
|
|
@ -9,6 +9,7 @@
|
||||||
#include "json-to-value.hh"
|
#include "json-to-value.hh"
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
#include "path-references.hh"
|
#include "path-references.hh"
|
||||||
|
#include "print-options.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "value-to-json.hh"
|
#include "value-to-json.hh"
|
||||||
|
@ -979,7 +980,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
|
||||||
if (args[0]->type() == nString)
|
if (args[0]->type() == nString)
|
||||||
printError("trace: %1%", args[0]->string.s);
|
printError("trace: %1%", args[0]->string.s);
|
||||||
else
|
else
|
||||||
printError("trace: %1%", ValuePrinter(state, *args[0]));
|
printError("trace: %1%", ValuePrinter(state, *args[0], debugPrintOptions));
|
||||||
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
|
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
|
||||||
const DebugTrace & last = state.debugTraces.front();
|
const DebugTrace & last = state.debugTraces.front();
|
||||||
state.runDebugRepl(nullptr, last.env, last.expr);
|
state.runDebugRepl(nullptr, last.env, last.expr);
|
||||||
|
|
32
src/libexpr/primops/toStringDebug.cc
Normal file
32
src/libexpr/primops/toStringDebug.cc
Normal file
|
@ -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,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -117,4 +117,21 @@ static PrintOptions errorPrintOptions = PrintOptions {
|
||||||
.maxStringLength = 1024,
|
.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
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,11 +554,24 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print the given value to `output`.
|
||||||
|
*/
|
||||||
void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
|
void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
|
||||||
{
|
{
|
||||||
Printer(output, state, options).print(v);
|
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)
|
std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer)
|
||||||
{
|
{
|
||||||
printValue(printer.state, output, printer.value, printer.options);
|
printValue(printer.state, output, printer.value, printer.options);
|
||||||
|
|
|
@ -47,6 +47,8 @@ std::ostream & printIdentifier(std::ostream & o, std::string_view s);
|
||||||
|
|
||||||
void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {});
|
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 `<<`
|
* A partially-applied form of `printValue` which can be formatted using `<<`
|
||||||
* without allocating an intermediate string.
|
* without allocating an intermediate string.
|
||||||
|
|
|
@ -28,10 +28,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 %'
|
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' \
|
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'\
|
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
|
set +x
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
trace: [ «thunk» ]
|
trace: [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
|
Loading…
Reference in a new issue