Skip to content

Commit

Permalink
Print the value in error: cannot coerce messages
Browse files Browse the repository at this point in the history
This extends the `error: cannot coerce a TYPE to a string` message
to print the value that could not be coerced. This helps with debugging
by making it easier to track down where the value is being produced
from, especially in errors with deep or unhelpful stack traces.
  • Loading branch information
9999years committed Jan 12, 2024
1 parent 52f949b commit d57b43f
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 33 deletions.
75 changes: 75 additions & 0 deletions doc/manual/rl-next/print-value-in-coercion-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
synopsis: Coercion errors include the failing value
issues: #561
prs: #9754
---
description: {

The `error: cannot coerce a <TYPE> to a string` message now includes the value
which caused the error. This makes debugging much easier:

```
$ cat bad.nix
let
pkgs = import <nixpkgs> {};
system = pkgs.lib.systems.elaborate "x86_64-linux";
in
import <nixpkgs> {inherit system;}
```

Previously, attempting to evaluate this expression would produce a confusing error message:

```
$ nix-instantiate --eval bad.nix
error:
… while evaluating a branch condition
at /nix/store/m8ah0r1ih2shq35vp3hj1k0m1c4hsfga-nixpkgs/nixpkgs/pkgs/stdenv/booter.nix:64:9:
63| go = pred: n:
64| if n == len
| ^
65| then rnul pred
… while calling the 'length' builtin
at /nix/store/m8ah0r1ih2shq35vp3hj1k0m1c4hsfga-nixpkgs/nixpkgs/pkgs/stdenv/booter.nix:62:13:
61| let
62| len = builtins.length list;
| ^
63| go = pred: n:
(stack trace truncated; use '--show-trace' to show the full trace)
error: cannot coerce a set to a string
```

Now, the error message includes the set itself. This makes debugging much
simpler, especially when the trace doesn't show the failing expression:

```
$ nix-instantiate --eval bad.nix
error:
… while evaluating a branch condition
at /nix/store/m8ah0r1ih2shq35vp3hj1k0m1c4hsfga-nixpkgs/nixpkgs/pkgs/stdenv/booter.nix:64:9:
63| go = pred: n:
64| if n == len
| ^
65| then rnul pred
… while calling the 'length' builtin
at /nix/store/m8ah0r1ih2shq35vp3hj1k0m1c4hsfga-nixpkgs/nixpkgs/pkgs/stdenv/booter.nix:62:13:
61| let
62| len = builtins.length list;
| ^
63| go = pred: n:
(stack trace truncated; use '--show-trace' to show the full trace)
error: cannot coerce a set to a string: { aesSupport = «thunk»;
avx2Support = «thunk»; avx512Support = «thunk»; avxSupport = «thunk»;
canExecute = «thunk»; config = «thunk»; darwinArch = «thunk»; darwinMinVersion
= «thunk»; darwinMinVersionVariable = «thunk»; darwinPlatform = «thunk»; «84
attributes elided»}
```
2 changes: 1 addition & 1 deletion doc/manual/src/language/string-interpolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ If neither is present, an error is thrown.
> "${a}"
> ```
>
> error: cannot coerce a set to a string
> error: cannot coerce a set to a string: { }
>
> at «string»:4:2:
>
Expand Down
11 changes: 7 additions & 4 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <iostream>
#include <fstream>
#include <functional>

Expand Down Expand Up @@ -2233,7 +2232,9 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) {
error("cannot coerce %1% to a string", showType(v))
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
}
Expand Down Expand Up @@ -2279,7 +2280,9 @@ BackedStringView EvalState::coerceToString(
}
}

error("cannot coerce %1% to a string", showType(v))
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
}
Expand Down Expand Up @@ -2639,7 +2642,7 @@ void EvalState::printStatistics()
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{
throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType())
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this)
});
}

Expand Down
1 change: 1 addition & 0 deletions src/libexpr/print-ambiguous.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "print-ambiguous.hh"
#include "print.hh"
#include "signals.hh"
#include "eval.hh"

namespace nix {

Expand Down
22 changes: 21 additions & 1 deletion src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ struct PrintOptions
*/
size_t maxDepth = std::numeric_limits<size_t>::max();
/**
* Maximum number of attributes in an attribute set to print.
* Maximum number of attributes in attribute sets to print.
*
* Note that this is a limit for the entire print invocation, not for each
* attribute set encountered.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();
/**
* Maximum number of list items to print.
*
* Note that this is a limit for the entire print invocation, not for each
* list encountered.
*/
size_t maxListItems = std::numeric_limits<size_t>::max();
/**
Expand All @@ -49,4 +55,18 @@ struct PrintOptions
size_t maxStringLength = std::numeric_limits<size_t>::max();
};

/**
* `PrintOptions` suitable for debugging.
*
* These options are used for printing values in error messages without
* printing "too much" output.
*/
static PrintOptions debugPrintOptions = PrintOptions {
.ansiColors = true,
.maxDepth = 10,
.maxAttrs = 10,
.maxListItems = 10,
.maxStringLength = 1024
};

}
18 changes: 14 additions & 4 deletions src/libexpr/print.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "store-api.hh"
#include "terminal.hh"
#include "english.hh"
#include "eval.hh"

namespace nix {

Expand All @@ -19,7 +20,7 @@ void printElided(
{
if (ansiColors)
output << ANSI_FAINT;
output << " «";
output << "«";
pluralize(output, value, single, plural);
output << " elided»";
if (ansiColors)
Expand All @@ -36,7 +37,7 @@ printLiteralString(std::ostream & str, const std::string_view string, size_t max
str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) {
if (charsPrinted >= maxLength) {
str << "\"";
str << "\" ";
printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
return str;
}
Expand Down Expand Up @@ -160,6 +161,8 @@ class Printer
EvalState & state;
PrintOptions options;
std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0;
size_t listItemsPrinted = 0;

void printRepeated()
{
Expand Down Expand Up @@ -278,7 +281,6 @@ class Printer
else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());

size_t attrsPrinted = 0;
for (auto & i : sorted) {
if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
Expand Down Expand Up @@ -306,7 +308,6 @@ class Printer

output << "[ ";
if (depth < options.maxDepth) {
size_t listItemsPrinted = 0;
for (auto elem : v.listItems()) {
if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items");
Expand Down Expand Up @@ -485,6 +486,9 @@ class Printer

void print(Value & v)
{
attrsPrinted = 0;
listItemsPrinted = 0;

if (options.trackRepeated) {
seen.emplace();
} else {
Expand All @@ -501,4 +505,10 @@ void printValue(EvalState & state, std::ostream & output, Value & v, PrintOption
Printer(output, state, options).print(v);
}

std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer)
{
printValue(printer.state, output, printer.value, printer.options);
return output;
}

}
21 changes: 20 additions & 1 deletion src/libexpr/print.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@

#include <iostream>

#include "eval.hh"
#include "print-options.hh"

namespace nix {

class EvalState;
struct Value;

/**
* Print a string as a Nix string literal.
*
Expand Down Expand Up @@ -59,4 +61,21 @@ std::ostream & printIdentifier(std::ostream & o, std::string_view s);

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

/**
* A partially-applied form of `printValue` which can be formatted using `<<`
* without allocating an intermediate string.
*/
class ValuePrinter {
friend std::ostream & operator << (std::ostream & output, const ValuePrinter & printer);
private:
EvalState & state;
Value & value;
PrintOptions options;

public:
ValuePrinter(EvalState & state, Value & value, PrintOptions options = PrintOptions {})
: state(state), value(value), options(options) { }
};

std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ error:
| ^
2|

error: cannot coerce a function to a string
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:4»
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ error:
| ^
2|

error: cannot coerce a function to a string
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:5»
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ error:
| ^
10|

error: cannot coerce a set to a string
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «4294967295 attributes elided»}; «4294967294 attributes elided»}; «4294967293 attributes elided»}
Loading

0 comments on commit d57b43f

Please sign in to comment.