Skip to content

Commit

Permalink
Print the value in error: cannot coerce messages (#9553)
Browse files Browse the repository at this point in the history
* Print the value in `error: cannot coerce` messages

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.

Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
  • Loading branch information
9999years and fricklerhandwerk authored Dec 8, 2023
1 parent 1399829 commit f0ac2a3
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 21 deletions.
50 changes: 50 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,50 @@
synopsis: Coercion errors include the failing value
issues: #561
prs: #9553
description: {

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

Previously, a failed string coercion produced a confusing error message if the trace didn't show where the offending value was defined:

```bash
$ nix-instantiate --eval --expr '
let x = { a = 1; }; in
"${x}"
'
error:
while evaluating a path segment

at «string»:4:2:

3|
4| "${x}"
| ^
5|

error: cannot coerce a set to a string
```
Now, the error message includes the value itself:
```bash
$ nix-instantiate --eval --expr '
let x = { a = 1; }; in
"${x}"
'
error:
while evaluating a path segment

at «string»:4:2:

3|
4| "${x}"
| ^
5|

error: cannot coerce a set to a string: { a = 1; }
```
}
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 f0ac2a3

Please sign in to comment.