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 12, 2024
1 parent 52f949b commit 04bb0d0
Show file tree
Hide file tree
Showing 15 changed files with 254 additions and 130 deletions.
54 changes: 54 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,54 @@
synopsis: Type errors include the failing value
issues: #561
prs: #9554
description: {

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

Before:

```
error:
… while calling the 'splitVersion' builtin
at bad.nix:5:3:
4| in
5| builtins.splitVersion pkgs.haskell.compiler
| ^
6|
… while evaluating the first argument passed to builtins.splitVersion
at «none»:0: (source not available)
error: value is a set while a string was expected
```

After:

```
error:
… while calling the 'splitVersion' builtin
at bad.nix:5:3:
4| in
5| builtins.splitVersion pkgs.haskell.compiler
| ^
6|
… while evaluating the first argument passed to builtins.splitVersion
error: value is a set while a string was expected: { ghc810 = <CODE>;
ghc8102Binary = <CODE>; ghc8102BinaryMinimal = <CODE>; ghc8107 = <CODE>;
ghc8107Binary = <CODE>; ghc8107BinaryMinimal = <CODE>; ghc865Binary =
<CODE>; ghc88 = <CODE>; ghc884 = <CODE>; ghc90 = <CODE>; ghc902 =
<CODE>; ghc92 = <CODE>; ghc924 = <CODE>; ghc924Binary = <CODE>;
ghc924BinaryMinimal = <CODE>; ghc94 = <CODE>; ghc942 = <CODE>; ghcHEAD =
<CODE>; ghcjs = <CODE>; ghcjs810 = <CODE>; integer-simple = <CODE>;
native-bignum = <CODE>; }
```

}
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("value is %1% while a set was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a list was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a Boolean was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a set was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while an integer was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a float was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a Boolean was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a function was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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("value is %1% while a string was expected: %2%",
showType(v),
ValuePrinter(*this, v, debugPrintOptions))
.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
14 changes: 14 additions & 0 deletions src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,18 @@ struct PrintOptions
size_t maxStringLength = std::numeric_limits<size_t>::max();
};

/**
* `PrintOptions` suitable for debugging.
*
* These options are used for printing values in error messages without
* printing "too much" output.
*/
static PrintOptions debugPrintOptions = 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);
}
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/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: value is an integer while a string was expected: 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: value is an integer while a list was expected: 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: value is an integer while a list was expected: 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: value is an integer while a set was expected: 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: value is a set while a string was expected: { }
Loading

0 comments on commit 04bb0d0

Please sign in to comment.