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 20, 2024
1 parent 382fa51 commit 489f190
Show file tree
Hide file tree
Showing 14 changed files with 168 additions and 35 deletions.
24 changes: 24 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,24 @@
---
synopsis: Coercion errors include the failing value
issues: #561
prs: #9754
---

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

Before:

```
error: cannot coerce a set to a string
```

After:

```
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, errorPrintOptions))
.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, errorPrintOptions))
.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
20 changes: 19 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,16 @@ struct PrintOptions
size_t maxStringLength = std::numeric_limits<size_t>::max();
};

/**
* `PrintOptions` for unknown and therefore potentially large values in error messages,
* to avoid printing "too much" output.
*/
static PrintOptions errorPrintOptions = 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);
}
1 change: 0 additions & 1 deletion tests/functional/ca/config.nix.in

This file was deleted.

30 changes: 30 additions & 0 deletions tests/functional/ca/config.nix.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
let
contentAddressedByDefault = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT" == "1";
caArgs = if contentAddressedByDefault then {
__contentAddressed = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
} else {};
in

rec {
shell = "@bash@";

path = "@coreutils@";

system = "@system@";

shared = builtins.getEnv "_NIX_TEST_SHARED";

mkDerivation = args:
derivation ({
inherit system;
builder = shell;
args = ["-e" args.builder or (builtins.toFile "builder-${args.name}.sh" ''
if [ -e "$NIX_ATTRS_SH_FILE" ]; then source $NIX_ATTRS_SH_FILE; fi;
eval "$buildCommand"
'')];
PATH = path;
} // caArgs // removeAttrs args ["builder" "meta"])
// { meta = args.meta or {}; };
}
1 change: 0 additions & 1 deletion tests/functional/dyn-drv/config.nix.in

This file was deleted.

30 changes: 30 additions & 0 deletions tests/functional/dyn-drv/config.nix.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
let
contentAddressedByDefault = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT" == "1";
caArgs = if contentAddressedByDefault then {
__contentAddressed = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
} else {};
in

rec {
shell = "@bash@";

path = "@coreutils@";

system = "@system@";

shared = builtins.getEnv "_NIX_TEST_SHARED";

mkDerivation = args:
derivation ({
inherit system;
builder = shell;
args = ["-e" args.builder or (builtins.toFile "builder-${args.name}.sh" ''
if [ -e "$NIX_ATTRS_SH_FILE" ]; then source $NIX_ATTRS_SH_FILE; fi;
eval "$buildCommand"
'')];
PATH = path;
} // caArgs // removeAttrs args ["builder" "meta"])
// { meta = args.meta or {}; };
}
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 489f190

Please sign in to comment.