libexpr: misc improvements for proper error position

When working on some more complex Nix code, there are sometimes rather
unhelpful or misleading error messages, especially if coerce-errors are
thrown.

This patch is a first steps towards improving that. I'm happy to file
more changes after that, but I'd like to gather some feedback first.

To summarize, this patch does the following things:

* Attrsets (a.k.a. `Bindings` in `libexpr`) now have a `Pos`. This is
  helpful e.g. to identify which attribute-set in `listToAttrs` is
  invalid.

* The `Value`-struct has a new method named `determinePos` which tries
  to guess the position of a value and falls back to a default if that's
  not possible.

  This can be used to provide better messages if a coercion fails.

* The new `determinePos`-API is used by `builtins.concatMap` now. With
  that change, Nix shows the exact position in the error where a wrong
  value was returned by the lambda.

  To make sure it's still obvious that `concatMap` is the problem,
  another stack-frame was added.

* The changes described above can be added to every other `primop`, but
  first I'd like to get some feedback about the overall approach.
This commit is contained in:
Maximilian Bosch 2021-01-08 22:27:00 +01:00
parent 3550a32b25
commit 7c76964daa
No known key found for this signature in database
GPG key ID: 091DBF4D1FC46B8E
6 changed files with 39 additions and 12 deletions

View file

@ -35,6 +35,7 @@ class Bindings
{ {
public: public:
typedef uint32_t size_t; typedef uint32_t size_t;
Pos *pos;
private: private:
size_t size_, capacity_; size_t size_, capacity_;

View file

@ -201,6 +201,15 @@ string showType(const Value & v)
} }
} }
Pos Value::determinePos(const Pos &pos) const
{
switch (internalType) {
case tAttrs: return *attrs->pos;
case tLambda: return lambda.fun->pos;
case tApp: return app.left->determinePos(pos);
default: return pos;
}
}
bool Value::isTrivial() const bool Value::isTrivial() const
{ {
@ -1060,6 +1069,8 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos)); v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), &i.pos));
v.attrs->sort(); // FIXME: inefficient v.attrs->sort(); // FIXME: inefficient
} }
v.attrs->pos = &pos;
} }

View file

@ -180,6 +180,7 @@ struct ExprOpHasAttr : Expr
struct ExprAttrs : Expr struct ExprAttrs : Expr
{ {
bool recursive; bool recursive;
Pos pos;
struct AttrDef { struct AttrDef {
bool inherited; bool inherited;
Expr * e; Expr * e;
@ -199,7 +200,8 @@ struct ExprAttrs : Expr
}; };
typedef std::vector<DynamicAttrDef> DynamicAttrDefs; typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs; DynamicAttrDefs dynamicAttrs;
ExprAttrs() : recursive(false) { }; ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false), pos(noPos) { };
COMMON_METHODS COMMON_METHODS
}; };

View file

@ -478,7 +478,7 @@ binds
$$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)); $$->attrs[i.symbol] = ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data));
} }
} }
| { $$ = new ExprAttrs; } | { $$ = new ExprAttrs(makeCurPos(@0, data)); }
; ;
attrs attrs

View file

@ -2149,21 +2149,27 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
state.forceAttrs(v2, pos); state.forceAttrs(v2, pos);
Bindings::iterator j = v2.attrs->find(state.sName); Bindings::iterator j = v2.attrs->find(state.sName);
if (j == v2.attrs->end()) if (j == v2.attrs->end()) {
throw TypeError({ auto e = TypeError({
.msg = hintfmt("'name' attribute missing in a call to 'listToAttrs'"), .msg = hintfmt("'name' attribute missing for 'listToAttrs'"),
.errPos = pos .errPos = *v2.attrs->pos
}); });
string name = state.forceStringNoCtx(*j->value, pos); e.addTrace(pos, hintfmt("while invoking '%s'", "listToAttrs"));
throw e;
}
string name = state.forceStringNoCtx(*j->value, *j->pos);
Symbol sym = state.symbols.create(name); Symbol sym = state.symbols.create(name);
if (seen.insert(sym).second) { if (seen.insert(sym).second) {
Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue)); Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
if (j2 == v2.attrs->end()) if (j2 == v2.attrs->end()) {
throw TypeError({ auto e = TypeError({
.msg = hintfmt("'value' attribute missing in a call to 'listToAttrs'"), .msg = hintfmt("'value' attribute missing for 'listToAttrs'"),
.errPos = pos .errPos = *v2.attrs->pos
}); });
e.addTrace(pos, hintfmt("while invoking '%s'", "listToAttrs"));
throw e;
}
v.attrs->push_back(Attr(sym, j2->value, j2->pos)); v.attrs->push_back(Attr(sym, j2->value, j2->pos));
} }
} }
@ -2804,7 +2810,12 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V
for (unsigned int n = 0; n < nrLists; ++n) { for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n]; Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos); state.callFunction(*args[0], *vElem, lists[n], pos);
state.forceList(lists[n], pos); try {
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)));
} catch (TypeError &e) {
e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap"));
throw e;
}
len += lists[n].listSize(); len += lists[n].listSize();
} }

View file

@ -341,6 +341,8 @@ public:
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size; return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
} }
Pos determinePos(const Pos &pos) const;
/* Check whether forcing this value requires a trivial amount of /* Check whether forcing this value requires a trivial amount of
computation. In particular, function applications are computation. In particular, function applications are
non-trivial. */ non-trivial. */