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 Dec 7, 2023
1 parent 3dcb834 commit c60e63e
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 21 deletions.
38 changes: 38 additions & 0 deletions doc/manual/rl-next/print-value-in-error-cannot-coerce.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
synopsis: Prints the failing value in `error: cannot coerce` messages
issues: #561
prs: #9553
description: {

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

```
$ nix repl nixpkgs
nix-repl> nixpkgs = legacyPackages.x86_64-linux.path
nix-repl> system = { system = "x86_64-linux"; }
nix-repl> import nixpkgs { inherit system; }
error:
… while evaluating a branch condition
at /nix/store/7g8wbm1z6948x0qma4hzkkb9a3xys76w-source/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/7g8wbm1z6948x0qma4hzkkb9a3xys76w-source/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: { system = "x86_64-linux"; }
```

}
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
10 changes: 6 additions & 4 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <iostream>
#include <fstream>
#include <functional>
#include <strstream>

#include <sys/resource.h>
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -2286,7 +2286,7 @@ 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), printValue(*this, v))
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
}
Expand Down Expand Up @@ -2332,7 +2332,7 @@ BackedStringView EvalState::coerceToString(
}
}

error("cannot coerce %1% to a string", showType(v))
error("cannot coerce %1% to a string: %2%", showType(v), printValue(*this, v))
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
}
Expand Down Expand Up @@ -2691,8 +2691,10 @@ void EvalState::printStatistics()

std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{
std::strstream printed;
print(printed);
throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType())
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), printed.str())
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ error:
| ^
2|

error: cannot coerce a function to a string
error: cannot coerce a function to a string: <LAMBDA>
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ error:
| ^
2|

error: cannot coerce a function to a string
error: cannot coerce a function to a string: <LAMBDA>
28 changes: 14 additions & 14 deletions tests/unit/libexpr/error_traces.cc
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ namespace nix {
TEST_F(ErrorTraceTest, toPath) {
ASSERT_TRACE2("toPath []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a string: %s", "a list", "[ ]"),
hintfmt("while evaluating the first argument passed to builtins.toPath"));

ASSERT_TRACE2("toPath \"foo\"",
Expand All @@ -309,7 +309,7 @@ namespace nix {
TEST_F(ErrorTraceTest, storePath) {
ASSERT_TRACE2("storePath true",
TypeError,
hintfmt("cannot coerce %s to a string", "a Boolean"),
hintfmt("cannot coerce %s to a string: %s", "a Boolean", "true"),
hintfmt("while evaluating the first argument passed to 'builtins.storePath'"));

}
Expand All @@ -318,7 +318,7 @@ namespace nix {
TEST_F(ErrorTraceTest, pathExists) {
ASSERT_TRACE2("pathExists []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a string: %s", "a list", "[ ]"),
hintfmt("while realising the context of a path"));

ASSERT_TRACE2("pathExists \"zorglub\"",
Expand All @@ -332,7 +332,7 @@ namespace nix {
TEST_F(ErrorTraceTest, baseNameOf) {
ASSERT_TRACE2("baseNameOf []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a string: %s", "a list", "[ ]"),
hintfmt("while evaluating the first argument passed to builtins.baseNameOf"));

}
Expand Down Expand Up @@ -377,7 +377,7 @@ namespace nix {
TEST_F(ErrorTraceTest, filterSource) {
ASSERT_TRACE2("filterSource [] []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("cannot coerce %s to a string: %s", "a list", "[ ]"),
hintfmt("while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"));

ASSERT_TRACE2("filterSource [] \"foo\"",
Expand Down Expand Up @@ -1038,7 +1038,7 @@ namespace nix {
TEST_F(ErrorTraceTest, toString) {
ASSERT_TRACE2("toString { a = 1; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ a = 1; }"),
hintfmt("while evaluating the first argument passed to builtins.toString"));

}
Expand All @@ -1057,7 +1057,7 @@ namespace nix {

ASSERT_TRACE2("substring 0 3 {}",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating the third argument (the string) passed to builtins.substring"));

ASSERT_TRACE1("substring (-3) 3 \"sometext\"",
Expand All @@ -1070,7 +1070,7 @@ namespace nix {
TEST_F(ErrorTraceTest, stringLength) {
ASSERT_TRACE2("stringLength {} # TODO: context is missing ???",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating the argument passed to builtins.stringLength"));

}
Expand Down Expand Up @@ -1143,7 +1143,7 @@ namespace nix {

ASSERT_TRACE2("concatStringsSep \"foo\" [ 1 2 {} ] # TODO: coerce to string is buggy",
TypeError,
hintfmt("cannot coerce %s to a string", "an integer"),
hintfmt("cannot coerce %s to a string: %s", "an integer", "1"),
hintfmt("while evaluating one element of the list of strings to concat passed to builtins.concatStringsSep"));

}
Expand Down Expand Up @@ -1229,12 +1229,12 @@ namespace nix {
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = {}; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating the attribute 'system' of derivation 'foo'"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = {}; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"drv\"; }",
Expand Down Expand Up @@ -1279,17 +1279,17 @@ namespace nix {
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ {} ]; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating an element of the argument list"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ \"a\" {} ]; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating an element of the argument list"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; FOO = {}; }",
TypeError,
hintfmt("cannot coerce %s to a string", "a set"),
hintfmt("cannot coerce %s to a string: %s", "a set", "{ }"),
hintfmt("while evaluating the attribute 'FOO' of derivation 'foo'"));
}
Expand Down

0 comments on commit c60e63e

Please sign in to comment.