Skip to content

Commit

Permalink
Merge pull request #9555 from 9999years/positions-in-errors
Browse files Browse the repository at this point in the history
Pass positions when evaluating
  • Loading branch information
roberth authored Dec 9, 2023
2 parents 7cdc878 + b9980b3 commit c8458bd
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 14 deletions.
43 changes: 43 additions & 0 deletions doc/manual/rl-next/source-positions-in-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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
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
```

}
12 changes: 7 additions & 5 deletions src/libexpr/eval-inline.hh
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ void EvalState::forceValue(Value & v, Callable getPos)
throw;
}
}
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isApp()) {
PosIdx pos = getPos();
callFunction(*v.app.left, *v.app.right, v, pos);
}
else if (v.isBlackhole())
error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
}
Expand All @@ -121,9 +123,9 @@ template <typename Callable>
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
{
forceValue(v, noPos);
PosIdx pos = getPos();
forceValue(v, pos);
if (v.type() != nAttrs) {
PosIdx pos = getPos();
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
}
Expand All @@ -132,7 +134,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
[[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{
forceValue(v, noPos);
forceValue(v, pos);
if (!v.isList()) {
error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
Expand Down
18 changes: 9 additions & 9 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
} else {
Value 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());
}
}
Expand Down Expand Up @@ -1458,7 +1458,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
e->eval(state, env, vTmp);

for (auto & i : attrPath) {
state.forceValue(*vAttrs, noPos);
state.forceValue(*vAttrs, getPos());
Bindings::iterator j;
auto name = getName(i, state, env);
if (vAttrs->type() != nAttrs ||
Expand Down Expand Up @@ -1627,7 +1627,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (countCalls) primOpCalls[name]++;

try {
vCur.primOp->fun(*this, noPos, args, vCur);
vCur.primOp->fun(*this, vCur.determinePos(noPos), args, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
Expand Down Expand Up @@ -1675,7 +1675,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
// 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)
// 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) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
Expand Down Expand Up @@ -1783,7 +1783,7 @@ values, or passed explicitly with '--arg' or '--argstr'. See
}
}

callFunction(fun, allocValue()->mkAttrs(attrs), res, noPos);
callFunction(fun, allocValue()->mkAttrs(attrs), res, pos);
}


Expand Down Expand Up @@ -1819,7 +1819,7 @@ void ExprAssert::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: !
}


Expand Down Expand Up @@ -2260,7 +2260,7 @@ BackedStringView EvalState::coerceToString(
std::string result;
for (auto [n, v2] : enumerate(v.listItems())) {
try {
result += *coerceToString(noPos, *v2, context,
result += *coerceToString(pos, *v2, context,
"while evaluating one element of the list",
coerceMore, copyToStore, canonicalizePath);
} catch (Error & e) {
Expand Down Expand Up @@ -2407,8 +2407,8 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &

bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{
forceValue(v1, noPos);
forceValue(v2, noPos);
forceValue(v1, pos);
forceValue(v2, pos);

/* !!! Hack to support some old broken code that relies on pointer
equality tests between sets. (Specifically, builderDefs calls
Expand Down
1 change: 1 addition & 0 deletions src/libexpr/nixexpr.hh
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ struct ExprOpNot : Expr
{
Expr * e;
ExprOpNot(Expr * e) : e(e) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};

Expand Down
20 changes: 20 additions & 0 deletions tests/functional/lang/eval-fail-attr-name-type.err.exp
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions tests/functional/lang/eval-fail-attr-name-type.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let
attrs = {
puppy.doggy = {};
};
key = 1;
in
attrs.puppy.${key}
12 changes: 12 additions & 0 deletions tests/functional/lang/eval-fail-call-primop.err.exp
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/functional/lang/eval-fail-call-primop.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
builtins.length 1
18 changes: 18 additions & 0 deletions tests/functional/lang/eval-fail-not-throws.err.exp
Original file line number Diff line number Diff line change
@@ -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!
1 change: 1 addition & 0 deletions tests/functional/lang/eval-fail-not-throws.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
! (throw "uh oh!")
11 changes: 11 additions & 0 deletions tests/functional/lang/eval-fail-using-set-as-attr-name.err.exp
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions tests/functional/lang/eval-fail-using-set-as-attr-name.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let
attr = {foo = "bar";};
key = {};
in
attr.${key}

0 comments on commit c8458bd

Please sign in to comment.