Merge pull request #9754 from 9999years/print-value-when-coercion-fails

Print the value in `error: cannot coerce` messages

(cherry picked from commit 5b7bfd2d6b89d7dd5f54c1ca6c8072358d31a84e)

===

test taken from 6e8d5983143ae576e3f4b1d2954a5267f2943a49; it was added
previously (and not backported because its pr was a mostly-revert), but
it's useful to have around.

Change-Id: Icbd14b55e3610ce7b774667bf14b82e6dc717982
This commit is contained in:
eldritch horrors 2024-03-08 05:06:09 +01:00
parent 2f7c3fa251
commit 609a8e0d94
10 changed files with 84 additions and 29 deletions

View file

@ -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»}
```

View file

@ -2279,7 +2279,9 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString); return std::move(*maybeString);
auto i = v.attrs->find(sOutPath); auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) { 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) .withTrace(pos, errorCtx)
.debugThrow<TypeError>(); .debugThrow<TypeError>();
} }
@ -2325,7 +2327,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) .withTrace(pos, errorCtx)
.debugThrow<TypeError>(); .debugThrow<TypeError>();
} }
@ -2657,7 +2661,7 @@ void EvalState::printStatistics()
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{ {
throw TypeError({ throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType()) .msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this)
}); });
} }

View file

@ -36,11 +36,17 @@ struct PrintOptions
*/ */
size_t maxDepth = std::numeric_limits<size_t>::max(); 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(); size_t maxAttrs = std::numeric_limits<size_t>::max();
/** /**
* Maximum number of list items to print. * 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(); size_t maxListItems = std::numeric_limits<size_t>::max();
/** /**

View file

@ -18,7 +18,7 @@ void printElided(
{ {
if (ansiColors) if (ansiColors)
output << ANSI_FAINT; output << ANSI_FAINT;
output << " «"; output << "«";
pluralize(output, value, single, plural); pluralize(output, value, single, plural);
output << " elided»"; output << " elided»";
if (ansiColors) if (ansiColors)
@ -35,7 +35,7 @@ printLiteralString(std::ostream & str, const std::string_view string, size_t max
str << "\""; str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) { for (auto i = string.begin(); i != string.end(); ++i) {
if (charsPrinted >= maxLength) { if (charsPrinted >= maxLength) {
str << "\""; str << "\" ";
printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors); printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
return str; return str;
} }
@ -159,6 +159,8 @@ private:
EvalState & state; EvalState & state;
PrintOptions options; PrintOptions options;
std::optional<ValuesSeen> seen; std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0;
size_t listItemsPrinted = 0;
void printRepeated() void printRepeated()
{ {
@ -277,7 +279,6 @@ private:
else else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp()); std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
size_t attrsPrinted = 0;
for (auto & i : sorted) { for (auto & i : sorted) {
if (attrsPrinted >= options.maxAttrs) { if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes"); printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
@ -305,7 +306,6 @@ private:
output << "[ "; output << "[ ";
if (depth < options.maxDepth) { if (depth < options.maxDepth) {
size_t listItemsPrinted = 0;
for (auto elem : v.listItems()) { for (auto elem : v.listItems()) {
if (listItemsPrinted >= options.maxListItems) { if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items"); printElided(v.listSize() - listItemsPrinted, "item", "items");
@ -484,6 +484,9 @@ public:
void print(Value & v) void print(Value & v)
{ {
attrsPrinted = 0;
listItemsPrinted = 0;
if (options.trackRepeated) { if (options.trackRepeated) {
seen.emplace(); seen.emplace();
} else { } else {

View file

@ -5,4 +5,4 @@ error:
| ^ | ^
2| 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»

View file

@ -5,4 +5,4 @@ error:
| ^ | ^
2| 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»

View file

@ -0,0 +1,9 @@
error:
… while evaluating a path segment
at /pwd/lang/eval-fail-bad-string-interpolation-4.nix:9:3:
8| # The error message should not be too long.
9| ''${pkgs}''
| ^
10|
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»}

View file

@ -0,0 +1,9 @@
let
# Basically a "billion laughs" attack, but toned down to simulated `pkgs`.
ha = x: y: { a = x y; b = x y; c = x y; d = x y; e = x y; f = x y; g = x y; h = x y; j = x y; };
has = ha (ha (ha (ha (x: x)))) "ha";
# A large structure that has already been evaluated.
pkgs = builtins.deepSeq has has;
in
# The error message should not be too long.
''${pkgs}''

View file

@ -295,7 +295,7 @@ namespace nix {
TEST_F(ErrorTraceTest, toPath) { TEST_F(ErrorTraceTest, toPath) {
ASSERT_TRACE2("toPath []", ASSERT_TRACE2("toPath []",
TypeError, 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")); hintfmt("while evaluating the first argument passed to builtins.toPath"));
ASSERT_TRACE2("toPath \"foo\"", ASSERT_TRACE2("toPath \"foo\"",
@ -309,7 +309,7 @@ namespace nix {
TEST_F(ErrorTraceTest, storePath) { TEST_F(ErrorTraceTest, storePath) {
ASSERT_TRACE2("storePath true", ASSERT_TRACE2("storePath true",
TypeError, TypeError,
hintfmt("cannot coerce %s to a string", "a Boolean"), hintfmt("cannot coerce %s to a string: %s", "a Boolean", ANSI_CYAN "true" ANSI_NORMAL),
hintfmt("while evaluating the first argument passed to builtins.storePath")); hintfmt("while evaluating the first argument passed to builtins.storePath"));
} }
@ -318,7 +318,7 @@ namespace nix {
TEST_F(ErrorTraceTest, pathExists) { TEST_F(ErrorTraceTest, pathExists) {
ASSERT_TRACE2("pathExists []", ASSERT_TRACE2("pathExists []",
TypeError, 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")); hintfmt("while realising the context of a path"));
ASSERT_TRACE2("pathExists \"zorglub\"", ASSERT_TRACE2("pathExists \"zorglub\"",
@ -332,7 +332,7 @@ namespace nix {
TEST_F(ErrorTraceTest, baseNameOf) { TEST_F(ErrorTraceTest, baseNameOf) {
ASSERT_TRACE2("baseNameOf []", ASSERT_TRACE2("baseNameOf []",
TypeError, 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")); hintfmt("while evaluating the first argument passed to builtins.baseNameOf"));
} }
@ -377,7 +377,7 @@ namespace nix {
TEST_F(ErrorTraceTest, filterSource) { TEST_F(ErrorTraceTest, filterSource) {
ASSERT_TRACE2("filterSource [] []", ASSERT_TRACE2("filterSource [] []",
TypeError, 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")); hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
ASSERT_TRACE2("filterSource [] \"foo\"", ASSERT_TRACE2("filterSource [] \"foo\"",
@ -1038,7 +1038,7 @@ namespace nix {
TEST_F(ErrorTraceTest, toString) { TEST_F(ErrorTraceTest, toString) {
ASSERT_TRACE2("toString { a = 1; }", ASSERT_TRACE2("toString { a = 1; }",
TypeError, TypeError,
hintfmt("cannot coerce %s to a string", "a set"), hintfmt("cannot coerce %s to a string: %s", "a set", "{ a = " ANSI_CYAN "1" ANSI_NORMAL "; }"),
hintfmt("while evaluating the first argument passed to builtins.toString")); hintfmt("while evaluating the first argument passed to builtins.toString"));
} }
@ -1057,7 +1057,7 @@ namespace nix {
ASSERT_TRACE2("substring 0 3 {}", ASSERT_TRACE2("substring 0 3 {}",
TypeError, 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")); hintfmt("while evaluating the third argument (the string) passed to builtins.substring"));
ASSERT_TRACE1("substring (-3) 3 \"sometext\"", ASSERT_TRACE1("substring (-3) 3 \"sometext\"",
@ -1070,7 +1070,7 @@ namespace nix {
TEST_F(ErrorTraceTest, stringLength) { TEST_F(ErrorTraceTest, stringLength) {
ASSERT_TRACE2("stringLength {} # TODO: context is missing ???", ASSERT_TRACE2("stringLength {} # TODO: context is missing ???",
TypeError, 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")); hintfmt("while evaluating the argument passed to builtins.stringLength"));
} }
@ -1143,7 +1143,7 @@ namespace nix {
ASSERT_TRACE2("concatStringsSep \"foo\" [ 1 2 {} ] # TODO: coerce to string is buggy", ASSERT_TRACE2("concatStringsSep \"foo\" [ 1 2 {} ] # TODO: coerce to string is buggy",
TypeError, TypeError,
hintfmt("cannot coerce %s to a string", "an integer"), hintfmt("cannot coerce %s to a string: %s", "an integer", ANSI_CYAN "1" ANSI_NORMAL),
hintfmt("while evaluating one element of the list of strings to concat passed to builtins.concatStringsSep")); hintfmt("while evaluating one element of the list of strings to concat passed to builtins.concatStringsSep"));
} }
@ -1229,12 +1229,12 @@ namespace nix {
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = {}; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = {}; }",
TypeError, 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'")); hintfmt("while evaluating the attribute 'system' of derivation 'foo'"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = {}; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = {}; }",
TypeError, 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'")); hintfmt("while evaluating the attribute 'outputs' of derivation 'foo'"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"drv\"; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"drv\"; }",
@ -1279,17 +1279,17 @@ namespace nix {
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ {} ]; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ {} ]; }",
TypeError, 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")); hintfmt("while evaluating an element of the argument list"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ \"a\" {} ]; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; args = [ \"a\" {} ]; }",
TypeError, 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")); hintfmt("while evaluating an element of the argument list"));
ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; FOO = {}; }", ASSERT_TRACE2("derivationStrict { name = \"foo\"; builder = 1; system = 1; outputs = \"out\"; FOO = {}; }",
TypeError, 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'")); hintfmt("while evaluating the attribute 'FOO' of derivation 'foo'"));
} }

View file

@ -370,7 +370,7 @@ TEST_F(ValuePrintingTests, ansiColorsStringElided)
v.mkString("puppy"); v.mkString("puppy");
test(v, test(v,
ANSI_MAGENTA "\"pup\"" ANSI_FAINT " «2 bytes elided»" ANSI_NORMAL, ANSI_MAGENTA "\"pup\" " ANSI_FAINT "«2 bytes elided»" ANSI_NORMAL,
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxStringLength = 3 .maxStringLength = 3
@ -756,7 +756,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «1 attribute elided»" ANSI_NORMAL "}", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL "}",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxAttrs = 1 .maxAttrs = 1
@ -769,7 +769,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «2 attributes elided»" ANSI_NORMAL "}", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL "}",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxAttrs = 1 .maxAttrs = 1
@ -793,7 +793,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 2; vList.bigList.size = 2;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «1 item elided»" ANSI_NORMAL "]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL "]",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1
@ -806,7 +806,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
vList.bigList.size = 3; vList.bigList.size = 3;
test(vList, test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «2 items elided»" ANSI_NORMAL "]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL "]",
PrintOptions { PrintOptions {
.ansiColors = true, .ansiColors = true,
.maxListItems = 1 .maxListItems = 1