Skip to content

Commit

Permalink
Print value on type error
Browse files Browse the repository at this point in the history
Adds the failing value to `value is <TYPE> while a <TYPE> is expected`
error messages.
  • Loading branch information
9999years committed Jan 20, 2024
1 parent 382fa51 commit 1c1225a
Show file tree
Hide file tree
Showing 19 changed files with 227 additions and 136 deletions.
23 changes: 23 additions & 0 deletions doc/manual/rl-next/print-value-in-type-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
synopsis: Type errors include the failing value
issues: #561
prs: #9753
---

In errors like `value is an integer while a list was expected`, the message now
includes the failing value.

Before:

```
error: value is a set while a string was expected
```

After:

```
error: expected a string but found a set: { ghc810 = «thunk»;
ghc8102Binary = «thunk»; ghc8107 = «thunk»; ghc8107Binary = «thunk»;
ghc865Binary = «thunk»; ghc90 = «thunk»; ghc902 = «thunk»; ghc92 = «thunk»;
ghc924Binary = «thunk»; ghc925 = «thunk»; «17 attributes elided»}
```
2 changes: 1 addition & 1 deletion doc/manual/rl-next/source-positions-in-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ error:
| ^
5|
error: value is a set while a string was expected
error: expected a string but found a set
```
4 changes: 2 additions & 2 deletions doc/manual/rl-next/with-error-reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ prs: 9658
Previously an incorrect `with` expression would report no position at all, making it hard to determine where the error originated:

```
nix-repl> with 1; a
nix-repl> with 1; a
error:
… <borked>
Expand All @@ -27,5 +27,5 @@ error:
1| with 1; a
| ^
error: value is an integer while a set was expected
error: expected a set but found an integer
```
11 changes: 9 additions & 2 deletions src/libexpr/eval-inline.hh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
///@file

#include "print.hh"
#include "eval.hh"

namespace nix {
Expand Down Expand Up @@ -114,7 +115,10 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
PosIdx pos = getPos();
forceValue(v, pos);
if (v.type() != nAttrs) {
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
error("expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx).debugThrow<TypeError>();
}
}

Expand All @@ -124,7 +128,10 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
{
forceValue(v, pos);
if (!v.isList()) {
error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
error("expected a list but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx).debugThrow<TypeError>();
}
}

Expand Down
38 changes: 30 additions & 8 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "eval-settings.hh"
#include "hash.hh"
#include "primops.hh"
#include "print-options.hh"
#include "types.hh"
#include "util.hh"
#include "store-api.hh"
Expand Down Expand Up @@ -29,9 +30,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 @@ -1153,7 +1154,10 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
Expand All @@ -1167,7 +1171,10 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
try {
e->eval(*this, env, v);
if (v.type() != nAttrs)
error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
error("expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
Expand Down Expand Up @@ -2076,7 +2083,10 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
try {
forceValue(v, pos);
if (v.type() != nInt)
error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
error("expected an integer but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.integer;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
Expand All @@ -2092,7 +2102,10 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
if (v.type() == nInt)
return v.integer;
else if (v.type() != nFloat)
error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
error("expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
Expand All @@ -2106,7 +2119,10 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
try {
forceValue(v, pos);
if (v.type() != nBool)
error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
Expand All @@ -2126,7 +2142,10 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro
try {
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
error("expected a function but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
Expand All @@ -2139,7 +2158,10 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
try {
forceValue(v, pos);
if (v.type() != nString)
error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
error("expected a string but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.string_view();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
if (args[0]->type() == nString)
printError("trace: %1%", args[0]->string_view());
else
printError("trace: %1%", printValue(state, *args[0]));
printError("trace: %1%", ValuePrinter(state, *args[0]));
state.forceValue(*args[1], pos);
v = *args[1];
}
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
12 changes: 12 additions & 0 deletions src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,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
};

}
7 changes: 7 additions & 0 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 Down Expand Up @@ -501,4 +502,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);
}
4 changes: 2 additions & 2 deletions src/libutil/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* try {
* e->eval(*this, env, v);
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* throwTypeError("expected a set but found %1%", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
Expand All @@ -349,7 +349,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* e->eval(*this, env, v);
* try {
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* throwTypeError("expected a set but found %1%", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
Expand Down
2 changes: 1 addition & 1 deletion src/nix/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption

else {
state->forceValueDeep(*v);
logger->cout("%s", printValue(*state, *v));
logger->cout("%s", ValuePrinter(*state, *v, PrintOptions { .force = true }));
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/dyn-drv/eval-outputOf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ nix --experimental-features 'nix-command' eval --impure --expr \
# resolve first. Adding a test so we don't liberalise it by accident.
expectStderr 1 nix --experimental-features 'nix-command dynamic-derivations' eval --impure --expr \
'builtins.outputOf (import ../dependencies.nix {}) "out"' \
| grepQuiet "value is a set while a string was expected"
| grepQuiet "expected a string but found a set"

# Test that "DrvDeep" string contexts are not supported at this time
#
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/lang/eval-fail-attr-name-type.err.exp
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ error:
| ^
8|

error: value is an integer while a string was expected
error: expected a string but found an integer: 1
2 changes: 1 addition & 1 deletion tests/functional/lang/eval-fail-call-primop.err.exp
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ error:

… while evaluating the first argument passed to builtins.length

error: value is an integer while a list was expected
error: expected a list but found an integer: 1
2 changes: 1 addition & 1 deletion tests/functional/lang/eval-fail-list.err.exp
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ error:
| ^
2|

error: value is an integer while a list was expected
error: expected a list but found an integer: 8
2 changes: 1 addition & 1 deletion tests/functional/lang/eval-fail-set-override.err.exp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error:
… while evaluating the `__overrides` attribute

error: value is an integer while a set was expected
error: expected a set but found an integer: 1
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ error:
| ^
6|

error: value is a set while a string was expected
error: expected a string but found a set: { }
Loading

0 comments on commit 1c1225a

Please sign in to comment.