From e6d07e0d89d964cde22894fca57a95177c085c8d Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 00:58:09 +0100 Subject: [PATCH] Refactor to use more traces and less string manipulations --- src/libexpr/eval-inline.hh | 35 ++- src/libexpr/eval.cc | 297 ++++++++++++--------- src/libexpr/eval.hh | 34 +-- src/libexpr/parser.y | 2 +- src/libexpr/primops.cc | 514 +++++++++++++++++-------------------- src/libexpr/value.hh | 2 +- src/libutil/error.cc | 23 +- src/libutil/error.hh | 10 +- 8 files changed, 475 insertions(+), 442 deletions(-) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index ad53a74cb..8980e8d4b 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -15,10 +15,10 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s)) }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, const std::string & s2)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) { throw TypeError({ - .msg = hintfmt(s, showType(v), s2), + .msg = hintfmt(s, showType(v)), .errPos = pos }); } @@ -52,26 +52,39 @@ void EvalState::forceValue(Value & v, Callable getPos) } -inline void EvalState::forceAttrs(Value & v, const Pos & pos, const std::string & errorCtx) +inline void EvalState::forceAttrs(Value & v, const Pos & pos, const std::string_view & errorCtx) { forceAttrs(v, [&]() { return pos; }, errorCtx); } template -inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string & errorCtx) +inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string_view & errorCtx) { - forceValue(v, getPos); - if (v.type() != nAttrs) - throwTypeError(getPos(), "%2%value is %1% while a set was expected", v, errorCtx); + try { + forceValue(v, noPos); + if (v.type() != nAttrs) { + throwTypeError(noPos, "value is %1% while a set was expected", v); + } + } catch (Error & e) { + Pos pos = getPos(); + e.addTrace(pos, errorCtx); + throw; + } } -inline void EvalState::forceList(Value & v, const Pos & pos, const std::string & errorCtx) +inline void EvalState::forceList(Value & v, const Pos & pos, const std::string_view & errorCtx) { - forceValue(v, pos); - if (!v.isList()) - throwTypeError(pos, "%2%value is %1% while a list was expected", v, errorCtx); + try { + forceValue(v, noPos); + if (!v.isList()) { + throwTypeError(noPos, "value is %1% while a list was expected", v); + } + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } /* Note: Various places expect the allocated memory to be zeroed. */ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 55c624cb9..33eeef902 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -694,9 +694,12 @@ std::optional EvalState::getDoc(Value & v) evaluator. So here are some helper functions for throwing exceptions. */ -LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2)) +LocalNoInlineNoReturn(void throwTypeErrorWithTrace(const Pos & pos, const char * s, const std::string & s2, const Symbol & sym, const Pos & p2, const std::string & s3)) { - throw EvalError(s, s2); + throw EvalError({ + .msg = hintfmt(s, s2, sym), + .errPos = pos + }).addTrace(p2, s3); } LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2)) @@ -707,23 +710,10 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const }); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3)) +LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const Value & v)) { - throw EvalError(s, s2, s3); -} - -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3)) -{ - throw EvalError({ - .msg = hintfmt(s, s2, s3), - .errPos = pos - }); -} - -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3, const std::string & s4)) -{ - throw EvalError({ - .msg = hintfmt(s, s2, s3, s4), + throw AssertionError({ + .msg = hintfmt(s, showType(v)), .errPos = pos }); } @@ -737,22 +727,6 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) -{ - throw TypeError({ - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos - }); -} - -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) -{ - throw AssertionError({ - .msg = hintfmt(s, showType(v)), - .errPos = pos - }); -} - LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1)) { throw AssertionError({ @@ -1040,21 +1014,31 @@ void EvalState::eval(Expr * e, Value & v) } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string & errorCtx) +inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string_view & errorCtx) { - Value v; - e->eval(*this, env, v); - if (v.type() != nBool) - throwTypeError(pos, "%2%value is %1% while a Boolean was expected", v, errorCtx); - return v.boolean; + try { + Value v; + e->eval(*this, env, v); + if (v.type() != nBool) + throw TypeError("value is %1% while a Boolean was expected", showType(v)); + return v.boolean; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } -inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & errorCtx) +inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) { - e->eval(*this, env, v); - if (v.type() != nAttrs) - throwTypeError(pos, "%2%value is %1% while a set was expected", v, errorCtx); + try { + e->eval(*this, env, v); + if (v.type() != nAttrs) + throw TypeError("value is %1% while a set was expected", showType(v)); + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } @@ -1297,6 +1281,10 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v) v.mkLambda(&env, this); } +const std::string prettyLambdaName(const ExprLambda & e) +{ + return e.name.set() ? std::string(e.name): "anonymous lambda"; +} void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos) { @@ -1336,7 +1324,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & env2.values[displ++] = args[0]; else { - forceAttrs(*args[0], pos, "While evaluating the value passed as argument to a function expecting an attribute set: "); + try { + forceAttrs(*args[0], lambda.pos, "While evaluating the value passed for this lambda parameter"); + } catch (Error & e) { + e.addTrace(pos, "from call site"); + throw; + } if (!lambda.arg.empty()) env2.values[displ++] = args[0]; @@ -1348,8 +1341,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & i : lambda.formals->formals) { auto j = args[0]->attrs->get(i.name); if (!j) { - if (!i.def) throwTypeError(pos, "Function %1% called without required argument '%2%'", - lambda, i.name); + if (!i.def) { + throwTypeErrorWithTrace(lambda.pos, + "Function '%1%' called without required argument '%2%'", prettyLambdaName(lambda), i.name, + pos, "from call site"); + } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { attrsUsed++; @@ -1363,8 +1359,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & /* Nope, so show the first unexpected argument to the user. */ for (auto & i : *args[0]->attrs) - if (!lambda.formals->has(i.name)) - throwTypeError(pos, "Function %1% called with unexpected argument '%2%'", lambda, i.name); + if (!lambda.formals->has(i.name)) { + throwTypeErrorWithTrace(lambda.pos, + "Function '%1%' called with unexpected argument '%2%'", prettyLambdaName(lambda), i.name, + pos, "from call site"); + } abort(); // can't happen } } @@ -1377,11 +1376,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & lambda.body->eval(*this, env2, vCur); } catch (Error & e) { if (loggerSettings.showTrace.get()) { - addErrorTrace(e, lambda.pos, "while evaluating %s", - (lambda.name.set() - ? "'" + (const std::string &) lambda.name + "'" - : "anonymous lambda")); - addErrorTrace(e, pos, "from call site%s", ""); + addErrorTrace(e, lambda.pos, "While evaluating the '%s' function", prettyLambdaName(lambda)); + if (pos) e.addTrace(pos, "from call site"); } throw; } @@ -1400,9 +1396,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & return; } else { /* We have all the arguments, so call the primop. */ + Symbol name = vCur.primOp->name; + nrPrimOpCalls++; - if (countCalls) primOpCalls[vCur.primOp->name]++; - vCur.primOp->fun(*this, pos, args, vCur); + if (countCalls) primOpCalls[name]++; + + try { + vCur.primOp->fun(*this, pos, args, vCur); + } catch (Error & e) { + addErrorTrace(e, pos, "While calling the '%1%' builtin", name); + throw; + } nrArgs -= argsLeft; args += argsLeft; @@ -1437,9 +1441,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (size_t i = 0; i < argsLeft; ++i) vArgs[argsDone + i] = args[i]; + Symbol name = primOp->primOp->name; nrPrimOpCalls++; - if (countCalls) primOpCalls[primOp->primOp->name]++; - primOp->primOp->fun(*this, pos, vArgs, vCur); + if (countCalls) primOpCalls[name]++; + + try { + primOp->primOp->fun(*this, noPos, vArgs, vCur); + } catch (Error & e) { + addErrorTrace(e, pos, "While calling the '%1%' builtin", name); + throw; + } nrArgs -= argsLeft; args += argsLeft; @@ -1452,8 +1463,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & heap-allocate a copy and use that instead. */ Value * args2[] = {allocValue(), args[0]}; *args2[0] = vCur; - /* !!! Should we use the attr pos here? */ - callFunction(*functor->value, 2, args2, vCur, pos); + try { + callFunction(*functor->value, 2, args2, vCur, *functor->pos); + } catch (Error & e) { + e.addTrace(pos, "While calling a functor (an attribute set with a 'functor' attribute)"); + throw; + } nrArgs--; args++; } @@ -1553,7 +1568,7 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v) { // We cheat in the parser, an pass the position of the condition as the position of the if itself. - (state.evalBool(env, cond, pos, "") ? then : else_)->eval(state, env, v); + (state.evalBool(env, cond, pos, "While evaluating a branch condition") ? then : else_)->eval(state, env, v); } @@ -1578,7 +1593,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v) { Value v1; e1->eval(state, env, v1); Value v2; e2->eval(state, env, v2); - v.mkBool(state.eqValues(v1, v2, pos, "")); + v.mkBool(state.eqValues(v1, v2, pos, "While testing two values for equality")); } @@ -1586,7 +1601,7 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v) { Value v1; e1->eval(state, env, v1); Value v2; e2->eval(state, env, v2); - v.mkBool(!state.eqValues(v1, v2, pos, "")); + v.mkBool(!state.eqValues(v1, v2, pos, "While testing two values for inequality")); } @@ -1655,7 +1670,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) } -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string & errorCtx) +void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string_view & errorCtx) { nrListConcats++; @@ -1739,20 +1754,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); + throwEvalError(i_pos, "cannot add %1% to an integer", vTmp); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); + throwEvalError(i_pos, "cannot add %1% to a float", vTmp); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not canonized in the first place if it's coming from a ./${foo} type path */ - auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, ""); + auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "While evaluating a path segment"); sSize += part->size(); s.emplace_back(std::move(part)); } @@ -1810,32 +1825,47 @@ void EvalState::forceValueDeep(Value & v) } -NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string & errorCtx) +NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string_view & errorCtx) { - forceValue(v, pos); - if (v.type() != nInt) - throwTypeError(pos, "%2%value is %1% while an integer was expected", v, errorCtx); - return v.integer; -} - - -NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string & errorCtx) -{ - forceValue(v, pos); - if (v.type() == nInt) + try { + forceValue(v, pos); + if (v.type() != nInt) + throw TypeError("value is %1% while an integer was expected", showType(v)); return v.integer; - else if (v.type() != nFloat) - throwTypeError(pos, "%2%value is %1% while a float was expected", v, errorCtx); - return v.fpoint; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } -bool EvalState::forceBool(Value & v, const Pos & pos, const std::string & errorCtx) +NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string_view & errorCtx) { - forceValue(v, pos); - if (v.type() != nBool) - throwTypeError(pos, "%2%value is %1% while a Boolean was expected", v, errorCtx); - return v.boolean; + try { + forceValue(v, pos); + if (v.type() == nInt) + return v.integer; + else if (v.type() != nFloat) + throw TypeError("value is %1% while a float was expected", showType(v)); + return v.fpoint; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } +} + + +bool EvalState::forceBool(Value & v, const Pos & pos, const std::string_view & errorCtx) +{ + try { + forceValue(v, pos); + if (v.type() != nBool) + throw TypeError("value is %1% while a Boolean was expected", showType(v)); + return v.boolean; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } @@ -1845,21 +1875,30 @@ bool EvalState::isFunctor(Value & fun) } -void EvalState::forceFunction(Value & v, const Pos & pos, const std::string & errorCtx) +void EvalState::forceFunction(Value & v, const Pos & pos, const std::string_view & errorCtx) { - forceValue(v, pos); - if (v.type() != nFunction && !isFunctor(v)) - throwTypeError(pos, "%2%value is %1% while a function was expected", v, errorCtx); + try { + forceValue(v, pos); + if (v.type() != nFunction && !isFunctor(v)) + throw TypeError("value is %1% while a function was expected", showType(v)); + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } } -std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::string & errorCtx) +std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::string_view & errorCtx) { - forceValue(v, pos); - if (v.type() != nString) { - throwTypeError(pos, "%2%value is %1% while a string was expected", v, errorCtx); + try { + forceValue(v, pos); + if (v.type() != nString) + throw TypeError("value is %1% while a string was expected", showType(v)); + return v.string.s; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; } - return v.string.s; } @@ -1894,7 +1933,7 @@ std::vector> Value::getContext() } -std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, const std::string & errorCtx) +std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, const std::string_view & errorCtx) { auto s = forceString(v, pos, errorCtx); copyContext(v, context); @@ -1902,18 +1941,21 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos } -std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, const std::string & errorCtx) +std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, const std::string_view & errorCtx) { - auto s = forceString(v, pos, errorCtx); - if (v.string.context) { - if (pos) - throwEvalError(pos, (errorCtx + ": the string '%1%' is not allowed to refer to a store path (such as '%2%')").c_str(), - v.string.s, v.string.context[0]); - else - throwEvalError((errorCtx + ": the string '%1%' is not allowed to refer to a store path (such as '%2%')").c_str(), - v.string.s, v.string.context[0]); + try { + auto s = forceString(v, pos, errorCtx); + if (v.string.context) { + if (pos) + throw EvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]); + else + throw EvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]); + } + return s; + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; } - return s; } @@ -1943,7 +1985,7 @@ std::optional EvalState::tryAttrsToString(const Pos & pos, Value & } BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, - bool coerceMore, bool copyToStore, bool canonicalizePath, const std::string & errorCtx) + bool coerceMore, bool copyToStore, bool canonicalizePath, const std::string_view & errorCtx) { forceValue(v, pos); @@ -1966,7 +2008,9 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & if (maybeString) return std::move(*maybeString); auto i = v.attrs->find(sOutPath); - if (i == v.attrs->end()) throwTypeError(pos, "%2%cannot coerce %1% to a string", v, errorCtx); + if (i == v.attrs->end()) { + throw TypeError("cannot coerce %1% to a string", showType(v)).addTrace(pos, errorCtx); + } return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -1986,8 +2030,13 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & if (v.isList()) { std::string result; for (auto [n, v2] : enumerate(v.listItems())) { - result += *coerceToString(pos, *v2, context, coerceMore, copyToStore, canonicalizePath, - errorCtx + ": While evaluating one element of the list"); + try { + result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath, + "While evaluating one element of the list"); + } catch (Error & e) { + e.addTrace(pos, errorCtx); + throw; + } if (n < v.listSize() - 1 /* !!! not quite correct */ && (!v2->isList() || v2->listSize() != 0)) @@ -1997,14 +2046,14 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } } - throwTypeError(pos, "%2%cannot coerce %1% to a string", v, errorCtx); + throw TypeError("cannot coerce %1% to a string", showType(v)).addTrace(pos, errorCtx); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throwEvalError("file names are not allowed to end in '%1%'", drvExtension); + throw EvalError("file names are not allowed to end in '%1%'", drvExtension); Path dstPath; auto i = srcToStore.find(path); @@ -2025,28 +2074,25 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path) } -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx) +Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx) { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throwEvalError(pos, "%2%string '%1%' doesn't represent an absolute path", path, errorCtx); + throw EvalError("string '%1%' doesn't represent an absolute path", path).addTrace(pos, errorCtx); return path; } -StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx) +StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx) { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - throw EvalError({ - .msg = hintfmt("%2%path '%1%' is not in the Nix store", path, errorCtx), - .errPos = pos - }); + throw EvalError("path '%1%' is not in the Nix store", path).addTrace(pos, errorCtx); } -bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::string & errorCtx) +bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::string_view & errorCtx) { forceValue(v1, noPos); forceValue(v2, noPos); @@ -2120,7 +2166,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::str return v1.fpoint == v2.fpoint; default: - throwEvalError(pos, "%3%cannot compare %1% with %2%", showType(v1), showType(v2), errorCtx); + throw EvalError("cannot compare %1% with %2%", showType(v1), showType(v2)).addTrace(pos, errorCtx); } } @@ -2242,12 +2288,9 @@ void EvalState::printStats() } -std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string & errorCtx) const +std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string_view & errorCtx) const { - throw TypeError({ - .msg = hintfmt("%2%cannot coerce %1% to a string", showType(), errorCtx), - .errPos = pos - }); + throw TypeError("cannot coerce %1% to a string", showType()).addTrace(pos, errorCtx); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index bd817c9fe..3f987922a 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -217,8 +217,8 @@ public: /* Evaluation the expression, then verify that it has the expected type. */ - inline bool evalBool(Env & env, Expr * e, const Pos & pos, const std::string & errorCtx); - inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & errorCtx); + inline bool evalBool(Env & env, Expr * e, const Pos & pos, const std::string_view & errorCtx); + inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx); /* If `v' is a thunk, enter it and overwrite `v' with the result of the evaluation of the thunk. If `v' is a delayed function @@ -234,20 +234,20 @@ public: void forceValueDeep(Value & v); /* Force `v', and then verify that it has the expected type. */ - NixInt forceInt(Value & v, const Pos & pos, const std::string & errorCtx); - NixFloat forceFloat(Value & v, const Pos & pos, const std::string & errorCtx); - bool forceBool(Value & v, const Pos & pos, const std::string & errorCtx); + NixInt forceInt(Value & v, const Pos & pos, const std::string_view & errorCtx); + NixFloat forceFloat(Value & v, const Pos & pos, const std::string_view & errorCtx); + bool forceBool(Value & v, const Pos & pos, const std::string_view & errorCtx); - void forceAttrs(Value & v, const Pos & pos, const std::string & errorCtx); + void forceAttrs(Value & v, const Pos & pos, const std::string_view & errorCtx); template - inline void forceAttrs(Value & v, Callable getPos, const std::string & errorCtx); + inline void forceAttrs(Value & v, Callable getPos, const std::string_view & errorCtx); - inline void forceList(Value & v, const Pos & pos, const std::string & errorCtx); - void forceFunction(Value & v, const Pos & pos, const std::string & errorCtx); // either lambda or primop - std::string_view forceString(Value & v, const Pos & pos, const std::string & errorCtx); - std::string_view forceString(Value & v, PathSet & context, const Pos & pos, const std::string & errorCtx); - std::string_view forceStringNoCtx(Value & v, const Pos & pos, const std::string & errorCtx); + inline void forceList(Value & v, const Pos & pos, const std::string_view & errorCtx); + void forceFunction(Value & v, const Pos & pos, const std::string_view & errorCtx); // either lambda or primop + std::string_view forceString(Value & v, const Pos & pos, const std::string_view & errorCtx); + std::string_view forceString(Value & v, PathSet & context, const Pos & pos, const std::string_view & errorCtx); + std::string_view forceStringNoCtx(Value & v, const Pos & pos, const std::string_view & errorCtx); /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ @@ -263,17 +263,17 @@ public: BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true, bool canonicalizePath = true, - const std::string & errorCtx = ""); + const std::string_view & errorCtx = ""); std::string copyPathToStore(PathSet & context, const Path & path); /* Path coercion. Converts strings, paths and derivations to a path. The result is guaranteed to be a canonicalised, absolute path. Nothing is copied to the store. */ - Path coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx); + Path coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx); /* Like coerceToPath, but the result must be a store path. */ - StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx); + StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx); public: @@ -329,7 +329,7 @@ public: /* Do a deep equality test between two values. That is, list elements and attributes are compared recursively. */ - bool eqValues(Value & v1, Value & v2, const Pos & pos, const std::string & errorCtx); + bool eqValues(Value & v1, Value & v2, const Pos & pos, const std::string_view & errorCtx); bool isFunctor(Value & fun); @@ -364,7 +364,7 @@ public: void mkThunk_(Value & v, Expr * expr); void mkPos(Value & v, ptr pos); - void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string & errorCtx); + void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string_view & errorCtx); /* Print statistics. */ void printStats(); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 76d45180b..b236c038b 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -396,7 +396,7 @@ expr_function ; expr_if - : IF expr THEN expr ELSE expr { $$ = new ExprIf(makeCurPos(@2, data), $2, $4, $6); } + : IF expr THEN expr ELSE expr { $$ = new ExprIf(CUR_POS, $2, $4, $6); } | expr_op ; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 1e22044d7..40bb925ae 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -103,7 +103,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea auto path = [&]() { try { - return state.coerceToPath(pos, v, context, "While realising the context of a path: "); + return state.coerceToPath(pos, v, context, "While realising the context of a path"); } catch (Error & e) { e.addTrace(pos, "while realising the context of a path"); throw; @@ -195,9 +195,9 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS , "/"), **state.vImportedDrvToDerivation); } - state.forceFunction(**state.vImportedDrvToDerivation, pos, "While evaluating imported-drv-to-derivation.nix.gen.hh: "); + state.forceFunction(**state.vImportedDrvToDerivation, pos, "While evaluating imported-drv-to-derivation.nix.gen.hh"); v.mkApp(*state.vImportedDrvToDerivation, w); - state.forceAttrs(v, pos, "While calling imported-drv-to-derivation.nix.gen.hh: "); + state.forceAttrs(v, pos, "While calling imported-drv-to-derivation.nix.gen.hh"); } else if (path == corepkgsPrefix + "fetchurl.nix") { @@ -210,7 +210,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS if (!vScope) state.evalFile(path, v); else { - state.forceAttrs(*vScope, pos, "While evaluating the first argument passed to builtins.scopedImport: "); + state.forceAttrs(*vScope, pos, "While evaluating the first argument passed to builtins.scopedImport"); Env * env = &state.allocEnv(vScope->attrs->size()); env->up = &state.baseEnv; @@ -314,7 +314,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value { auto path = realisePath(state, pos, *args[0]); - std::string sym(state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.importNative: ")); + std::string sym(state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.importNative")); void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!handle) @@ -340,7 +340,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value /* Execute a program and parse its output */ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.exec: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.exec"); auto elems = args[0]->listElems(); auto count = args[0]->listSize(); if (count == 0) { @@ -533,50 +533,74 @@ static RegisterPrimOp primop_isPath({ .fun = prim_isPath, }); +template + static inline void withExceptionContext(Trace trace, Callable&& func) +{ + try + { + func(); + } + catch(Error & e) + { + e.pushTrace(trace); + throw; + } +} + struct CompareValues { EvalState & state; const Pos & pos; - const std::string errorCtx; + const std::string_view errorCtx; - CompareValues(EvalState & state, const Pos & pos, const std::string && errorCtx) : state(state), pos(pos), errorCtx(std::move(errorCtx)) { }; + CompareValues(EvalState & state, const Pos & pos, const std::string_view && errorCtx) : state(state), pos(pos), errorCtx(errorCtx) { }; bool operator () (Value * v1, Value * v2) const { - if (v1->type() == nFloat && v2->type() == nInt) - return v1->fpoint < v2->integer; - if (v1->type() == nInt && v2->type() == nFloat) - return v1->integer < v2->fpoint; - if (v1->type() != v2->type()) - throw EvalError({ - .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), - .errPos = pos, - }); - switch (v1->type()) { - case nInt: - return v1->integer < v2->integer; - case nFloat: - return v1->fpoint < v2->fpoint; - case nString: - return strcmp(v1->string.s, v2->string.s) < 0; - case nPath: - return strcmp(v1->path, v2->path) < 0; - case nList: - // Lexicographic comparison - for (size_t i = 0;; i++) { - if (i == v2->listSize()) { - return false; - } else if (i == v1->listSize()) { - return true; - } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx)) { - return (*this)(v1->listElems()[i], v2->listElems()[i]); - } - } - default: + return (*this)(v1, v2, errorCtx); + } + + bool operator () (Value * v1, Value * v2, const std::string_view & errorCtx) const + { + try { + if (v1->type() == nFloat && v2->type() == nInt) + return v1->fpoint < v2->integer; + if (v1->type() == nInt && v2->type() == nFloat) + return v1->integer < v2->fpoint; + if (v1->type() != v2->type()) throw EvalError({ .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), - .errPos = pos, + .errPos = std::nullopt, }); + switch (v1->type()) { + case nInt: + return v1->integer < v2->integer; + case nFloat: + return v1->fpoint < v2->fpoint; + case nString: + return strcmp(v1->string.s, v2->string.s) < 0; + case nPath: + return strcmp(v1->path, v2->path) < 0; + case nList: + // Lexicographic comparison + for (size_t i = 0;; i++) { + if (i == v2->listSize()) { + return false; + } else if (i == v1->listSize()) { + return true; + } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx)) { + return (*this)(v1->listElems()[i], v2->listElems()[i], "While comparing two lists"); + } + } + default: + throw EvalError({ + .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .errPos = std::nullopt, + }); + } + } catch (Error & e) { + e.addTrace(noPos, errorCtx); + throw; } } }; @@ -590,106 +614,70 @@ typedef std::list ValueList; static Bindings::iterator getAttr( - EvalState & state, - std::string_view funcName, Symbol attrSym, Bindings * attrSet, - const Pos & pos) + std::string_view errorCtx) { Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { - hintformat errorMsg = hintfmt( - "attribute '%s' missing for call to '%s'", - attrSym, - funcName - ); - - Pos aPos = *attrSet->pos; - if (aPos == noPos) { - throw TypeError({ - .msg = errorMsg, - .errPos = pos, - }); - } else { - auto e = TypeError({ - .msg = errorMsg, - .errPos = aPos, - }); - - // Adding another trace for the function name to make it clear - // which call received wrong arguments. - e.addTrace(pos, hintfmt("while invoking '%s'", funcName)); - throw e; - } + throw TypeError({ + .msg = hintfmt("attribute '%s' missing %s", attrSym, errorCtx), + .errPos = *attrSet->pos + }); } - return value; } static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.genericClosure: "); + state.forceAttrs(*args[0], noPos, "While evaluating the first argument pased to builtins.genericClosure"); /* Get the start set. */ - Bindings::iterator startSet = getAttr( - state, - "genericClosure", - state.sStartSet, - args[0]->attrs, - pos - ); + Bindings::iterator startSet = getAttr(state.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); - state.forceList(*startSet->value, pos, "While evaluating the `startSet` attribute passed to builtins.genericClosure: "); + state.forceList(*startSet->value, noPos, "While evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"); ValueList workSet; for (auto elem : startSet->value->listItems()) workSet.push_back(elem); + if (startSet->value->listSize() == 0) { + v = *startSet->value; + return; + } + /* Get the operator. */ - Bindings::iterator op = getAttr( - state, - "genericClosure", - state.sOperator, - args[0]->attrs, - pos - ); + Bindings::iterator op = getAttr(state.sOperator, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); + state.forceFunction(*op->value, noPos, "While evaluating the 'operator' attribute passed as argument to builtins.genericClosure"); - state.forceFunction(*op->value, pos, "While evaluating the `operator` attribute passed to builtins.genericClosure: "); - - /* Construct the closure by applying the operator to element of + /* Construct the closure by applying the operator to elements of `workSet', adding the result to `workSet', continuing until no new elements are found. */ ValueList res; // `doneKeys' doesn't need to be a GC root, because its values are // reachable from res. - auto cmp = CompareValues(state, pos, "While comparing the `key` attributes of two genericClosure elements"); + auto cmp = CompareValues(state, noPos, "While comparing the `key` attributes of two genericClosure elements"); std::set doneKeys(cmp); while (!workSet.empty()) { Value * e = *(workSet.begin()); workSet.pop_front(); - state.forceAttrs(*e, pos, "While evaluating one item to be part of the genericClosure: "); + state.forceAttrs(*e, noPos, "While evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"); - Bindings::iterator key = - e->attrs->find(state.sKey); - if (key == e->attrs->end()) - throw EvalError({ - .msg = hintfmt("While evaluating one of the attribute sets to be part of the genericClosure: attribute `key` required"), - .errPos = pos - }); - state.forceValue(*key->value, pos); + Bindings::iterator key = getAttr(state.sKey, e->attrs, "in one of the attrsets generated by (or initially passed to) builtins.genericClosure"); + state.forceValue(*key->value, noPos); if (!doneKeys.insert(key->value).second) continue; res.push_back(e); /* Call the `operator' function with `e' as argument. */ - Value res; - state.callFunction(*op->value, 1, &e, res, pos); - state.forceList(res, pos, "While evaluating the return value of the `operator` passed to builtins.genericClosure: "); + Value newElements; + state.callFunction(*op->value, 1, &e, newElements, noPos); + state.forceList(newElements, noPos, "While evaluating the return value of the `operator` passed to builtins.genericClosure"); /* Add the values returned by the operator to the work set. */ - for (auto elem : res.listItems()) { - state.forceValue(*elem, pos); + for (auto elem : newElements.listItems()) { + state.forceValue(*elem, noPos); // "While evaluating one one of the elements returned by the `operator` passed to builtins.genericClosure"); workSet.push_back(elem); } } @@ -782,7 +770,7 @@ static RegisterPrimOp primop_ceil({ static void prim_floor(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "While evaluating the first argument passed to builtins.floor: "); + auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "While evaluating the first argument passed to builtins.floor"); v.mkInt(floor(value)); } @@ -839,7 +827,7 @@ static RegisterPrimOp primop_tryEval({ /* Return an environment variable. Use with care. */ static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) { - std::string name(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.getEnv: ")); + std::string name(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.getEnv")); v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or("")); } @@ -937,21 +925,15 @@ static RegisterPrimOp primop_trace({ derivation. */ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.derivationStrict: "); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.derivationStrict"); /* Figure out the name first (for stack backtraces). */ - Bindings::iterator attr = getAttr( - state, - "derivationStrict", - state.sName, - args[0]->attrs, - pos - ); + Bindings::iterator attr = getAttr(state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict"); std::string drvName; Pos & posDrvName(*attr->pos); try { - drvName = state.forceStringNoCtx(*attr->value, pos, "While evaluating the `name` attribute passed to builtins.derivationStrict: "); + drvName = state.forceStringNoCtx(*attr->value, pos, "While evaluating the `name` attribute passed to builtins.derivationStrict"); } catch (Error & e) { e.addTrace(posDrvName, "while evaluating the derivation attribute 'name'"); throw; @@ -961,14 +943,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * std::ostringstream jsonBuf; std::unique_ptr jsonObject; attr = args[0]->attrs->find(state.sStructuredAttrs); - if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "While evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict: ")) + if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "While evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict")) jsonObject = std::make_unique(jsonBuf); /* Check whether null attributes should be ignored. */ bool ignoreNulls = false; attr = args[0]->attrs->find(state.sIgnoreNulls); if (attr != args[0]->attrs->end()) - ignoreNulls = state.forceBool(*attr->value, pos, "While evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict: "); + ignoreNulls = state.forceBool(*attr->value, pos, "While evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict"); /* Build the derivation expression by processing the attributes. */ Derivation drv; @@ -1064,26 +1046,26 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * printValueAsJSON(state, true, *i->value, pos, placeholder, context); if (i->name == state.sBuilder) - drv.builder = state.forceString(*i->value, context, posDrvName, "While evaluating the `builder` attribute passed to builtins.derivationStrict: "); + drv.builder = state.forceString(*i->value, context, posDrvName, "While evaluating the `builder` attribute passed to builtins.derivationStrict"); else if (i->name == state.sSystem) - drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `system` attribute passed to builtins.derivationStrict: "); + drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `system` attribute passed to builtins.derivationStrict"); else if (i->name == state.sOutputHash) - outputHash = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHash` attribute passed to builtins.derivationStrict: "); + outputHash = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHash` attribute passed to builtins.derivationStrict"); else if (i->name == state.sOutputHashAlgo) - outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict: "); + outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict"); else if (i->name == state.sOutputHashMode) - handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHashMode` attribute passed to builtins.derivationStrict: ")); + handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "While evaluating the `outputHashMode` attribute passed to builtins.derivationStrict")); else if (i->name == state.sOutputs) { /* Require ‘outputs’ to be a list of strings. */ - state.forceList(*i->value, posDrvName, "While evaluating the `outputs` attribute passed to builtins.derivationStrict: "); + state.forceList(*i->value, posDrvName, "While evaluating the `outputs` attribute passed to builtins.derivationStrict"); Strings ss; for (auto elem : i->value->listItems()) - ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "While evaluating an element of the `outputs` attribute passed to builtins.derivationStrict: ")); + ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "While evaluating an element of the `outputs` attribute passed to builtins.derivationStrict")); handleOutputs(ss); } } else { - auto s = state.coerceToString(*i->pos, *i->value, context, true, "While evaluating an attribute passed to builtins.derivationStrict: ").toOwned(); + auto s = state.coerceToString(*i->pos, *i->value, context, true, "While evaluating an attribute passed to builtins.derivationStrict").toOwned(); drv.env.emplace(key, s); if (i->name == state.sBuilder) drv.builder = std::move(s); else if (i->name == state.sSystem) drv.platform = std::move(s); @@ -1291,7 +1273,7 @@ static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info { ‘out’. */ static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) { - v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.placeholder: "))); + v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.placeholder"))); } static RegisterPrimOp primop_placeholder({ @@ -1315,7 +1297,7 @@ static RegisterPrimOp primop_placeholder({ static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - Path path = state.coerceToPath(pos, *args[0], context, "While evaluating the first argument passed to builtins.toPath: "); + Path path = state.coerceToPath(pos, *args[0], context, "While evaluating the first argument passed to builtins.toPath"); v.mkString(canonPath(path), context); } @@ -1346,7 +1328,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V }); PathSet context; - Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "While evaluating the first argument passed to builtins.storePath: ")); + Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "While evaluating the first argument passed to builtins.storePath")); /* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink directly in the store. The latter condition is necessary so e.g. nix-push does the right thing. */ @@ -1416,7 +1398,7 @@ static RegisterPrimOp primop_pathExists({ static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.baseNameOf: ")), context); + v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.baseNameOf")), context); } static RegisterPrimOp primop_baseNameOf({ @@ -1436,7 +1418,7 @@ static RegisterPrimOp primop_baseNameOf({ static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto path = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.dirOf: "); + auto path = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.dirOf"); auto dir = dirOf(*path); if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context); } @@ -1483,25 +1465,19 @@ static RegisterPrimOp primop_readFile({ which are desugared to 'findFile __nixPath "x"'. */ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.findFile: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.findFile"); SearchPath searchPath; for (auto v2 : args[0]->listItems()) { - state.forceAttrs(*v2, pos, "While evaluating an element of the list passed to builtins.findFile: "); + state.forceAttrs(*v2, pos, "While evaluating an element of the list passed to builtins.findFile"); std::string prefix; Bindings::iterator i = v2->attrs->find(state.sPrefix); if (i != v2->attrs->end()) - prefix = state.forceStringNoCtx(*i->value, pos, "While evaluating the `prefix` attribute of an element of the list passed to builtins.findFile: "); + prefix = state.forceStringNoCtx(*i->value, pos, "While evaluating the `prefix` attribute of an element of the list passed to builtins.findFile"); - i = getAttr( - state, - "findFile", - state.sPath, - v2->attrs, - pos - ); + i = getAttr(state.sPath, v2->attrs, "in an element of the __nixPath"); PathSet context; auto path = state.coerceToString(pos, *i->value, context, false, false, @@ -1521,7 +1497,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va searchPath.emplace_back(prefix, path); } - auto path = state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.findFile: "); + auto path = state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.findFile"); v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos))); } @@ -1535,7 +1511,7 @@ static RegisterPrimOp primop_findFile(RegisterPrimOp::Info { /* Return the cryptographic hash of a file in base-16. */ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto type = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hashFile: "); + auto type = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hashFile"); std::optional ht = parseHashType(type); if (!ht) throw Error({ @@ -1742,7 +1718,7 @@ static RegisterPrimOp primop_toJSON({ /* Parse a JSON string to a value. */ static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto s = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.fromJSON: "); + auto s = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.fromJSON"); try { parseJSON(state, s, v); } catch (JSONParseError &e) { @@ -1771,8 +1747,8 @@ static RegisterPrimOp primop_fromJSON({ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - std::string name(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.toFile: ")); - std::string contents(state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.toFile: ")); + std::string name(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.toFile")); + std::string contents(state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.toFile")); StorePathSet refs; @@ -1929,7 +1905,7 @@ static void addPath( Value res; state.callFunction(*filterFun, 2, args, res, pos); - return state.forceBool(res, pos, "While evaluating the return value of the path filter function: "); + return state.forceBool(res, pos, "While evaluating the return value of the path filter function"); }) : defaultPathFilter; std::optional expectedStorePath; @@ -1955,9 +1931,9 @@ static void addPath( static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - Path path = state.coerceToPath(pos, *args[1], context, "While evaluating the second argument (the path to filter) passed to builtins.filterSource: "); + Path path = state.coerceToPath(pos, *args[1], context, "While evaluating the second argument (the path to filter) passed to builtins.filterSource"); - state.forceFunction(*args[0], pos, "While evaluating the first argument to builtins.filterSource: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument to builtins.filterSource"); addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context); } @@ -2018,7 +1994,7 @@ static RegisterPrimOp primop_filterSource({ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.path: "); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.path"); Path path; std::string name; Value * filterFun = nullptr; @@ -2029,15 +2005,15 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value for (auto & attr : *args[0]->attrs) { auto & n(attr.name); if (n == "path") - path = state.coerceToPath(*attr.pos, *attr.value, context, "While evaluating the `path` attribute passed to builtins.path: "); + path = state.coerceToPath(*attr.pos, *attr.value, context, "While evaluating the `path` attribute passed to builtins.path"); else if (attr.name == state.sName) - name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the `name` attribute passed to builtins.path: "); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the `name` attribute passed to builtins.path"); else if (n == "filter") - state.forceFunction(*(filterFun = attr.value), *attr.pos, "While evaluating the `filter` parameter passed to builtins.path: "); + state.forceFunction(*(filterFun = attr.value), *attr.pos, "While evaluating the `filter` parameter passed to builtins.path"); else if (n == "recursive") - method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos, "While evaluating the `recursive` attribute passed to builtins.path: ") }; + method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos, "While evaluating the `recursive` attribute passed to builtins.path") }; else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `sha256` attribute passed to builtins.path: "), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `sha256` attribute passed to builtins.path"), htSHA256); else throw EvalError({ .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), @@ -2100,7 +2076,7 @@ static RegisterPrimOp primop_path({ strings. */ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrNames: "); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrNames"); state.mkList(v, args[0]->attrs->size()); @@ -2127,7 +2103,7 @@ static RegisterPrimOp primop_attrNames({ order as attrNames. */ static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrValues: "); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrValues"); state.mkList(v, args[0]->attrs->size()); @@ -2158,14 +2134,12 @@ static RegisterPrimOp primop_attrValues({ /* Dynamic version of the `.' operator. */ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.getAttr: "); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.getAttr: "); + auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.getAttr"); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.getAttr"); Bindings::iterator i = getAttr( - state, - "getAttr", state.symbols.create(attr), args[1]->attrs, - pos + "in the attribute set under consideration" ); // !!! add to stack trace? if (state.countCalls && *i->pos != noPos) state.attrSelects[*i->pos]++; @@ -2188,8 +2162,8 @@ static RegisterPrimOp primop_getAttr({ /* Return position information of the specified attribute. */ static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.unsafeGetAttrPos: "); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.unsafeGetAttrPos: "); + auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.unsafeGetAttrPos"); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.unsafeGetAttrPos"); Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); if (i == args[1]->attrs->end()) v.mkNull(); @@ -2206,8 +2180,8 @@ static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info { /* Dynamic version of the `?' operator. */ static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hasAttr: "); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.hasAttr: "); + auto attr = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hasAttr"); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.hasAttr"); v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end()); } @@ -2240,8 +2214,8 @@ static RegisterPrimOp primop_isAttrs({ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.removeAttrs: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.removeAttrs: "); + state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.removeAttrs"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.removeAttrs"); /* Get the attribute names to be removed. We keep them as Attrs instead of Symbols so std::set_difference @@ -2249,7 +2223,7 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, boost::container::small_vector names; names.reserve(args[1]->listSize()); for (auto elem : args[1]->listItems()) { - state.forceStringNoCtx(*elem, pos, "While evaluating the values of the second argument passed to builtins.removeAttrs: "); + state.forceStringNoCtx(*elem, pos, "While evaluating the values of the second argument passed to builtins.removeAttrs"); names.emplace_back(state.symbols.create(elem->string.s), nullptr); } std::sort(names.begin(), names.end()); @@ -2288,34 +2262,22 @@ static RegisterPrimOp primop_removeAttrs({ name, the first takes precedence. */ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the argument passed to builtins.listToAttrs: "); + state.forceList(*args[0], pos, "While evaluating the argument passed to builtins.listToAttrs"); auto attrs = state.buildBindings(args[0]->listSize()); std::set seen; for (auto v2 : args[0]->listItems()) { - state.forceAttrs(*v2, pos, "While evaluating an element of the list passed to builtins.listToAttrs: "); + state.forceAttrs(*v2, pos, "While evaluating an element of the list passed to builtins.listToAttrs"); - Bindings::iterator j = getAttr( - state, - "listToAttrs", - state.sName, - v2->attrs, - pos - ); + Bindings::iterator j = getAttr(state.sName, v2->attrs, "in a {name=...; value=...;} pair"); - auto name = state.forceStringNoCtx(*j->value, *j->pos, "While evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs: "); + auto name = state.forceStringNoCtx(*j->value, *j->pos, "While evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs"); Symbol sym = state.symbols.create(name); if (seen.insert(sym).second) { - Bindings::iterator j2 = getAttr( - state, - "listToAttrs", - state.sValue, - v2->attrs, - pos - ); + Bindings::iterator j2 = getAttr(state.sValue, v2->attrs, "in a {name=...; value=...;} pair"); attrs.insert(sym, j2->value, j2->pos); } } @@ -2350,8 +2312,8 @@ static RegisterPrimOp primop_listToAttrs({ static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.intersectAttrs: "); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.intersectAttrs: "); + state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.intersectAttrs"); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.intersectAttrs"); auto attrs = state.buildBindings(std::min(args[0]->attrs->size(), args[1]->attrs->size())); @@ -2376,14 +2338,14 @@ static RegisterPrimOp primop_intersectAttrs({ static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.catAttrs: ")); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.catAttrs: "); + Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.catAttrs")); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.catAttrs"); Value * res[args[1]->listSize()]; unsigned int found = 0; for (auto v2 : args[1]->listItems()) { - state.forceAttrs(*v2, pos, "While evaluating an element in the list passed as second argument to builtins.catAttrs: "); + state.forceAttrs(*v2, pos, "While evaluating an element in the list passed as second argument to builtins.catAttrs"); Bindings::iterator i = v2->attrs->find(attrName); if (i != v2->attrs->end()) res[found++] = i->value; @@ -2456,7 +2418,7 @@ static RegisterPrimOp primop_functionArgs({ /* */ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.mapAttrs: "); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.mapAttrs"); auto attrs = state.buildBindings(args[1]->attrs->size()); @@ -2497,15 +2459,15 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args std::map> attrsSeen; - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.zipAttrsWith: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.zipAttrsWith: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.zipAttrsWith"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.zipAttrsWith"); const auto listSize = args[1]->listSize(); const auto listElems = args[1]->listElems(); for (unsigned int n = 0; n < listSize; ++n) { Value * vElem = listElems[n]; try { - state.forceAttrs(*vElem, noPos, "While evaluating a value of the list passed as second argument to builtins.zipAttrsWith: "); + state.forceAttrs(*vElem, noPos, "While evaluating a value of the list passed as second argument to builtins.zipAttrsWith"); for (auto & attr : *vElem->attrs) attrsSeen[attr.name].first++; } catch (TypeError & e) { @@ -2595,7 +2557,7 @@ static RegisterPrimOp primop_isList({ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) { - state.forceList(list, pos, "While evaluating the first argument passed to builtins.elemAt: "); + state.forceList(list, pos, "While evaluating the first argument passed to builtins.elemAt"); if (n < 0 || (unsigned int) n >= list.listSize()) throw Error({ .msg = hintfmt("list index %1% is out of bounds", n), @@ -2608,7 +2570,7 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu /* Return the n-1'th element of a list. */ static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v) { - elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.elemAt: "), v); + elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.elemAt"), v); } static RegisterPrimOp primop_elemAt({ @@ -2643,7 +2605,7 @@ static RegisterPrimOp primop_head({ don't want to use it! */ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.tail: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.tail"); if (args[0]->listSize() == 0) throw Error({ .msg = hintfmt("'tail' called on an empty list"), @@ -2674,17 +2636,19 @@ static RegisterPrimOp primop_tail({ /* Apply a function to every element of a list. */ static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.map: "); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.map"); + + if (args[1]->listSize() == 0) { + v = *args[1]; + return; + } + + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.map"); state.mkList(v, args[1]->listSize()); - - if (args[1]->listSize() > 0) { - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.map: "); - - for (unsigned int n = 0; n < v.listSize(); ++n) - (v.listElems()[n] = state.allocValue())->mkApp( - args[0], args[1]->listElems()[n]); - }; + for (unsigned int n = 0; n < v.listSize(); ++n) + (v.listElems()[n] = state.allocValue())->mkApp( + args[0], args[1]->listElems()[n]); } static RegisterPrimOp primop_map({ @@ -2695,7 +2659,7 @@ static RegisterPrimOp primop_map({ example, ```nix - map (x: "foo" + x) [ "bar" "bla" "abc" ] + map (x"foo" + x) [ "bar" "bla" "abc" ] ``` evaluates to `[ "foobar" "foobla" "fooabc" ]`. @@ -2708,14 +2672,14 @@ static RegisterPrimOp primop_map({ returns true. */ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.filter: "); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.filter"); if (args[1]->listSize() == 0) { v = *args[1]; return; } - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.filter: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.filter"); // FIXME: putting this on the stack is risky. Value * vs[args[1]->listSize()]; @@ -2725,7 +2689,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu for (unsigned int n = 0; n < args[1]->listSize(); ++n) { Value res; state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos); - if (state.forceBool(res, pos, "While evaluating the return value of the filtering function passed to builtins.filter: ")) + if (state.forceBool(res, pos, "While evaluating the return value of the filtering function passed to builtins.filter")) vs[k++] = args[1]->listElems()[n]; else same = false; @@ -2753,9 +2717,9 @@ static RegisterPrimOp primop_filter({ static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v) { bool res = false; - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.elem: "); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.elem"); for (auto elem : args[1]->listItems()) - if (state.eqValues(*args[0], *elem, pos, "While searching for the presence of the given element in the list: ")) { + if (state.eqValues(*args[0], *elem, pos, "While searching for the presence of the given element in the list")) { res = true; break; } @@ -2775,7 +2739,7 @@ static RegisterPrimOp primop_elem({ /* Concatenate a list of lists. */ static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.concatLists: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.concatLists"); state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos, "While evaluating a value of the list passed to builtins.concatLists"); } @@ -2791,7 +2755,7 @@ static RegisterPrimOp primop_concatLists({ /* Return the length of a list. This is an O(1) time operation. */ static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.length: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.length"); v.mkInt(args[0]->listSize()); } @@ -2808,8 +2772,8 @@ static RegisterPrimOp primop_length({ right. The operator is applied strictly. */ static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.foldlStrict: "); - state.forceList(*args[2], pos, "While evaluating the third argument passed to builtins.foldlStrict: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.foldlStrict"); + state.forceList(*args[2], pos, "While evaluating the third argument passed to builtins.foldlStrict"); if (args[2]->listSize()) { Value * vCur = args[1]; @@ -2841,13 +2805,13 @@ static RegisterPrimOp primop_foldlStrict({ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos, std::string("While evaluating the first argument passed to builtins.: ") + (any ? "any" : "all: ")); - state.forceList(*args[1], pos, std::string("While evaluating the second argument passed to builtins.: ") + (any ? "any" : "all")); + state.forceFunction(*args[0], pos, std::string("While evaluating the first argument passed to builtins.") + (any ? "any" : "all: ")); + state.forceList(*args[1], pos, std::string("While evaluating the second argument passed to builtins.") + (any ? "any" : "all")); Value vTmp; for (auto elem : args[1]->listItems()) { state.callFunction(*args[0], *elem, vTmp, pos); - bool res = state.forceBool(vTmp, pos, std::string("While evaluating the return value of the function passed to builtins.: ") + (any ? "any" : "all")); + bool res = state.forceBool(vTmp, pos, std::string("While evaluating the return value of the function passed to builtins.") + (any ? "any" : "all")); if (res == any) { v.mkBool(any); return; @@ -2890,7 +2854,7 @@ static RegisterPrimOp primop_all({ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto len = state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.genList: "); + auto len = state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.genList"); if (len < 0) throw EvalError({ @@ -2928,7 +2892,7 @@ static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Va static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.sort: "); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.sort"); auto len = args[1]->listSize(); if (len == 0) { @@ -2936,7 +2900,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value return; } - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.sort: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.sort"); state.mkList(v, len); for (unsigned int n = 0; n < len; ++n) { @@ -2948,12 +2912,12 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value /* Optimization: if the comparator is lessThan, bypass callFunction. */ if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) - return CompareValues(state, pos, "While evaluating the ordering function passed to builtins.sort: ")(a, b); + return CompareValues(state, noPos, "While evaluating the ordering function passed to builtins.sort")(a, b); Value * vs[] = {a, b}; Value vBool; - state.callFunction(*args[0], 2, vs, vBool, pos); - return state.forceBool(vBool, pos, "While evaluating the return value of the sorting function passed to builtins.sort: "); + state.callFunction(*args[0], 2, vs, vBool, noPos); + return state.forceBool(vBool, pos, "While evaluating the return value of the sorting function passed to builtins.sort"); }; /* FIXME: std::sort can segfault if the comparator is not a strict @@ -2985,8 +2949,8 @@ static RegisterPrimOp primop_sort({ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.partition: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.partition: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.partition"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.partition"); auto len = args[1]->listSize(); @@ -2997,7 +2961,7 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V state.forceValue(*vElem, pos); Value res; state.callFunction(*args[0], *vElem, res, pos); - if (state.forceBool(res, pos, "While evaluating the return value of the partition function passed to builtins.partition: ")) + if (state.forceBool(res, pos, "While evaluating the return value of the partition function passed to builtins.partition")) right.push_back(vElem); else wrong.push_back(vElem); @@ -3045,15 +3009,15 @@ static RegisterPrimOp primop_partition({ static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.groupBy: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.groupBy: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.groupBy"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.groupBy"); ValueVectorMap attrs; for (auto vElem : args[1]->listItems()) { Value res; state.callFunction(*args[0], *vElem, res, pos); - auto name = state.forceStringNoCtx(res, pos, "While evaluating the return value of the grouping function passed to builtins.groupBy: "); + auto name = state.forceStringNoCtx(res, pos, "While evaluating the return value of the grouping function passed to builtins.groupBy"); Symbol sym = state.symbols.create(name); auto vector = attrs.try_emplace(sym, ValueVector()).first; vector->second.push_back(vElem); @@ -3097,8 +3061,8 @@ static RegisterPrimOp primop_groupBy({ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.concatMap: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.concatMap: "); + state.forceFunction(*args[0], pos, "While evaluating the first argument passed to builtins.concatMap"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.concatMap"); auto nrLists = args[1]->listSize(); Value lists[nrLists]; @@ -3108,7 +3072,7 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V Value * vElem = args[1]->listElems()[n]; state.callFunction(*args[0], *vElem, lists[n], pos); try { - state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "While evaluating the return value of the function passed to buitlins.concatMap: "); + state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "While evaluating the return value of the function passed to buitlins.concatMap"); } catch (TypeError &e) { e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap")); throw; @@ -3147,11 +3111,11 @@ static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); if (args[0]->type() == nFloat || args[1]->type() == nFloat) - v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first argument of the addition: ") - + state.forceFloat(*args[1], pos, "While evaluating the second argument of the addition: ")); + v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first argument of the addition") + + state.forceFloat(*args[1], pos, "While evaluating the second argument of the addition")); else - v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the addition: ") - + state.forceInt(*args[1], pos, "While evaluating the second argument of the addition: ")); + v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the addition") + + state.forceInt(*args[1], pos, "While evaluating the second argument of the addition")); } static RegisterPrimOp primop_add({ @@ -3168,11 +3132,11 @@ static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); if (args[0]->type() == nFloat || args[1]->type() == nFloat) - v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first argument of the subtraction: ") - - state.forceFloat(*args[1], pos, "While evaluating the second argument of the subtraction: ")); + v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first argument of the subtraction") + - state.forceFloat(*args[1], pos, "While evaluating the second argument of the subtraction")); else - v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the subtraction: ") - - state.forceInt(*args[1], pos, "While evaluating the second argument of the subtraction: ")); + v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the subtraction") + - state.forceInt(*args[1], pos, "While evaluating the second argument of the subtraction")); } static RegisterPrimOp primop_sub({ @@ -3189,11 +3153,11 @@ static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); if (args[0]->type() == nFloat || args[1]->type() == nFloat) - v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first of the multiplication: ") - * state.forceFloat(*args[1], pos, "While evaluating the second argument of the multiplication: ")); + v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first of the multiplication") + * state.forceFloat(*args[1], pos, "While evaluating the second argument of the multiplication")); else - v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the multiplication: ") - * state.forceInt(*args[1], pos, "While evaluating the second argument of the multiplication: ")); + v.mkInt( state.forceInt(*args[0], pos, "While evaluating the first argument of the multiplication") + * state.forceInt(*args[1], pos, "While evaluating the second argument of the multiplication")); } static RegisterPrimOp primop_mul({ @@ -3210,7 +3174,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); - NixFloat f2 = state.forceFloat(*args[1], pos, "While evaluating the second operand of the division: "); + NixFloat f2 = state.forceFloat(*args[1], pos, "While evaluating the second operand of the division"); if (f2 == 0) throw EvalError({ .msg = hintfmt("division by zero"), @@ -3218,10 +3182,10 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & }); if (args[0]->type() == nFloat || args[1]->type() == nFloat) { - v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first operand of the division: ") / f2); + v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first operand of the division") / f2); } else { - NixInt i1 = state.forceInt(*args[0], pos, "While evaluating the first operand of the division: "); - NixInt i2 = state.forceInt(*args[1], pos, "While evaluating the second operand of the division: "); + NixInt i1 = state.forceInt(*args[0], pos, "While evaluating the first operand of the division"); + NixInt i2 = state.forceInt(*args[1], pos, "While evaluating the second operand of the division"); /* Avoid division overflow as it might raise SIGFPE. */ if (i1 == std::numeric_limits::min() && i2 == -1) throw EvalError({ @@ -3244,8 +3208,8 @@ static RegisterPrimOp primop_div({ static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v) { - v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitAnd: ") - & state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitAnd: ")); + v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitAnd") + & state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitAnd")); } static RegisterPrimOp primop_bitAnd({ @@ -3259,8 +3223,8 @@ static RegisterPrimOp primop_bitAnd({ static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v) { - v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitOr: ") - | state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitOr: ")); + v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitOr") + | state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitOr")); } static RegisterPrimOp primop_bitOr({ @@ -3274,8 +3238,8 @@ static RegisterPrimOp primop_bitOr({ static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v) { - v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitXor: ") - ^ state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitXor: ")); + v.mkInt(state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.bitXor") + ^ state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.bitXor")); } static RegisterPrimOp primop_bitXor({ @@ -3319,7 +3283,7 @@ static RegisterPrimOp primop_lessThan({ static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context, true, false, "While evaluating the first argument passed to builtins.toString: "); + auto s = state.coerceToString(pos, *args[0], context, true, false, "While evaluating the first argument passed to builtins.toString"); v.mkString(*s, context); } @@ -3353,10 +3317,10 @@ static RegisterPrimOp primop_toString({ non-negative. */ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, Value & v) { - int start = state.forceInt(*args[0], pos, "While evaluating the first argument (the start offset) passed to builtins.substring: "); - int len = state.forceInt(*args[1], pos, "While evaluating the second argument (the substring length) passed to builtins.substring: "); + int start = state.forceInt(*args[0], pos, "While evaluating the first argument (the start offset) passed to builtins.substring"); + int len = state.forceInt(*args[1], pos, "While evaluating the second argument (the substring length) passed to builtins.substring"); PathSet context; - auto s = state.coerceToString(pos, *args[2], context, "While evaluating the third argument (the string) passed to builtins.substring: "); + auto s = state.coerceToString(pos, *args[2], context, "While evaluating the third argument (the string) passed to builtins.substring"); if (start < 0) throw EvalError({ @@ -3390,7 +3354,7 @@ static RegisterPrimOp primop_substring({ static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.stringLength: "); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.stringLength"); v.mkInt(s->size()); } @@ -3407,7 +3371,7 @@ static RegisterPrimOp primop_stringLength({ /* Return the cryptographic hash of a string in base-16. */ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto type = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hashString: "); + auto type = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.hashString"); std::optional ht = parseHashType(type); if (!ht) throw Error({ @@ -3416,7 +3380,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, }); PathSet context; // discarded - auto s = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.hashString: "); + auto s = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.hashString"); v.mkString(hashString(*ht, s).to_string(Base16, false)); } @@ -3455,14 +3419,14 @@ std::shared_ptr makeRegexCache() void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto re = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.match: "); + auto re = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.match"); try { auto regex = state.regexCache->get(re); PathSet context; - const auto str = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.match: "); + const auto str = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.match"); std::cmatch match; if (!std::regex_match(str.begin(), str.end(), match, regex)) { @@ -3536,14 +3500,14 @@ static RegisterPrimOp primop_match({ non-matching parts interleaved by the lists of the matching groups. */ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto re = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.split: "); + auto re = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.split"); try { auto regex = state.regexCache->get(re); PathSet context; - const auto str = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.split: "); + const auto str = state.forceString(*args[1], context, pos, "While evaluating the second argument passed to builtins.split"); auto begin = std::cregex_iterator(str.begin(), str.end(), regex); auto end = std::cregex_iterator(); @@ -3642,8 +3606,8 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * { PathSet context; - auto sep = state.forceString(*args[0], context, pos, "While evaluating the first argument (the separator string) passed to builtins.concatStringsSep: "); - state.forceList(*args[1], pos, "While evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep: "); + auto sep = state.forceString(*args[0], context, pos, "While evaluating the first argument (the separator string) passed to builtins.concatStringsSep"); + state.forceList(*args[1], pos, "While evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep"); std::string res; res.reserve((args[1]->listSize() + 32) * sep.size()); @@ -3651,7 +3615,7 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * for (auto elem : args[1]->listItems()) { if (first) first = false; else res += sep; - res += *state.coerceToString(pos, *elem, context, "While evaluating one element of the list of strings to concat passed to builtins.concatStringsSep: "); + res += *state.coerceToString(pos, *elem, context, "While evaluating one element of the list of strings to concat passed to builtins.concatStringsSep"); } v.mkString(res, context); @@ -3670,8 +3634,8 @@ static RegisterPrimOp primop_concatStringsSep({ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.replaceStrings: "); - state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.replaceStrings: "); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.replaceStrings"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.replaceStrings"); if (args[0]->listSize() != args[1]->listSize()) throw EvalError({ .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), @@ -3681,18 +3645,18 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar std::vector from; from.reserve(args[0]->listSize()); for (auto elem : args[0]->listItems()) - from.emplace_back(state.forceString(*elem, pos, "While evaluating one of the strings to replace in builtins.replaceStrings: ")); + from.emplace_back(state.forceString(*elem, pos, "While evaluating one of the strings to replace in builtins.replaceStrings")); std::vector> to; to.reserve(args[1]->listSize()); for (auto elem : args[1]->listItems()) { PathSet ctx; - auto s = state.forceString(*elem, ctx, pos, "While evaluating one of the replacement strings of builtins.replaceStrings: "); + auto s = state.forceString(*elem, ctx, pos, "While evaluating one of the replacement strings of builtins.replaceStrings"); to.emplace_back(s, std::move(ctx)); } PathSet context; - auto s = state.forceString(*args[2], context, pos, "While evaluating the third argument passed to builtins.replaceStrings: "); + auto s = state.forceString(*args[2], context, pos, "While evaluating the third argument passed to builtins.replaceStrings"); std::string res; // Loops one past last character to handle the case where 'from' contains an empty string. @@ -3750,7 +3714,7 @@ static RegisterPrimOp primop_replaceStrings({ static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto name = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.parseDrvName: "); + auto name = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.parseDrvName"); DrvName parsed(name); auto attrs = state.buildBindings(2); attrs.alloc(state.sName).mkString(parsed.name); @@ -3774,8 +3738,8 @@ static RegisterPrimOp primop_parseDrvName({ static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto version1 = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.compareVersions: "); - auto version2 = state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.compareVersions: "); + auto version1 = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.compareVersions"); + auto version2 = state.forceStringNoCtx(*args[1], pos, "While evaluating the second argument passed to builtins.compareVersions"); v.mkInt(compareVersions(version1, version2)); } @@ -3794,7 +3758,7 @@ static RegisterPrimOp primop_compareVersions({ static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto version = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.splitVersion: "); + auto version = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.splitVersion"); auto iter = version.cbegin(); Strings components; while (iter != version.cend()) { diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 858227018..403e38258 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -85,7 +85,7 @@ class ExternalValueBase /* Coerce the value to a string. Defaults to uncoercable, i.e. throws an * error. */ - virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string & errorCtx) const; + virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string_view & errorCtx) const; /* Compare to another value of the same type. Defaults to uncomparable, * i.e. always false. diff --git a/src/libutil/error.cc b/src/libutil/error.cc index dcd2f82a5..134b99893 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -185,15 +185,15 @@ void printAtPos(const ErrPos & pos, std::ostream & out) if (pos) { switch (pos.origin) { case foFile: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos)); + out << fmt(ANSI_BLUE " at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos)); break; } case foString: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos)); + out << fmt(ANSI_BLUE " at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos)); break; } case foStdin: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos)); + out << fmt(ANSI_BLUE " at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos)); break; } default: @@ -269,7 +269,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s oss << einfo.msg << "\n"; if (einfo.errPos.has_value() && *einfo.errPos) { - oss << "\n"; printAtPos(*einfo.errPos, oss); auto loc = getCodeLines(*einfo.errPos); @@ -278,26 +277,34 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s if (loc.has_value()) { oss << "\n"; printCodeLines(oss, "", *einfo.errPos, *loc); - oss << "\n"; } + oss << "\n"; } // traces - if (showTrace && !einfo.traces.empty()) { + if (!einfo.traces.empty()) { + unsigned int count = 0; for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { + if (!showTrace && count > 3) { + oss << "\n" << "(truncated)" << "\n"; + break; + } + + if (iter->hint.str().empty()) continue; + count++; oss << "\n" << "… " << iter->hint.str() << "\n"; if (iter->pos.has_value() && (*iter->pos)) { + count++; auto pos = iter->pos.value(); - oss << "\n"; printAtPos(pos, oss); auto loc = getCodeLines(pos); if (loc.has_value()) { oss << "\n"; printCodeLines(oss, "", pos, *loc); - oss << "\n"; } + oss << "\n"; } } } diff --git a/src/libutil/error.hh b/src/libutil/error.hh index d55e1d701..ad29f8d2a 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -130,6 +130,8 @@ protected: public: unsigned int status = 1; // exit status + BaseError(const BaseError &) = default; + template BaseError(unsigned int status, const Args & ... args) : err { .level = lvlError, .msg = hintfmt(args...) } @@ -165,10 +167,14 @@ public: const std::string & msg() const { return calcWhat(); } const ErrorInfo & info() const { calcWhat(); return err; } + void pushTrace(Trace trace) { + err.traces.push_front(trace); + } + template - BaseError & addTrace(std::optional e, const std::string & fs, const Args & ... args) + BaseError & addTrace(std::optional e, const std::string_view & fs, const Args & ... args) { - return addTrace(e, hintfmt(fs, args...)); + return addTrace(e, hintfmt(std::string(fs), args...)); } BaseError & addTrace(std::optional e, hintformat hint);