Pass positions when evaluating

This includes position information in more places, making debugging
easier.

Before:

```
$ nix-instantiate --show-trace --eval tests/functional/lang/eval-fail-using-set-as-attr-name.nix
error:
       … while evaluating an attribute name

         at «none»:0: (source not available)

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

After:

```
error:
       … while evaluating an attribute name

         at /pwd/lang/eval-fail-using-set-as-attr-name.nix:5:10:

            4| in
            5|   attr.${key}
             |          ^
            6|

       error: value is a set while a string was expected
```
This commit is contained in:
Rebecca Turner 2023-12-07 10:01:42 -08:00
parent 3dcb83409d
commit 0b80935c22
No known key found for this signature in database
12 changed files with 137 additions and 14 deletions

View file

@ -0,0 +1,45 @@
synopsis: Source locations are printed more consistently in errors
issues: #561
prs: #9555
description: {
Source location information is now included in error messages more
consistently. Given this code:
```nix
let
attr = {foo = "bar";};
key = {};
in
attr.${key}
```
Previously, Nix would show this unhelpful message when attempting to evaluate
it:
```
error:
… while evaluating an attribute name
at «none»:0: (source not available)
error: value is a set while a string was expected
```
Now, the error message displays where the problematic value was found:
```
error:
… while evaluating an attribute name
at bad.nix:4:11:
3| key = {};
4| in attr.${key}
| ^
5|
error: value is a set while a string was expected
```
}

View file

@ -103,8 +103,10 @@ void EvalState::forceValue(Value & v, Callable getPos)
throw; throw;
} }
} }
else if (v.isApp()) else if (v.isApp()) {
callFunction(*v.app.left, *v.app.right, v, noPos); PosIdx pos = getPos();
callFunction(*v.app.left, *v.app.right, v, pos);
}
else if (v.isBlackhole()) else if (v.isBlackhole())
error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>(); error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
} }
@ -121,9 +123,9 @@ template <typename Callable>
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx) inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
{ {
forceValue(v, noPos);
if (v.type() != nAttrs) {
PosIdx pos = getPos(); 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", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
} }
} }
@ -132,7 +134,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx) inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, noPos); forceValue(v, pos);
if (!v.isList()) { 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", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
} }

View file

@ -344,7 +344,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
} else { } else {
Value nameValue; Value nameValue;
name.expr->eval(state, env, nameValue); name.expr->eval(state, env, nameValue);
state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name"); state.forceStringNoCtx(nameValue, name.expr->getPos(), "while evaluating an attribute name");
return state.symbols.create(nameValue.string_view()); return state.symbols.create(nameValue.string_view());
} }
} }
@ -1514,7 +1514,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
e->eval(state, env, vTmp); e->eval(state, env, vTmp);
for (auto & i : attrPath) { for (auto & i : attrPath) {
state.forceValue(*vAttrs, noPos); state.forceValue(*vAttrs, getPos());
Bindings::iterator j; Bindings::iterator j;
auto name = getName(i, state, env); auto name = getName(i, state, env);
if (vAttrs->type() != nAttrs || if (vAttrs->type() != nAttrs ||
@ -1683,7 +1683,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (countCalls) primOpCalls[name]++; if (countCalls) primOpCalls[name]++;
try { try {
vCur.primOp->fun(*this, noPos, args, vCur); vCur.primOp->fun(*this, vCur.determinePos(noPos), args, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name); addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw; throw;
@ -1731,7 +1731,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
// 1. Unify this and above code. Heavily redundant. // 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc) // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin. // so the debugger allows to inspect the wrong parameters passed to the builtin.
primOp->primOp->fun(*this, noPos, vArgs, vCur); primOp->primOp->fun(*this, vCur.determinePos(noPos), vArgs, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name); addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw; throw;
@ -1839,7 +1839,7 @@ https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbo
} }
} }
callFunction(fun, allocValue()->mkAttrs(attrs), res, noPos); callFunction(fun, allocValue()->mkAttrs(attrs), res, pos);
} }
@ -1875,7 +1875,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: ! v.mkBool(!state.evalBool(env, e, getPos(), "in the argument of the not operator")); // XXX: FIXME: !
} }
@ -2316,7 +2316,7 @@ BackedStringView EvalState::coerceToString(
std::string result; std::string result;
for (auto [n, v2] : enumerate(v.listItems())) { for (auto [n, v2] : enumerate(v.listItems())) {
try { try {
result += *coerceToString(noPos, *v2, context, result += *coerceToString(pos, *v2, context,
"while evaluating one element of the list", "while evaluating one element of the list",
coerceMore, copyToStore, canonicalizePath); coerceMore, copyToStore, canonicalizePath);
} catch (Error & e) { } catch (Error & e) {
@ -2463,8 +2463,8 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx) bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v1, noPos); forceValue(v1, pos);
forceValue(v2, noPos); forceValue(v2, pos);
/* !!! Hack to support some old broken code that relies on pointer /* !!! Hack to support some old broken code that relies on pointer
equality tests between sets. (Specifically, builderDefs calls equality tests between sets. (Specifically, builderDefs calls

View file

@ -405,6 +405,7 @@ struct ExprOpNot : Expr
{ {
Expr * e; Expr * e;
ExprOpNot(Expr * e) : e(e) { }; ExprOpNot(Expr * e) : e(e) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS COMMON_METHODS
}; };

View file

@ -0,0 +1,20 @@
error:
… while evaluating the attribute 'puppy."${key}"'
at /pwd/lang/eval-fail-attr-name-type.nix:3:5:
2| attrs = {
3| puppy.doggy = {};
| ^
4| };
… while evaluating an attribute name
at /pwd/lang/eval-fail-attr-name-type.nix:7:17:
6| in
7| attrs.puppy.${key}
| ^
8|
error: value is an integer while a string was expected

View file

@ -0,0 +1,7 @@
let
attrs = {
puppy.doggy = {};
};
key = 1;
in
attrs.puppy.${key}

View file

@ -0,0 +1,12 @@
error:
… while calling the 'length' builtin
at /pwd/lang/eval-fail-call-primop.nix:1:1:
1| builtins.length 1
| ^
2|
… while evaluating the first argument passed to builtins.length
error: value is an integer while a list was expected

View file

@ -0,0 +1 @@
builtins.length 1

View file

@ -0,0 +1,18 @@
error:
… in the argument of the not operator
at /pwd/lang/eval-fail-not-throws.nix:1:4:
1| ! (throw "uh oh!")
| ^
2|
… while calling the 'throw' builtin
at /pwd/lang/eval-fail-not-throws.nix:1:4:
1| ! (throw "uh oh!")
| ^
2|
error: uh oh!

View file

@ -0,0 +1 @@
! (throw "uh oh!")

View file

@ -0,0 +1,11 @@
error:
… while evaluating an attribute name
at /pwd/lang/eval-fail-using-set-as-attr-name.nix:5:10:
4| in
5| attr.${key}
| ^
6|
error: value is a set while a string was expected

View file

@ -0,0 +1,5 @@
let
attr = {foo = "bar";};
key = {};
in
attr.${key}