From 00e242feed5ac848f5948dd2731bfabe603999ce Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 3 Mar 2022 21:46:20 +0100 Subject: [PATCH 01/97] Add some context to coercion error strings --- src/libexpr/eval.cc | 36 +++++++++++++----------------------- src/libexpr/eval.hh | 5 ++--- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 2d7309738..99a06f14e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -850,7 +850,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) if (env->type == Env::HasWithExpr) { if (noEval) return 0; Value * v = allocValue(); - evalAttrs(*env->up, (Expr *) env->values[0], *v); + evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, ""); env->values[0] = v; env->type = Env::HasWithAttrs; } @@ -1055,31 +1055,21 @@ void EvalState::eval(Expr * e, Value & v) } -inline bool EvalState::evalBool(Env & env, Expr * e) +inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string & location) { Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwTypeError("value is %1% while a Boolean was expected", v); + throwTypeError(pos, (location + ": value is %1% while a Boolean was expected").c_str(), v); return v.boolean; } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) -{ - Value v; - e->eval(*this, env, v); - if (v.type() != nBool) - throwTypeError(pos, "value is %1% while a Boolean was expected", v); - return v.boolean; -} - - -inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v) +inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & location) { e->eval(*this, env, v); if (v.type() != nAttrs) - throwTypeError("value is %1% while a set was expected", v); + throwTypeError(pos, (location + ": value is %1% while a set was expected").c_str(), v); } @@ -1577,13 +1567,13 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v) { - (state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v); + (state.evalBool(env, cond, pos, "In the condition of the if operator") ? then : else_)->eval(state, env, v); } void ExprAssert::eval(EvalState & state, Env & env, Value & v) { - if (!state.evalBool(env, cond, pos)) { + if (!state.evalBool(env, cond, pos, "In the condition of the assert statement")) { std::ostringstream out; cond->show(out); throwAssertionError(pos, "assertion '%1%' failed", out.str()); @@ -1594,7 +1584,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(!state.evalBool(env, e)); + v.mkBool(!state.evalBool(env, e, noPos, "In the argument of the not operator")); // XXX: FIXME: ! } @@ -1616,27 +1606,27 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v) void ExprOpAnd::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos)); + v.mkBool(state.evalBool(env, e1, pos, "In the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "In the right operand of the AND (&&) operator")); } void ExprOpOr::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); + v.mkBool(state.evalBool(env, e1, pos, "In the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "In the right operand of the OR (||) operator")); } void ExprOpImpl::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); + v.mkBool(!state.evalBool(env, e1, pos, "In the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "In the right operand of the IMPL (->) operator")); } void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) { Value v1, v2; - state.evalAttrs(env, e1, v1); - state.evalAttrs(env, e2, v2); + state.evalAttrs(env, e1, v1, pos, "In the left operand of the update (//) operator"); + state.evalAttrs(env, e2, v2, pos, "In the right operand of the update (//) operator"); state.nrOpUpdates++; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 800b00eef..f6298f74c 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -217,9 +217,8 @@ public: /* Evaluation the expression, then verify that it has the expected type. */ - inline bool evalBool(Env & env, Expr * e); - inline bool evalBool(Env & env, Expr * e, const Pos & pos); - inline void evalAttrs(Env & env, Expr * e, Value & v); + inline bool evalBool(Env & env, Expr * e, const Pos & pos, const std::string & location); + inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & location); /* 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 From be1f0697468bd6c0f2be4f7e058270c161098e9f Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 4 Mar 2022 05:04:47 +0100 Subject: [PATCH 02/97] Add error context for most basic coercions --- src/libcmd/installables.cc | 2 +- src/libexpr/attr-path.cc | 2 +- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval-inline.hh | 16 +- src/libexpr/eval.cc | 70 +++--- src/libexpr/eval.hh | 22 +- src/libexpr/flake/flake.cc | 10 +- src/libexpr/get-drvs.cc | 16 +- src/libexpr/nixexpr.hh | 1 - src/libexpr/parser.y | 28 +-- src/libexpr/primops.cc | 303 ++++++++++++++------------ src/libexpr/primops/context.cc | 18 +- src/libexpr/primops/fetchMercurial.cc | 6 +- src/libexpr/primops/fetchTree.cc | 14 +- src/libexpr/primops/fromTOML.cc | 2 +- src/nix/bundle.cc | 2 +- src/nix/flake.cc | 46 ++-- src/nix/main.cc | 2 +- src/nix/prefetch.cc | 16 +- src/nix/repl.cc | 4 +- src/nix/upgrade-nix.cc | 2 +- 21 files changed, 309 insertions(+), 275 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 3209456bf..e85482239 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -524,7 +524,7 @@ ref openEvalCache( auto vFlake = state.allocValue(); flake::callFlake(state, *lockedFlake, *vFlake); - state.forceAttrs(*vFlake, noPos); + state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake"); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index eb0e706c7..63ee8b1ae 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -112,7 +112,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) // FIXME: is it possible to extract the Pos object instead of doing this // toString + parsing? - auto pos = state.forceString(*v2); + auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation"); auto colon = pos.rfind(':'); if (colon == std::string::npos) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 00d0749f9..66078b694 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -336,7 +336,7 @@ Value & AttrCursor::getValue() if (!_value) { if (parent) { auto & vParent = parent->first->getValue(); - root->state.forceAttrs(vParent, noPos); + root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set"); auto attr = vParent.attrs->get(parent->second); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index aef1f6351..112c70e7b 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)) +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v, const std::string & s2)) { throw TypeError({ - .msg = hintfmt(s, showType(v)), + .msg = hintfmt(s, showType(v), s2), .errPos = pos }); } @@ -52,26 +52,26 @@ void EvalState::forceValue(Value & v, Callable getPos) } -inline void EvalState::forceAttrs(Value & v, const Pos & pos) +inline void EvalState::forceAttrs(Value & v, const Pos & pos, const std::string & errorCtx) { - forceAttrs(v, [&]() { return pos; }); + forceAttrs(v, [&]() { return pos; }, errorCtx); } template -inline void EvalState::forceAttrs(Value & v, Callable getPos) +inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string & errorCtx) { forceValue(v, getPos); if (v.type() != nAttrs) - throwTypeError(getPos(), "value is %1% while a set was expected", v); + throwTypeError(getPos(), "%2%: value is %1% while a set was expected", v, errorCtx); } -inline void EvalState::forceList(Value & v, const Pos & pos) +inline void EvalState::forceList(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (!v.isList()) - throwTypeError(pos, "value is %1% while a list was expected", v); + throwTypeError(pos, "%2%: value is %1% while a list was expected", v, errorCtx); } /* Note: Various places expect the allocated memory to be zeroed. */ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 99a06f14e..6a21701f3 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -297,7 +297,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) } else { Value nameValue; name.expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue); + state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name"); return state.symbols.create(nameValue.string.s); } } @@ -763,9 +763,17 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) +//LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) +//{ +// throw TypeError(s, showType(v)); +//} + +LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) { - throw TypeError(s, showType(v)); + throw AssertionError({ + .msg = hintfmt(s, showType(v)), + .errPos = pos + }); } LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1)) @@ -1060,7 +1068,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std: Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwTypeError(pos, (location + ": value is %1% while a Boolean was expected").c_str(), v); + throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, location); return v.boolean; } @@ -1069,7 +1077,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos { e->eval(*this, env, v); if (v.type() != nAttrs) - throwTypeError(pos, (location + ": value is %1% while a set was expected").c_str(), v); + throwTypeError(pos, "%2%: value is %1% while a set was expected", v, location); } @@ -1142,7 +1150,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Hence we need __overrides.) */ if (hasOverrides) { Value * vOverrides = (*v.attrs)[overrides->second.displ].value; - state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }); + state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute"); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); for (auto & i : *v.attrs) newBnds->push_back(i); @@ -1170,7 +1178,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) state.forceValue(nameVal, i.pos); if (nameVal.type() == nNull) continue; - state.forceStringNoCtx(nameVal); + state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute"); Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) @@ -1260,7 +1268,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) return; } } else { - state.forceAttrs(*vAttrs, pos); + state.forceAttrs(*vAttrs, pos, "While selecting an attribute"); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) throwEvalError(pos, "attribute '%1%' missing", name); } @@ -1351,7 +1359,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & env2.values[displ++] = args[0]; else { - forceAttrs(*args[0], pos); + forceAttrs(*args[0], pos, "While evaluating the value passed as argument to a function expecting an attribute set"); if (!lambda.arg.empty()) env2.values[displ++] = args[0]; @@ -1567,7 +1575,8 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v) { - (state.evalBool(env, cond, pos, "In the condition of the if operator") ? then : else_)->eval(state, env, 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); } @@ -1665,18 +1674,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) Value v1; e1->eval(state, env, v1); Value v2; e2->eval(state, env, v2); Value * lists[2] = { &v1, &v2 }; - state.concatLists(v, 2, lists, pos); + state.concatLists(v, 2, lists, pos, "While evaluating one of the elements to concatenate"); } -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos) +void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string & errorCtx) { nrListConcats++; Value * nonEmpty = 0; size_t len = 0; for (size_t n = 0; n < nrLists; ++n) { - forceList(*lists[n], pos); + forceList(*lists[n], pos, errorCtx); auto l = lists[n]->listSize(); len += l; if (l) nonEmpty = lists[n]; @@ -1824,31 +1833,31 @@ void EvalState::forceValueDeep(Value & v) } -NixInt EvalState::forceInt(Value & v, const Pos & pos) +NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (v.type() != nInt) - throwTypeError(pos, "value is %1% while an integer was expected", v); + throwTypeError(pos, "%2%: value is %1% while an integer was expected", v, errorCtx); return v.integer; } -NixFloat EvalState::forceFloat(Value & v, const Pos & pos) +NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwTypeError(pos, "value is %1% while a float was expected", v); + throwTypeError(pos, "%2%: value is %1% while a float was expected", v, errorCtx); return v.fpoint; } -bool EvalState::forceBool(Value & v, const Pos & pos) +bool EvalState::forceBool(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (v.type() != nBool) - throwTypeError(pos, "value is %1% while a Boolean was expected", v); + throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, errorCtx); return v.boolean; } @@ -1859,22 +1868,19 @@ bool EvalState::isFunctor(Value & fun) } -void EvalState::forceFunction(Value & v, const Pos & pos) +void EvalState::forceFunction(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwTypeError(pos, "value is %1% while a function was expected", v); + throwTypeError(pos, "%2%: value is %1% while a function was expected", v, errorCtx); } -std::string_view EvalState::forceString(Value & v, const Pos & pos) +std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::string & errorCtx) { forceValue(v, pos); if (v.type() != nString) { - if (pos) - throwTypeError(pos, "value is %1% while a string was expected", v); - else - throwTypeError("value is %1% while a string was expected", v); + throwTypeError(pos, "%2%: value is %1% while a string was expected", v, errorCtx); } return v.string.s; } @@ -1911,23 +1917,23 @@ std::vector> Value::getContext() } -std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos) +std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, const std::string & errorCtx) { - auto s = forceString(v, pos); + auto s = forceString(v, pos, errorCtx); copyContext(v, context); return s; } -std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos) +std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, const std::string & errorCtx) { - auto s = forceString(v, pos); + auto s = forceString(v, pos, errorCtx); if (v.string.context) { if (pos) - throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", + 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("the string '%1%' is not allowed to refer to a store path (such as '%2%')", + 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]); } return s; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index f6298f74c..ae2a5f4f1 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -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); - NixFloat forceFloat(Value & v, const Pos & pos); - bool forceBool(Value & v, const Pos & pos); + 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); - void forceAttrs(Value & v, const Pos & pos); + void forceAttrs(Value & v, const Pos & pos, const std::string & errorCtx); template - inline void forceAttrs(Value & v, Callable getPos); + inline void forceAttrs(Value & v, Callable getPos, const std::string & errorCtx); - inline void forceList(Value & v, const Pos & pos); - void forceFunction(Value & v, const Pos & pos); // either lambda or primop - std::string_view forceString(Value & v, const Pos & pos = noPos); - std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos); - std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos); + 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); /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ @@ -363,7 +363,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); + void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string & errorCtx); /* Print statistics. */ void printStats(); diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 6a1aca40d..b0f087b11 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -255,7 +255,7 @@ static Flake getFlake( for (auto & setting : *nixConfig->value->attrs) { forceTrivialValue(state, *setting.value, *setting.pos); if (setting.value->type() == nString) - flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))}); + flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos, ""))}); else if (setting.value->type() == nPath) { PathSet emptyContext = {}; flake.config.settings.emplace( @@ -263,16 +263,16 @@ static Flake getFlake( state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); } else if (setting.value->type() == nInt) - flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)}); + flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos, "")}); else if (setting.value->type() == nBool) - flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, *setting.pos) }}); + flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, *setting.pos, "") }}); else if (setting.value->type() == nList) { std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", setting.name, showType(*setting.value)); - ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos)); + ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos, "")); } flake.config.settings.insert({setting.name, ss}); } @@ -708,7 +708,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va { state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); + std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake")); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 7630c5ff4..975490d36 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -50,7 +50,7 @@ std::string DrvInfo::queryName() const if (name == "" && attrs) { auto i = attrs->find(state->sName); if (i == attrs->end()) throw TypeError("derivation name missing"); - name = state->forceStringNoCtx(*i->value); + name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo"); } return name; } @@ -60,7 +60,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo"); } return system; } @@ -108,15 +108,15 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos); + state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo"); /* For each output... */ for (auto elem : i->value->listItems()) { /* Evaluate the corresponding set. */ - std::string name(state->forceStringNoCtx(*elem, *i->pos)); + std::string name(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo")); Bindings::iterator out = attrs->find(state->symbols.create(name)); if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, *i->pos); + state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output"); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); @@ -151,7 +151,7 @@ std::string DrvInfo::queryOutputName() const { if (outputName == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutputName); - outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : ""; + outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo") : ""; } return outputName; } @@ -163,7 +163,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos); + state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo"); meta = a->value->attrs; return meta; } @@ -364,7 +364,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos)) + if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`")) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 12b54b8eb..dc001fef8 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -4,7 +4,6 @@ #include "symbol-table.hh" #include "error.hh" - namespace nix { diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 919b9cfae..76d45180b 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(CUR_POS, $2, $4, $6); } + : IF expr THEN expr ELSE expr { $$ = new ExprIf(makeCurPos(@2, data), $2, $4, $6); } | expr_op ; @@ -405,21 +405,21 @@ expr_op | '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } - | expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); } - | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); } - | expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); } - | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); } - | expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); } - | expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); } - | expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); } - | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } + | expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); } + | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); } + | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); } + | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); } + | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); } + | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); } + | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); } + | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '+' expr_op - { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } - | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } - | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } - | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } - | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); } + { $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } + | expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); } + | expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); } + | expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); } + | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); } | expr_app ; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3124025aa..e05e6d45a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -195,9 +195,9 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS , "/"), **state.vImportedDrvToDerivation); } - state.forceFunction(**state.vImportedDrvToDerivation, pos); + state.forceFunction(**state.vImportedDrvToDerivation, pos, "While evaluating imported-drv-to-derivation.nix.gen.hh"); v.mkApp(*state.vImportedDrvToDerivation, w); - state.forceAttrs(v, pos); + 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); + 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)); + 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); + 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) { @@ -381,7 +381,6 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) } } - /* Return a string representing the type of the expression. */ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v) { @@ -535,8 +534,10 @@ static RegisterPrimOp primop_isPath({ struct CompareValues { EvalState & state; + const Pos & pos; + const std::string errorCtx; - CompareValues(EvalState & state) : state(state) { }; + CompareValues(EvalState & state, const Pos & pos, const std::string && errorCtx) : state(state), pos(pos), errorCtx(std::move(errorCtx)) { }; bool operator () (Value * v1, Value * v2) const { @@ -545,7 +546,10 @@ struct CompareValues if (v1->type() == nInt && v2->type() == nFloat) return v1->integer < v2->fpoint; if (v1->type() != v2->type()) - throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); + throw EvalError({ + .msg = hintfmt("%s: cannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .errPos = pos, + }); switch (v1->type()) { case nInt: return v1->integer < v2->integer; @@ -567,7 +571,10 @@ struct CompareValues } } default: - throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); + throw EvalError({ + .msg = hintfmt("%s: cannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .errPos = pos, + }); } } }; @@ -619,7 +626,7 @@ static Bindings::iterator getAttr( static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[0], pos, "While evaluating the first argument passed to builtins.genericClosure"); /* Get the start set. */ Bindings::iterator startSet = getAttr( @@ -630,7 +637,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar pos ); - state.forceList(*startSet->value, pos); + state.forceList(*startSet->value, pos, "While evaluating the `startSet` attribute passed to builtins.genericClosure"); ValueList workSet; for (auto elem : startSet->value->listItems()) @@ -645,7 +652,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar pos ); - state.forceValue(*op->value, pos); + state.forceFunction(*op->value, pos, "While evaluating the `operator` attribute passed to builtins.genericClosure"); /* Construct the closure by applying the operator to element of `workSet', adding the result to `workSet', continuing until @@ -653,19 +660,19 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar ValueList res; // `doneKeys' doesn't need to be a GC root, because its values are // reachable from res. - auto cmp = CompareValues(state); + auto cmp = CompareValues(state, pos, "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); + state.forceAttrs(*e, pos, "While evaluating one item to be part of the genericClosure"); Bindings::iterator key = e->attrs->find(state.sKey); if (key == e->attrs->end()) throw EvalError({ - .msg = hintfmt("attribute 'key' required"), + .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); @@ -676,7 +683,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar /* Call the `operator' function with `e' as argument. */ Value call; call.mkApp(op->value, e); - state.forceList(call, pos); + state.forceList(call, pos, "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 : call.listItems()) { @@ -750,7 +757,7 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { static void prim_ceil(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto value = state.forceFloat(*args[0], args[0]->determinePos(pos)); + auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "While evaluating the first argument passed to builtins.ceil"); v.mkInt(ceil(value)); } @@ -769,7 +776,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)); + auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "While evaluating the first argument passed to builtins.floor"); v.mkInt(floor(value)); } @@ -826,7 +833,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)); + 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("")); } @@ -924,7 +931,7 @@ static RegisterPrimOp primop_trace({ derivation. */ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); + 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( @@ -938,7 +945,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * std::string drvName; Pos & posDrvName(*attr->pos); try { - drvName = state.forceStringNoCtx(*attr->value, pos); + 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; @@ -948,14 +955,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)) + 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); + 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; @@ -1021,7 +1028,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } if (i->name == state.sContentAddressed) { - contentAddressed = state.forceBool(*i->value, pos); + contentAddressed = state.forceBool(*i->value, pos, "While evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict"); if (contentAddressed) settings.requireExperimentalFeature(Xp::CaDerivations); } @@ -1029,7 +1036,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ else if (i->name == state.sArgs) { - state.forceList(*i->value, pos); + state.forceList(*i->value, pos, "While evaluating the `args` attribute passed to builtins.derivationStrict"); for (auto elem : i->value->listItems()) { auto s = state.coerceToString(posDrvName, *elem, context, true).toOwned(); drv.args.push_back(s); @@ -1048,21 +1055,21 @@ 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); + 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); + 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); + 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); + 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)); + 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); + 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)); + ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "While evaluating an element of the `outputs` attribute passed to builtins.derivationStrict")); handleOutputs(ss); } @@ -1275,7 +1282,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))); + v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.placeholder"))); } static RegisterPrimOp primop_placeholder({ @@ -1467,17 +1474,17 @@ 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); + 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); + 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); + prefix = state.forceStringNoCtx(*i->value, pos, "While evaluating the `prefix` attribute passed to an element of the list passed to builtins.findFile"); i = getAttr( state, @@ -1504,7 +1511,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); + 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))); } @@ -1518,7 +1525,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); + 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({ @@ -1725,7 +1732,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); + auto s = state.forceStringNoCtx(*args[0], pos, "While evaluating the first argument passed to builtins.fromJSON"); try { parseJSON(state, s, v); } catch (JSONParseError &e) { @@ -1754,8 +1761,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)); - std::string contents(state.forceString(*args[1], context, pos)); + 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; @@ -1912,7 +1919,7 @@ static void addPath( Value res; state.callFunction(*filterFun, 2, args, res, pos); - return state.forceBool(res, pos); + return state.forceBool(res, pos, "While evaluating the return value of the path filter function"); }) : defaultPathFilter; std::optional expectedStorePath; @@ -2009,7 +2016,7 @@ static RegisterPrimOp primop_filterSource({ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.path"); Path path; std::string name; Value * filterFun = nullptr; @@ -2022,14 +2029,14 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value if (n == "path") path = state.coerceToPath(*attr.pos, *attr.value, context); else if (attr.name == state.sName) - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the `name` attribute passed to builtins.path"); else if (n == "filter") { state.forceValue(*attr.value, pos); filterFun = attr.value; } else if (n == "recursive") - method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) }; + 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), 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), @@ -2092,7 +2099,7 @@ static RegisterPrimOp primop_path({ strings. */ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrNames"); state.mkList(v, args[0]->attrs->size()); @@ -2119,7 +2126,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); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.attrValues"); state.mkList(v, args[0]->attrs->size()); @@ -2150,8 +2157,8 @@ 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); - state.forceAttrs(*args[1], pos); + 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", @@ -2180,8 +2187,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); - state.forceAttrs(*args[1], pos); + 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(); @@ -2198,8 +2205,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); - state.forceAttrs(*args[1], pos); + 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()); } @@ -2232,8 +2239,8 @@ static RegisterPrimOp primop_isAttrs({ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); - state.forceList(*args[1], pos); + 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 @@ -2241,7 +2248,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); + 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()); @@ -2280,14 +2287,14 @@ 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); + 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); + state.forceAttrs(*v2, pos, "While evaluating an element of the list passed to builtins.listToAttrs"); Bindings::iterator j = getAttr( state, @@ -2297,7 +2304,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, pos ); - auto name = state.forceStringNoCtx(*j->value, *j->pos); + 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) { @@ -2342,8 +2349,8 @@ static RegisterPrimOp primop_listToAttrs({ static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], pos); - state.forceAttrs(*args[1], pos); + 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())); @@ -2368,14 +2375,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)); - state.forceList(*args[1], pos); + 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); + 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; @@ -2448,7 +2455,7 @@ static RegisterPrimOp primop_functionArgs({ /* */ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[1], pos); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.mapAttrs"); auto attrs = state.buildBindings(args[1]->attrs->size()); @@ -2489,15 +2496,15 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args std::map> attrsSeen; - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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); + 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) { @@ -2587,7 +2594,7 @@ static RegisterPrimOp primop_isList({ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) { - state.forceList(list, pos); + 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), @@ -2600,7 +2607,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), 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({ @@ -2635,7 +2642,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); + 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"), @@ -2666,13 +2673,17 @@ 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); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.map"); state.mkList(v, args[1]->listSize()); - for (unsigned int n = 0; n < v.listSize(); ++n) - (v.listElems()[n] = state.allocValue())->mkApp( - args[0], args[1]->listElems()[n]); + 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]); + }; } static RegisterPrimOp primop_map({ @@ -2696,8 +2707,14 @@ static RegisterPrimOp primop_map({ returns true. */ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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"); // FIXME: putting this on the stack is risky. Value * vs[args[1]->listSize()]; @@ -2707,7 +2724,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)) + 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; @@ -2735,7 +2752,7 @@ 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); + 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)) { res = true; @@ -2757,8 +2774,8 @@ 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); - state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); + 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"); } static RegisterPrimOp primop_concatLists({ @@ -2773,7 +2790,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); + state.forceList(*args[0], pos, "While evaluating the first argument passed to builtins.length"); v.mkInt(args[0]->listSize()); } @@ -2790,8 +2807,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); - state.forceList(*args[2], pos); + 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]; @@ -2823,13 +2840,13 @@ static RegisterPrimOp primop_foldlStrict({ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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); + 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; @@ -2872,7 +2889,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); + auto len = state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.genList"); if (len < 0) throw EvalError({ @@ -2910,10 +2927,16 @@ 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.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.sort"); auto len = args[1]->listSize(); + if (len == 0) { + v = *args[1]; + return; + } + + 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) { state.forceValue(*args[1]->listElems()[n], pos); @@ -2924,12 +2947,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)(a, b); + return CompareValues(state, pos, "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); + 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 @@ -2961,8 +2984,8 @@ static RegisterPrimOp primop_sort({ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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(); @@ -2973,7 +2996,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)) + 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); @@ -3021,15 +3044,15 @@ static RegisterPrimOp primop_partition({ static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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); + 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); @@ -3073,8 +3096,8 @@ static RegisterPrimOp primop_groupBy({ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceFunction(*args[0], pos); - state.forceList(*args[1], pos); + 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]; @@ -3084,7 +3107,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))); + 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; @@ -3123,9 +3146,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) + state.forceFloat(*args[1], pos)); + 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) + state.forceInt(*args[1], pos)); + 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({ @@ -3142,9 +3167,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) - state.forceFloat(*args[1], pos)); + 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) - state.forceInt(*args[1], pos)); + 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({ @@ -3161,9 +3188,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) * state.forceFloat(*args[1], pos)); + 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) * state.forceInt(*args[1], pos)); + 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({ @@ -3180,7 +3209,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); + 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"), @@ -3188,10 +3217,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) / state.forceFloat(*args[1], pos)); + v.mkFloat(state.forceFloat(*args[0], pos, "While evaluating the first operand of the division") / f2); } else { - NixInt i1 = state.forceInt(*args[0], pos); - NixInt i2 = state.forceInt(*args[1], pos); + 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({ @@ -3214,7 +3243,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) & state.forceInt(*args[1], pos)); + 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({ @@ -3228,7 +3258,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) | state.forceInt(*args[1], pos)); + 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({ @@ -3242,7 +3273,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) ^ state.forceInt(*args[1], pos)); + 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({ @@ -3258,7 +3290,8 @@ static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Va { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); - CompareValues comp{state}; + // pos is exact here, no need for a message. + CompareValues comp(state, pos, ""); v.mkBool(comp(args[0], args[1])); } @@ -3319,8 +3352,8 @@ 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); - int len = state.forceInt(*args[1], pos); + int start = state.forceInt(*args[0], pos, "While evaluating the first argument passed to builtins.substring"); + int len = state.forceInt(*args[1], pos, "While evaluating the second argument passed to builtins.substring"); PathSet context; auto s = state.coerceToString(pos, *args[2], context); @@ -3373,7 +3406,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); + 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({ @@ -3382,7 +3415,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, }); PathSet context; // discarded - auto s = state.forceString(*args[1], context, pos); + 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)); } @@ -3421,14 +3454,14 @@ std::shared_ptr makeRegexCache() void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) { - auto re = state.forceStringNoCtx(*args[0], pos); + 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); + 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)) { @@ -3502,14 +3535,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); + 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); + 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(); @@ -3608,8 +3641,8 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * { PathSet context; - auto sep = state.forceString(*args[0], context, pos); - state.forceList(*args[1], pos); + auto sep = state.forceString(*args[0], context, pos, "While evaluating the first argument passed to builtins.concatStringsSep"); + state.forceList(*args[1], pos, "While evaluating the second argument passed to builtins.concatStringsSep"); std::string res; res.reserve((args[1]->listSize() + 32) * sep.size()); @@ -3636,8 +3669,8 @@ static RegisterPrimOp primop_concatStringsSep({ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceList(*args[0], pos); - state.forceList(*args[1], pos); + 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"), @@ -3647,18 +3680,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)); + 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); + 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); + 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. @@ -3716,7 +3749,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); + 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); @@ -3740,8 +3773,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); - auto version2 = state.forceStringNoCtx(*args[1], pos); + 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)); } @@ -3760,7 +3793,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); + 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/primops/context.cc b/src/libexpr/primops/context.cc index 3701bd442..f1feb4dc6 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -17,7 +17,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - state.forceString(*args[0], context, pos); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext"); v.mkBool(!context.empty()); } @@ -72,7 +72,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Strings outputs; }; PathSet context; - state.forceString(*args[0], context, pos); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext"); auto contextInfos = std::map(); for (const auto & p : context) { Path drv; @@ -136,9 +136,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext); static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto orig = state.forceString(*args[0], context, pos); + auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext"); - state.forceAttrs(*args[1], pos); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext"); auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); @@ -150,16 +150,16 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos); + state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context"); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context")) context.insert(i.name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) { + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context")) { if (!isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), @@ -172,7 +172,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos); + state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context"); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), @@ -180,7 +180,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos); + auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context"); context.insert(concatStrings("!", name, "!", i.name)); } } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index b7f715859..199f7508e 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -19,8 +19,6 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos); - for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") @@ -28,14 +26,14 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos); + auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial"); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 9c2da2178..a40eddfca 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -102,7 +102,7 @@ static void fetchTree( state.forceValue(*args[0], pos); if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree"); fetchers::Attrs attrs; @@ -112,7 +112,7 @@ static void fetchTree( .msg = hintfmt("unexpected attribute 'type'"), .errPos = pos }); - type = state.forceStringNoCtx(*aType->value, *aType->pos); + type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree"); } else if (!type) throw Error({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), @@ -195,16 +195,14 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos); - for (auto & attr : *args[0]->attrs) { std::string n(attr.name); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos); + url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch"); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch"), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), @@ -218,7 +216,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, .errPos = pos }); } else - url = state.forceStringNoCtx(*args[0], pos); + url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch"); url = resolveUri(*url); diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index dd4280030..6bfd8e285 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) { - auto toml = state.forceStringNoCtx(*args[0], pos); + auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML"); std::istringstream tomlStream(std::string{toml}); diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 7ed558dee..aa074edc2 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -111,7 +111,7 @@ struct CmdBundle : InstallableCommand if (!outLink) { auto &attr = vRes->attrs->need(evalState->sName); - outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos); + outLink = evalState->forceStringNoCtx(*attr.value, *attr.pos, ""); } // TODO: will crash if not a localFSStore? diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 144f8f886..703c3beb2 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, std::function callback) { auto pos = vFlake.determinePos(noPos); - state.forceAttrs(vFlake, pos); + state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs"); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); assert(aOutputs); - state.forceAttrs(*aOutputs->value, pos); + state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake"); auto sHydraJobs = state.symbols.create("hydraJobs"); @@ -401,13 +401,13 @@ struct CmdFlakeCheck : FlakeCommand checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) { try { - state->forceAttrs(v, pos); + state->forceAttrs(v, pos, ""); if (state->isDerivation(v)) throw Error("jobset should not be a derivation at top-level"); for (auto & attr : *v.attrs) { - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos, ""); auto attrPath2 = attrPath + "." + (std::string) attr.name; if (state->isDerivation(*attr.value)) { Activity act(*logger, lvlChatty, actUnknown, @@ -429,7 +429,7 @@ struct CmdFlakeCheck : FlakeCommand fmt("checking NixOS configuration '%s'", attrPath)); Bindings & bindings(*state->allocBindings(0)); auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first; - state->forceAttrs(*vToplevel, pos); + state->forceValue(*vToplevel, pos); if (!state->isDerivation(*vToplevel)) throw Error("attribute 'config.system.build.toplevel' is not a derivation"); } catch (Error & e) { @@ -443,7 +443,7 @@ struct CmdFlakeCheck : FlakeCommand Activity act(*logger, lvlChatty, actUnknown, fmt("checking template '%s'", attrPath)); - state->forceAttrs(v, pos); + state->forceAttrs(v, pos, ""); if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (attr->name == state->symbols.create("path")) { @@ -457,7 +457,7 @@ struct CmdFlakeCheck : FlakeCommand throw Error("template '%s' lacks attribute 'path'", attrPath); if (auto attr = v.attrs->get(state->symbols.create("description"))) - state->forceStringNoCtx(*attr->value, *attr->pos); + state->forceStringNoCtx(*attr->value, *attr->pos, ""); else throw Error("template '%s' lacks attribute 'description'", attrPath); @@ -513,10 +513,10 @@ struct CmdFlakeCheck : FlakeCommand warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement); if (name == "checks") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos, ""); for (auto & attr2 : *attr.value->attrs) { auto drvPath = checkDerivation( fmt("%s.%s.%s", name, attr.name, attr2.name), @@ -528,10 +528,10 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "packages" || name == "devShells") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos, ""); for (auto & attr2 : *attr.value->attrs) checkDerivation( fmt("%s.%s.%s", name, attr.name, attr2.name), @@ -540,10 +540,10 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "apps") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos, ""); for (auto & attr2 : *attr.value->attrs) checkApp( fmt("%s.%s.%s", name, attr.name, attr2.name), @@ -552,7 +552,7 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "defaultPackage" || name == "devShell") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); checkDerivation( @@ -562,7 +562,7 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "defaultApp") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); checkApp( @@ -572,7 +572,7 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "legacyPackages") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); // FIXME: do getDerivations? @@ -583,7 +583,7 @@ struct CmdFlakeCheck : FlakeCommand checkOverlay(name, vOutput, pos); else if (name == "overlays") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) checkOverlay(fmt("%s.%s", name, attr.name), *attr.value, *attr.pos); @@ -593,14 +593,14 @@ struct CmdFlakeCheck : FlakeCommand checkModule(name, vOutput, pos); else if (name == "nixosModules") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) checkModule(fmt("%s.%s", name, attr.name), *attr.value, *attr.pos); } else if (name == "nixosConfigurations") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) checkNixOSConfiguration(fmt("%s.%s", name, attr.name), *attr.value, *attr.pos); @@ -613,14 +613,14 @@ struct CmdFlakeCheck : FlakeCommand checkTemplate(name, vOutput, pos); else if (name == "templates") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) checkTemplate(fmt("%s.%s", name, attr.name), *attr.value, *attr.pos); } else if (name == "defaultBundler") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); checkBundler( @@ -630,10 +630,10 @@ struct CmdFlakeCheck : FlakeCommand } else if (name == "bundlers") { - state->forceAttrs(vOutput, pos); + state->forceAttrs(vOutput, pos, ""); for (auto & attr : *vOutput.attrs) { checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos, ""); for (auto & attr2 : *attr.value->attrs) { checkBundler( fmt("%s.%s.%s", name, attr.name, attr2.name), diff --git a/src/nix/main.cc b/src/nix/main.cc index b923f2535..0c6286686 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -196,7 +196,7 @@ static void showHelp(std::vector subcommand, MultiCommand & topleve if (!attr) throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); - auto markdown = state.forceString(*attr->value); + auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text"); RunPager pager; std::cout << renderMarkdownToTerminal(markdown) << "\n"; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index f2dd44ba4..4376da896 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url) Value vMirrors; // FIXME: use nixpkgs flake state.eval(state.parseExprFromString("import ", "."), vMirrors); - state.forceAttrs(vMirrors, noPos); + state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors"); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) throw Error("unknown mirror name '%s'", mirrorName); - state.forceList(*mirrorList->value, noPos); + state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration"); if (mirrorList->value->listSize() < 1) throw Error("mirror URL '%s' did not expand to anything", url); - std::string mirror(state.forceString(*mirrorList->value->listElems()[0])); + std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror")); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); } @@ -196,27 +196,27 @@ static int main_nix_prefetch_url(int argc, char * * argv) Value vRoot; state->evalFile(path, vRoot); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); - state->forceAttrs(v, noPos); + state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch"); /* Extract the URL. */ auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos); + state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch"); if (attr.value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0]); + url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list"); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); if (!attr2) printInfo("warning: this does not look like a fetchurl call"); else - unpack = state->forceString(*attr2->value) == "recursive"; + unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch") == "recursive"; /* Extract the name. */ if (!name) { auto attr3 = v.attrs->get(state->symbols.create("name")); if (!attr3) - name = state->forceString(*attr3->value); + name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch"); } } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 3a51a13e6..7011ff939 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -342,7 +342,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix) Expr * e = parseString(expr); Value v; e->eval(*state, *env, v); - state->forceAttrs(v, noPos); + state->forceAttrs(v, noPos, "nevermind, it is ignored anyway"); for (auto & i : *v.attrs) { std::string name = i.name; @@ -675,7 +675,7 @@ void NixRepl::reloadFiles() void NixRepl::addAttrsToScope(Value & attrs) { - state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }); + state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope"); if (displ + attrs.attrs->size() >= envSize) throw Error("environment full; cannot add more variables"); diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 17a5a77ee..1f433a199 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Bindings & bindings(*state->allocBindings(0)); auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; - return store->parseStorePath(state->forceString(*v2)); + return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version")); } }; From 3a5855353e5dc2986c38bb66eaff5538dea70da1 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 4 Mar 2022 21:47:58 +0100 Subject: [PATCH 03/97] Add detailed error mesage for coerceTo{String,Path} --- src/libexpr/eval.cc | 34 +++++++------- src/libexpr/eval.hh | 7 +-- src/libexpr/flake/flake.cc | 2 +- src/libexpr/get-drvs.cc | 6 +-- src/libexpr/primops.cc | 66 +++++++++++++++------------ src/libexpr/primops/context.cc | 4 +- src/libexpr/primops/fetchMercurial.cc | 4 +- src/libexpr/primops/fetchTree.cc | 4 +- src/libexpr/value.hh | 2 +- src/nix-env/user-env.cc | 4 +- src/nix/bundle.cc | 4 +- src/nix/eval.cc | 2 +- src/nix/flake.cc | 2 +- src/nix/repl.cc | 4 +- 14 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 6a21701f3..b2a8651d9 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1775,7 +1775,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) /* 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, ""); sSize += part->size(); s.emplace_back(std::move(part)); } @@ -1958,14 +1958,15 @@ std::optional EvalState::tryAttrsToString(const Pos & pos, Value & if (i != v.attrs->end()) { Value v1; callFunction(*i->value, v, v1, pos); - return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned(); + return coerceToString(pos, v1, context, coerceMore, copyToStore, + "While evaluating the result of the `toString` attribute").toOwned(); } return {}; } BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, - bool coerceMore, bool copyToStore, bool canonicalizePath) + bool coerceMore, bool copyToStore, bool canonicalizePath, const std::string & errorCtx) { forceValue(v, pos); @@ -1988,12 +1989,12 @@ 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, "cannot coerce a set to a string"); - return coerceToString(pos, *i->value, context, coerceMore, copyToStore); + if (i == v.attrs->end()) throwTypeError(pos, "%2%: cannot coerce %1% to a string", v, errorCtx); + return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } if (v.type() == nExternal) - return v.external->coerceToString(pos, context, coerceMore, copyToStore); + return v.external->coerceToString(pos, context, coerceMore, copyToStore, errorCtx); if (coerceMore) { @@ -2008,7 +2009,8 @@ 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); + result += *coerceToString(pos, *v2, context, coerceMore, copyToStore, canonicalizePath, + errorCtx + ": While evaluating one element of the list"); if (n < v.listSize() - 1 /* !!! not quite correct */ && (!v2->isList() || v2->listSize() != 0)) @@ -2018,7 +2020,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } } - throwTypeError(pos, "cannot coerce %1% to a string", v); + throwTypeError(pos, "%2%: cannot coerce %1% to a string", v, errorCtx); } @@ -2046,22 +2048,22 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path) } -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) +Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx) { - auto path = coerceToString(pos, v, context, false, false).toOwned(); + auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); + throwEvalError(pos, "%2%: string '%1%' doesn't represent an absolute path", path, errorCtx); return path; } -StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context) +StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx) { - auto path = coerceToString(pos, v, context, false, false).toOwned(); + auto path = coerceToString(pos, v, context, false, false, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; throw EvalError({ - .msg = hintfmt("path '%1%' is not in the Nix store", path), + .msg = hintfmt("%2%: path '%1%' is not in the Nix store", path, errorCtx), .errPos = pos }); } @@ -2263,10 +2265,10 @@ void EvalState::printStats() } -std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const +std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string & errorCtx) const { throw TypeError({ - .msg = hintfmt("cannot coerce %1% to a string", showType()), + .msg = hintfmt("%2%: cannot coerce %1% to a string", showType(), errorCtx), .errPos = pos }); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index ae2a5f4f1..9e8ac4926 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -262,17 +262,18 @@ public: referenced paths are copied to the Nix store as a side effect. */ BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true, - bool canonicalizePath = true); + bool canonicalizePath = true, + const std::string & 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); + Path coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx); /* Like coerceToPath, but the result must be a store path. */ - StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context); + StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx); public: diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index b0f087b11..fe2daaf21 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -260,7 +260,7 @@ static Flake getFlake( PathSet emptyContext = {}; flake.config.settings.emplace( setting.name, - state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); + state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned()); } else if (setting.value->type() == nInt) flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos, "")}); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 975490d36..9bb545430 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -74,7 +74,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)}; + drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo")}; } return drvPath.value_or(std::nullopt); } @@ -94,7 +94,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context); + outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo"); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -122,7 +122,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context)); + outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo")); } } else outputs.emplace("out", queryOutPath()); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index e05e6d45a..14db24f68 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); + 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; @@ -350,10 +350,12 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) }); } PathSet context; - auto program = state.coerceToString(pos, *elems[0], context, false, false).toOwned(); + auto program = state.coerceToString(pos, *elems[0], context, false, false, + "While evaluating the first element of the argument passed to builtins.exec").toOwned(); Strings commandArgs; for (unsigned int i = 1; i < args[0]->listSize(); ++i) { - commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false).toOwned()); + commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false, + "While evaluating an element of the argument passed to builtins.exec").toOwned()); } try { auto _ = state.realiseContext(context); // FIXME: Handle CA derivations @@ -714,7 +716,8 @@ static RegisterPrimOp primop_abort({ .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context).toOwned(); + auto s = state.coerceToString(pos, *args[0], context, + "While evaluating the error message passed to builtins.abort").toOwned(); throw Abort("evaluation aborted with the following error message: '%1%'", s); } }); @@ -732,7 +735,8 @@ static RegisterPrimOp primop_throw({ .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context).toOwned(); + auto s = state.coerceToString(pos, *args[0], context, + "While evaluating the error message passed to builtin.throw").toOwned(); throw ThrownError(s); } }); @@ -744,7 +748,8 @@ static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * a v = *args[1]; } catch (Error & e) { PathSet context; - e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned()); + e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context, + "While evaluating the error message passed to builtins.addErrorContext").toOwned()); throw; } } @@ -757,7 +762,8 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { static void prim_ceil(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.ceil"); + auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), + "While evaluating the first argument passed to builtins.ceil"); v.mkInt(ceil(value)); } @@ -1028,7 +1034,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } if (i->name == state.sContentAddressed) { - contentAddressed = state.forceBool(*i->value, pos, "While evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict"); + contentAddressed = state.forceBool(*i->value, pos, + "While evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict"); if (contentAddressed) settings.requireExperimentalFeature(Xp::CaDerivations); } @@ -1036,9 +1043,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ else if (i->name == state.sArgs) { - state.forceList(*i->value, pos, "While evaluating the `args` attribute passed to builtins.derivationStrict"); + state.forceList(*i->value, pos, + "While evaluating the `args` attribute passed to builtins.derivationStrict"); for (auto elem : i->value->listItems()) { - auto s = state.coerceToString(posDrvName, *elem, context, true).toOwned(); + auto s = state.coerceToString(posDrvName, *elem, context, true, + "While evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned(); drv.args.push_back(s); } } @@ -1074,7 +1083,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } } else { - auto s = state.coerceToString(*i->pos, *i->value, context, true).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); @@ -1306,7 +1315,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); + Path path = state.coerceToPath(pos, *args[0], context, "While evaluating the first argument passed to builtins.toPath"); v.mkString(canonPath(path), context); } @@ -1337,7 +1346,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)); + 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. */ @@ -1407,7 +1416,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)), 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({ @@ -1427,7 +1436,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); + 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); } @@ -1484,7 +1493,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va 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 passed to 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, @@ -1495,7 +1504,8 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va ); PathSet context; - auto path = state.coerceToString(pos, *i->value, context, false, false).toOwned(); + auto path = state.coerceToString(pos, *i->value, context, false, false, + "While evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned(); try { auto rewrites = state.realiseContext(context); @@ -1945,7 +1955,7 @@ 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); + Path path = state.coerceToPath(pos, *args[1], context, "While evaluating the second argument (the path to filter) passed to builtins.filterSource"); state.forceValue(*args[0], pos); if (args[0]->type() != nFunction) @@ -2027,7 +2037,7 @@ 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); + 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"); else if (n == "filter") { @@ -2045,7 +2055,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value } if (path.empty()) throw EvalError({ - .msg = hintfmt("'path' required"), + .msg = hintfmt("Missing required 'path' attribute in the first argument to builtins.path"), .errPos = pos }); if (name.empty()) @@ -3318,7 +3328,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); + auto s = state.coerceToString(pos, *args[0], context, true, false, "While evaluating the first argument passed to builtins.toString"); v.mkString(*s, context); } @@ -3352,10 +3362,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 passed to builtins.substring"); - int len = state.forceInt(*args[1], pos, "While evaluating the second argument 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); + auto s = state.coerceToString(pos, *args[2], context, "While evaluating the third argument (the string) passed to builtins.substring"); if (start < 0) throw EvalError({ @@ -3389,7 +3399,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); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.stringLength"); v.mkInt(s->size()); } @@ -3641,8 +3651,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 passed to builtins.concatStringsSep"); - state.forceList(*args[1], pos, "While evaluating the second argument 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()); @@ -3650,7 +3660,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); + 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); diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index f1feb4dc6..78320dc09 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardStringContext"); v.mkString(*s); } @@ -33,7 +33,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto s = state.coerceToString(pos, *args[0], context); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardOutputDependency"); PathSet context2; for (auto & p : context) diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index 199f7508e..dcaf3d362 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -22,7 +22,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); + url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. @@ -48,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar }); } else - url = state.coerceToString(pos, *args[0], context, false, false).toOwned(); + url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial").toOwned(); // FIXME: git externals probably can be used to bypass the URI // whitelist. Ah well. diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index a40eddfca..2baa272cb 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -125,7 +125,7 @@ static void fetchTree( if (attr.name == state.sType) continue; state.forceValue(*attr.value, *attr.pos); if (attr.value->type() == nPath || attr.value->type() == nString) { - auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); + auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false, "").toOwned(); attrs.emplace(attr.name, attr.name == "url" ? type == "git" @@ -151,7 +151,7 @@ static void fetchTree( input = fetchers::Input::fromAttrs(std::move(attrs)); } else { - auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned(); + auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher").toOwned(); if (type == "git") { fetchers::Attrs attrs; diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index d0fa93e92..858227018 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; + virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string & errorCtx) const; /* Compare to another value of the same type. Defaults to uncomparable, * i.e. always false. diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index af4f350ff..b30ccc7f6 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -132,9 +132,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); PathSet context; Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); - auto topLevelDrv = state.coerceToStorePath(*aDrvPath.pos, *aDrvPath.value, context); + auto topLevelDrv = state.coerceToStorePath(*aDrvPath.pos, *aDrvPath.value, context, ""); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); - auto topLevelOut = state.coerceToStorePath(*aOutPath.pos, *aOutPath.value, context); + auto topLevelOut = state.coerceToStorePath(*aOutPath.pos, *aOutPath.value, context, ""); /* Realise the resulting store expression. */ debug("building user environment"); diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index aa074edc2..9ff66899e 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand throw Error("the bundler '%s' does not produce a derivation", bundler.what()); PathSet context2; - auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2); + auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2, ""); auto attr2 = vRes->attrs->get(evalState->sOutPath); if (!attr2) throw Error("the bundler '%s' does not produce a derivation", bundler.what()); - auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2); + auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2, ""); store->buildPaths({ DerivedPath::Built { drvPath } }); diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 8cd04d5fe..8ee3ba45b 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -107,7 +107,7 @@ struct CmdEval : MixJSON, InstallableCommand else if (raw) { stopProgressBar(); - std::cout << *state->coerceToString(noPos, *v, context); + std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output"); } else if (json) { diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 703c3beb2..356f89713 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -448,7 +448,7 @@ struct CmdFlakeCheck : FlakeCommand if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (attr->name == state->symbols.create("path")) { PathSet context; - auto path = state->coerceToPath(*attr->pos, *attr->value, context); + auto path = state->coerceToPath(*attr->pos, *attr->value, context, ""); if (!store->isInStore(path)) throw Error("template '%s' has a bad 'path' attribute"); // TODO: recursively check the flake in 'path'. diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 7011ff939..0cb68552c 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -461,7 +461,7 @@ bool NixRepl::processLine(std::string line) if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context); + auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit"); pos.file = state->symbols.create(*filename); } else if (v.isLambda()) { pos = v.lambda.fun->pos; @@ -780,7 +780,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m Bindings::iterator i = v.attrs->find(state->sDrvPath); PathSet context; if (i != v.attrs->end()) - str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context)); + str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation")); else str << "???"; str << "»"; From ed02fa3c403d4ab92c0e32c9d033231f46f87c85 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 4 Mar 2022 22:15:30 +0100 Subject: [PATCH 04/97] s/forceValue/forceFunction/ where applicable --- src/libexpr/primops.cc | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 14db24f68..b56293c30 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1957,15 +1957,7 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args PathSet context; Path path = state.coerceToPath(pos, *args[1], context, "While evaluating the second argument (the path to filter) passed to builtins.filterSource"); - state.forceValue(*args[0], pos); - if (args[0]->type() != nFunction) - throw TypeError({ - .msg = hintfmt( - "first argument in call to 'filterSource' is not a function but %1%", - showType(*args[0])), - .errPos = pos - }); - + 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); } @@ -2040,10 +2032,9 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value 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"); - else if (n == "filter") { - state.forceValue(*attr.value, pos); - filterFun = attr.value; - } else if (n == "recursive") + else if (n == "filter") + 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") }; else if (n == "sha256") expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `sha256` attribute passed to builtins.path"), htSHA256); From 57684d62475c9ec37e2abc2b7df2ec3581d0f011 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 4 Mar 2022 22:51:56 +0100 Subject: [PATCH 05/97] fixup! s/forceValue/forceFunction/ where applicable --- src/libexpr/eval.cc | 19 +++---------------- src/libexpr/primops.cc | 10 +++++----- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index b2a8651d9..03329645f 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -747,14 +747,6 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) -{ - throw TypeError({ - .msg = hintfmt(s), - .errPos = pos - }); -} - LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) { throw TypeError({ @@ -763,11 +755,6 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const }); } -//LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) -//{ -// throw TypeError(s, showType(v)); -//} - LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) { throw AssertionError({ @@ -1371,7 +1358,7 @@ 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, "%1% called without required argument '%2%'", + if (!i.def) throwTypeError(pos, "Function %1% called without required argument '%2%'", lambda, i.name); env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1387,7 +1374,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & user. */ for (auto & i : *args[0]->attrs) if (!lambda.formals->has(i.name)) - throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); + throwTypeError(pos, "Function %1% called with unexpected argument '%2%'", lambda, i.name); abort(); // can't happen } } @@ -2059,7 +2046,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, cons StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string & errorCtx) { - auto path = coerceToString(pos, v, context, false, false, errorCtx).toOwned(); + auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; throw EvalError({ diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index b56293c30..a72d0f12a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -683,12 +683,12 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar res.push_back(e); /* Call the `operator' function with `e' as argument. */ - Value call; - call.mkApp(op->value, e); - state.forceList(call, pos, "While evaluating the return value of the `operator` passed to builtins.genericClosure"); + Value res; + state.callFunction(*op->value, 1, &e, res, *op->pos); + state.forceList(res, pos, "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 : call.listItems()) { + for (auto elem : res.listItems()) { state.forceValue(*elem, pos); workSet.push_back(elem); } @@ -2033,7 +2033,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value else if (attr.name == state.sName) 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") }; else if (n == "sha256") From cbbbf368818f147f9ce4562b497536a9e46fd657 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 4 Mar 2022 22:55:14 +0100 Subject: [PATCH 06/97] Use 'errorCtx' name everywhere --- src/libexpr/eval.cc | 8 ++++---- src/libexpr/eval.hh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 03329645f..168b3582a 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1050,21 +1050,21 @@ void EvalState::eval(Expr * e, Value & v) } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string & location) +inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string & errorCtx) { Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, location); + throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, errorCtx); return v.boolean; } -inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & location) +inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & errorCtx) { e->eval(*this, env, v); if (v.type() != nAttrs) - throwTypeError(pos, "%2%: value is %1% while a set was expected", v, location); + throwTypeError(pos, "%2%: value is %1% while a set was expected", v, errorCtx); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 9e8ac4926..dadd46433 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 & location); - inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string & location); + 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); /* 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 From 407801592719d77fcdad53b447e909e3ab255086 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 5 Mar 2022 21:18:30 +0100 Subject: [PATCH 07/97] DRY addPrimOp --- src/libexpr/eval.cc | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 168b3582a..d3ab5ad08 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -636,25 +636,7 @@ void EvalState::addConstant(const std::string & name, Value * v) Value * EvalState::addPrimOp(const std::string & name, size_t arity, PrimOpFun primOp) { - auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; - Symbol sym = symbols.create(name2); - - /* Hack to make constants lazy: turn them into a application of - the primop to a dummy value. */ - if (arity == 0) { - auto vPrimOp = allocValue(); - vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym }); - Value v; - v.mkApp(vPrimOp, vPrimOp); - return addConstant(name, v); - } - - Value * v = allocValue(); - v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); - staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); - baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(sym, v)); - return v; + return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = symbols.create(name) }); } From 1b5a8db148dd4403c64bc058ce1a4a5e46d52031 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 5 Mar 2022 21:19:04 +0100 Subject: [PATCH 08/97] change error location for genericClosure operator errors --- src/libexpr/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index a72d0f12a..fff382c5e 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -684,7 +684,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar /* Call the `operator' function with `e' as argument. */ Value res; - state.callFunction(*op->value, 1, &e, res, *op->pos); + state.callFunction(*op->value, 1, &e, res, pos); state.forceList(res, pos, "While evaluating the return value of the `operator` passed to builtins.genericClosure"); /* Add the values returned by the operator to the work set. */ From 13c4dc65327c9654c47e6d80c0f4e1797b999f97 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Mon, 7 Mar 2022 11:33:03 +0100 Subject: [PATCH 09/97] more fixes --- src/libcmd/installables.cc | 2 +- src/libexpr/attr-path.cc | 2 +- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval-inline.hh | 4 +- src/libexpr/eval.cc | 56 ++--- src/libexpr/eval.hh | 2 +- src/libexpr/flake/flake.cc | 2 +- src/libexpr/get-drvs.cc | 22 +- src/libexpr/primops.cc | 298 +++++++++++++------------- src/libexpr/primops/context.cc | 22 +- src/libexpr/primops/fetchMercurial.cc | 8 +- src/libexpr/primops/fetchTree.cc | 14 +- src/libexpr/primops/fromTOML.cc | 2 +- src/nix/eval.cc | 2 +- src/nix/flake.cc | 4 +- src/nix/main.cc | 2 +- src/nix/prefetch.cc | 16 +- src/nix/repl.cc | 8 +- src/nix/upgrade-nix.cc | 2 +- 19 files changed, 239 insertions(+), 231 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e85482239..437380a4d 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -524,7 +524,7 @@ ref openEvalCache( auto vFlake = state.allocValue(); flake::callFlake(state, *lockedFlake, *vFlake); - state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake"); + state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake: "); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 63ee8b1ae..930400bd0 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -112,7 +112,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) // FIXME: is it possible to extract the Pos object instead of doing this // toString + parsing? - auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation"); + auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation: "); auto colon = pos.rfind(':'); if (colon == std::string::npos) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 66078b694..6528725e7 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -336,7 +336,7 @@ Value & AttrCursor::getValue() if (!_value) { if (parent) { auto & vParent = parent->first->getValue(); - root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set"); + root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set: "); auto attr = vParent.attrs->get(parent->second); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 112c70e7b..ad53a74cb 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -63,7 +63,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string { forceValue(v, getPos); if (v.type() != nAttrs) - throwTypeError(getPos(), "%2%: value is %1% while a set was expected", v, errorCtx); + throwTypeError(getPos(), "%2%value is %1% while a set was expected", v, errorCtx); } @@ -71,7 +71,7 @@ inline void EvalState::forceList(Value & v, const Pos & pos, const std::string & { forceValue(v, pos); if (!v.isList()) - throwTypeError(pos, "%2%: value is %1% while a list was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a list was expected", v, errorCtx); } /* Note: Various places expect the allocated memory to be zeroed. */ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d3ab5ad08..55c624cb9 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -297,7 +297,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) } else { Value nameValue; name.expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name"); + state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name: "); return state.symbols.create(nameValue.string.s); } } @@ -720,6 +720,14 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const }); } +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), + .errPos = pos + }); +} + LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2)) { // p1 is where the error occurred; p2 is a position mentioned in the message. @@ -1037,7 +1045,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std: Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a Boolean was expected", v, errorCtx); return v.boolean; } @@ -1046,7 +1054,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos { e->eval(*this, env, v); if (v.type() != nAttrs) - throwTypeError(pos, "%2%: value is %1% while a set was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a set was expected", v, errorCtx); } @@ -1119,7 +1127,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Hence we need __overrides.) */ if (hasOverrides) { Value * vOverrides = (*v.attrs)[overrides->second.displ].value; - state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute"); + state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute: "); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); for (auto & i : *v.attrs) newBnds->push_back(i); @@ -1147,7 +1155,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) state.forceValue(nameVal, i.pos); if (nameVal.type() == nNull) continue; - state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute"); + state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute: "); Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) @@ -1237,7 +1245,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) return; } } else { - state.forceAttrs(*vAttrs, pos, "While selecting an attribute"); + state.forceAttrs(*vAttrs, pos, "While selecting an attribute: "); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) throwEvalError(pos, "attribute '%1%' missing", name); } @@ -1328,7 +1336,7 @@ 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"); + forceAttrs(*args[0], pos, "While evaluating the value passed as argument to a function expecting an attribute set: "); if (!lambda.arg.empty()) env2.values[displ++] = args[0]; @@ -1570,7 +1578,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)); + v.mkBool(state.eqValues(v1, v2, pos, "")); } @@ -1578,7 +1586,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)); + v.mkBool(!state.eqValues(v1, v2, pos, "")); } @@ -1806,7 +1814,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string & error { forceValue(v, pos); if (v.type() != nInt) - throwTypeError(pos, "%2%: value is %1% while an integer was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while an integer was expected", v, errorCtx); return v.integer; } @@ -1817,7 +1825,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string & e if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwTypeError(pos, "%2%: value is %1% while a float was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a float was expected", v, errorCtx); return v.fpoint; } @@ -1826,7 +1834,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos, const std::string & errorC { forceValue(v, pos); if (v.type() != nBool) - throwTypeError(pos, "%2%: value is %1% while a Boolean was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a Boolean was expected", v, errorCtx); return v.boolean; } @@ -1841,7 +1849,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos, const std::string & er { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwTypeError(pos, "%2%: value is %1% while a function was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a function was expected", v, errorCtx); } @@ -1849,7 +1857,7 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::s { forceValue(v, pos); if (v.type() != nString) { - throwTypeError(pos, "%2%: value is %1% while a string was expected", v, errorCtx); + throwTypeError(pos, "%2%value is %1% while a string was expected", v, errorCtx); } return v.string.s; } @@ -1958,7 +1966,7 @@ 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()) throwTypeError(pos, "%2%cannot coerce %1% to a string", v, errorCtx); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -1989,7 +1997,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } } - throwTypeError(pos, "%2%: cannot coerce %1% to a string", v, errorCtx); + throwTypeError(pos, "%2%cannot coerce %1% to a string", v, errorCtx); } @@ -2021,7 +2029,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, cons { 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); + throwEvalError(pos, "%2%string '%1%' doesn't represent an absolute path", path, errorCtx); return path; } @@ -2032,13 +2040,13 @@ StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & con if (auto storePath = store->maybeParseStorePath(path)) return *storePath; throw EvalError({ - .msg = hintfmt("%2%: path '%1%' is not in the Nix store", path, errorCtx), + .msg = hintfmt("%2%path '%1%' is not in the Nix store", path, errorCtx), .errPos = pos }); } -bool EvalState::eqValues(Value & v1, Value & v2) +bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::string & errorCtx) { forceValue(v1, noPos); forceValue(v2, noPos); @@ -2077,7 +2085,7 @@ bool EvalState::eqValues(Value & v1, Value & v2) case nList: if (v1.listSize() != v2.listSize()) return false; for (size_t n = 0; n < v1.listSize(); ++n) - if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; + if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false; return true; case nAttrs: { @@ -2087,7 +2095,7 @@ bool EvalState::eqValues(Value & v1, Value & v2) Bindings::iterator i = v1.attrs->find(sOutPath); Bindings::iterator j = v2.attrs->find(sOutPath); if (i != v1.attrs->end() && j != v2.attrs->end()) - return eqValues(*i->value, *j->value); + return eqValues(*i->value, *j->value, pos, errorCtx); } if (v1.attrs->size() != v2.attrs->size()) return false; @@ -2095,7 +2103,7 @@ bool EvalState::eqValues(Value & v1, Value & v2) /* Otherwise, compare the attributes one by one. */ Bindings::iterator i, j; for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) - if (i->name != j->name || !eqValues(*i->value, *j->value)) + if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx)) return false; return true; @@ -2112,7 +2120,7 @@ bool EvalState::eqValues(Value & v1, Value & v2) return v1.fpoint == v2.fpoint; default: - throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); + throwEvalError(pos, "%3%cannot compare %1% with %2%", showType(v1), showType(v2), errorCtx); } } @@ -2237,7 +2245,7 @@ void EvalState::printStats() std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string & errorCtx) const { throw TypeError({ - .msg = hintfmt("%2%: cannot coerce %1% to a string", showType(), errorCtx), + .msg = hintfmt("%2%cannot coerce %1% to a string", showType(), errorCtx), .errPos = pos }); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index dadd46433..bd817c9fe 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -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); + bool eqValues(Value & v1, Value & v2, const Pos & pos, const std::string & errorCtx); bool isFunctor(Value & fun); diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index fe2daaf21..560747210 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -708,7 +708,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va { state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake")); + std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake: ")); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 9bb545430..94f26e925 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -50,7 +50,7 @@ std::string DrvInfo::queryName() const if (name == "" && attrs) { auto i = attrs->find(state->sName); if (i == attrs->end()) throw TypeError("derivation name missing"); - name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo"); + name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo: "); } return name; } @@ -60,7 +60,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo"); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo: "); } return system; } @@ -74,7 +74,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo")}; + drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo: ")}; } return drvPath.value_or(std::nullopt); } @@ -94,7 +94,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo"); + outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo: "); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -108,21 +108,21 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo"); + state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo: "); /* For each output... */ for (auto elem : i->value->listItems()) { /* Evaluate the corresponding set. */ - std::string name(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo")); + std::string name(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo: ")); Bindings::iterator out = attrs->find(state->symbols.create(name)); if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output"); + state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output: "); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo")); + outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo: ")); } } else outputs.emplace("out", queryOutPath()); @@ -151,7 +151,7 @@ std::string DrvInfo::queryOutputName() const { if (outputName == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutputName); - outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo") : ""; + outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo: ") : ""; } return outputName; } @@ -163,7 +163,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo"); + state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo: "); meta = a->value->attrs; return meta; } @@ -364,7 +364,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`")) + if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`: ")) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index fff382c5e..1e22044d7 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) { @@ -549,7 +549,7 @@ struct CompareValues return v1->integer < v2->fpoint; if (v1->type() != v2->type()) throw EvalError({ - .msg = hintfmt("%s: cannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), .errPos = pos, }); switch (v1->type()) { @@ -568,13 +568,13 @@ struct CompareValues return false; } else if (i == v1->listSize()) { return true; - } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i])) { + } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx)) { return (*this)(v1->listElems()[i], v2->listElems()[i]); } } default: throw EvalError({ - .msg = hintfmt("%s: cannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), .errPos = pos, }); } @@ -628,7 +628,7 @@ static Bindings::iterator getAttr( 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], pos, "While evaluating the first argument passed to builtins.genericClosure: "); /* Get the start set. */ Bindings::iterator startSet = getAttr( @@ -639,7 +639,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar pos ); - state.forceList(*startSet->value, pos, "While evaluating the `startSet` attribute passed to builtins.genericClosure"); + state.forceList(*startSet->value, pos, "While evaluating the `startSet` attribute passed to builtins.genericClosure: "); ValueList workSet; for (auto elem : startSet->value->listItems()) @@ -654,7 +654,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar pos ); - state.forceFunction(*op->value, pos, "While evaluating the `operator` attribute passed 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 `workSet', adding the result to `workSet', continuing until @@ -668,7 +668,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar Value * e = *(workSet.begin()); workSet.pop_front(); - state.forceAttrs(*e, pos, "While evaluating one item to be part of the genericClosure"); + state.forceAttrs(*e, pos, "While evaluating one item to be part of the genericClosure: "); Bindings::iterator key = e->attrs->find(state.sKey); @@ -685,7 +685,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar /* 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"); + state.forceList(res, pos, "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()) { @@ -782,7 +782,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 +839,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,7 +937,7 @@ 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( @@ -951,7 +951,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * 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 +961,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 +1064,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 +1291,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 +1315,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 +1346,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 +1416,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 +1436,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,17 +1483,17 @@ 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, @@ -1521,7 +1521,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 +1535,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 +1742,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 +1771,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 +1929,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 +1955,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 +2018,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 +2029,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 +2100,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 +2127,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,8 +2158,8 @@ 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", @@ -2188,8 +2188,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 +2206,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 +2240,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 +2249,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,14 +2288,14 @@ 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, @@ -2305,7 +2305,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, pos ); - 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) { @@ -2350,8 +2350,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 +2376,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 +2456,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 +2497,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 +2595,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 +2608,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 +2643,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,12 +2674,12 @@ 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: "); 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"); + 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( @@ -2708,14 +2708,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 +2725,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 +2753,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)) { + if (state.eqValues(*args[0], *elem, pos, "While searching for the presence of the given element in the list: ")) { res = true; break; } @@ -2775,7 +2775,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 +2791,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 +2808,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 +2841,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 +2890,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 +2928,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 +2936,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 +2948,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, pos, "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"); + 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 +2985,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 +2997,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 +3045,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 +3097,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 +3108,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 +3147,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 +3168,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 +3189,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 +3210,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 +3218,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 +3244,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 +3259,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 +3274,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 +3319,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 +3353,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 +3390,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 +3407,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 +3416,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 +3455,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 +3536,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 +3642,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 +3651,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 +3670,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 +3681,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 +3750,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 +3774,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 +3794,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/primops/context.cc b/src/libexpr/primops/context.cc index 78320dc09..222d183e4 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_unsafeDiscardStringContext(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.unsafeDiscardStringContext"); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardStringContext: "); v.mkString(*s); } @@ -17,7 +17,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext"); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext: "); v.mkBool(!context.empty()); } @@ -33,7 +33,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); static void prim_unsafeDiscardOutputDependency(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.unsafeDiscardOutputDependency"); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardOutputDependency: "); PathSet context2; for (auto & p : context) @@ -72,7 +72,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Strings outputs; }; PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext"); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext: "); auto contextInfos = std::map(); for (const auto & p : context) { Path drv; @@ -136,9 +136,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext); static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext"); + auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext: "); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext"); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext: "); auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); @@ -150,16 +150,16 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context"); + state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context: "); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context")) + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context: ")) context.insert(i.name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context")) { + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context: ")) { if (!isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), @@ -172,7 +172,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context"); + state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context: "); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), @@ -180,7 +180,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context"); + auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context: "); context.insert(concatStrings("!", name, "!", i.name)); } } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index dcaf3d362..7434b8101 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -22,18 +22,18 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); + url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial: ").toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial"); + auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial: "); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial"); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial: "); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), @@ -48,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar }); } else - url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial").toOwned(); + url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial: ").toOwned(); // FIXME: git externals probably can be used to bypass the URI // whitelist. Ah well. diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 2baa272cb..31a8907f1 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -102,7 +102,7 @@ static void fetchTree( state.forceValue(*args[0], pos); if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree"); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree: "); fetchers::Attrs attrs; @@ -112,7 +112,7 @@ static void fetchTree( .msg = hintfmt("unexpected attribute 'type'"), .errPos = pos }); - type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree"); + type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree: "); } else if (!type) throw Error({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), @@ -151,7 +151,7 @@ static void fetchTree( input = fetchers::Input::fromAttrs(std::move(attrs)); } else { - auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher").toOwned(); + auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher: ").toOwned(); if (type == "git") { fetchers::Attrs attrs; @@ -198,11 +198,11 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, for (auto & attr : *args[0]->attrs) { std::string n(attr.name); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch"); + url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch: "); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch"), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch: "), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch"); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch: "); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), @@ -216,7 +216,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, .errPos = pos }); } else - url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch"); + url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch: "); url = resolveUri(*url); diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 6bfd8e285..2a038bb20 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) { - auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML"); + auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML: "); std::istringstream tomlStream(std::string{toml}); diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 8ee3ba45b..1d66d457c 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -107,7 +107,7 @@ struct CmdEval : MixJSON, InstallableCommand else if (raw) { stopProgressBar(); - std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output"); + std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output: "); } else if (json) { diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 356f89713..9ba6e5be1 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, std::function callback) { auto pos = vFlake.determinePos(noPos); - state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs"); + state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs: "); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); assert(aOutputs); - state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake"); + state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake: "); auto sHydraJobs = state.symbols.create("hydraJobs"); diff --git a/src/nix/main.cc b/src/nix/main.cc index 0c6286686..3ba7ce8b6 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -196,7 +196,7 @@ static void showHelp(std::vector subcommand, MultiCommand & topleve if (!attr) throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); - auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text"); + auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text: "); RunPager pager; std::cout << renderMarkdownToTerminal(markdown) << "\n"; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index 4376da896..a4a8ab605 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url) Value vMirrors; // FIXME: use nixpkgs flake state.eval(state.parseExprFromString("import ", "."), vMirrors); - state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors"); + state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors: "); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) throw Error("unknown mirror name '%s'", mirrorName); - state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration"); + state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration: "); if (mirrorList->value->listSize() < 1) throw Error("mirror URL '%s' did not expand to anything", url); - std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror")); + std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror: ")); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); } @@ -196,27 +196,27 @@ static int main_nix_prefetch_url(int argc, char * * argv) Value vRoot; state->evalFile(path, vRoot); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); - state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch"); + state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch: "); /* Extract the URL. */ auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch"); + state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch: "); if (attr.value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list"); + url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list: "); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); if (!attr2) printInfo("warning: this does not look like a fetchurl call"); else - unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch") == "recursive"; + unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch: ") == "recursive"; /* Extract the name. */ if (!name) { auto attr3 = v.attrs->get(state->symbols.create("name")); if (!attr3) - name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch"); + name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch: "); } } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 0cb68552c..038ed4911 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -342,7 +342,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix) Expr * e = parseString(expr); Value v; e->eval(*state, *env, v); - state->forceAttrs(v, noPos, "nevermind, it is ignored anyway"); + state->forceAttrs(v, noPos, "nevermind, it is ignored anyway: "); for (auto & i : *v.attrs) { std::string name = i.name; @@ -461,7 +461,7 @@ bool NixRepl::processLine(std::string line) if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit"); + auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit: "); pos.file = state->symbols.create(*filename); } else if (v.isLambda()) { pos = v.lambda.fun->pos; @@ -675,7 +675,7 @@ void NixRepl::reloadFiles() void NixRepl::addAttrsToScope(Value & attrs) { - state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope"); + state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope: "); if (displ + attrs.attrs->size() >= envSize) throw Error("environment full; cannot add more variables"); @@ -780,7 +780,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m Bindings::iterator i = v.attrs->find(state->sDrvPath); PathSet context; if (i != v.attrs->end()) - str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation")); + str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation: ")); else str << "???"; str << "»"; diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 1f433a199..ae314a7c6 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Bindings & bindings(*state->allocBindings(0)); auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; - return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version")); + return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version: ")); } }; From e6d07e0d89d964cde22894fca57a95177c085c8d Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 00:58:09 +0100 Subject: [PATCH 10/97] 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); From 1942fed6d9cee95775046c5ad3d253ab2e8ab210 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 01:10:04 +0100 Subject: [PATCH 11/97] Revert extra colon at end os strings --- src/libcmd/installables.cc | 2 +- src/libexpr/attr-path.cc | 2 +- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval.cc | 8 ++++---- src/libexpr/flake/flake.cc | 2 +- src/libexpr/get-drvs.cc | 22 +++++++++++----------- src/libexpr/primops.cc | 2 +- src/libexpr/primops/context.cc | 22 +++++++++++----------- src/libexpr/primops/fetchMercurial.cc | 8 ++++---- src/libexpr/primops/fetchTree.cc | 14 +++++++------- src/libexpr/primops/fromTOML.cc | 2 +- src/nix/eval.cc | 2 +- src/nix/flake.cc | 4 ++-- src/nix/main.cc | 2 +- src/nix/prefetch.cc | 16 ++++++++-------- src/nix/repl.cc | 8 ++++---- src/nix/upgrade-nix.cc | 2 +- 17 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 437380a4d..e85482239 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -524,7 +524,7 @@ ref openEvalCache( auto vFlake = state.allocValue(); flake::callFlake(state, *lockedFlake, *vFlake); - state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake: "); + state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake"); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 930400bd0..63ee8b1ae 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -112,7 +112,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) // FIXME: is it possible to extract the Pos object instead of doing this // toString + parsing? - auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation: "); + auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation"); auto colon = pos.rfind(':'); if (colon == std::string::npos) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 6528725e7..66078b694 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -336,7 +336,7 @@ Value & AttrCursor::getValue() if (!_value) { if (parent) { auto & vParent = parent->first->getValue(); - root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set: "); + root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set"); auto attr = vParent.attrs->get(parent->second); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 33eeef902..e3dfe6718 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -297,7 +297,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) } else { Value nameValue; name.expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name: "); + state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name"); return state.symbols.create(nameValue.string.s); } } @@ -1111,7 +1111,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Hence we need __overrides.) */ if (hasOverrides) { Value * vOverrides = (*v.attrs)[overrides->second.displ].value; - state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute: "); + state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute"); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); for (auto & i : *v.attrs) newBnds->push_back(i); @@ -1139,7 +1139,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) state.forceValue(nameVal, i.pos); if (nameVal.type() == nNull) continue; - state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute: "); + state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute"); Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) @@ -1229,7 +1229,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) return; } } else { - state.forceAttrs(*vAttrs, pos, "While selecting an attribute: "); + state.forceAttrs(*vAttrs, pos, "While selecting an attribute"); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) throwEvalError(pos, "attribute '%1%' missing", name); } diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 560747210..fe2daaf21 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -708,7 +708,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va { state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake: ")); + std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake")); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 94f26e925..9bb545430 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -50,7 +50,7 @@ std::string DrvInfo::queryName() const if (name == "" && attrs) { auto i = attrs->find(state->sName); if (i == attrs->end()) throw TypeError("derivation name missing"); - name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo: "); + name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo"); } return name; } @@ -60,7 +60,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo: "); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo"); } return system; } @@ -74,7 +74,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo: ")}; + drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo")}; } return drvPath.value_or(std::nullopt); } @@ -94,7 +94,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo: "); + outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo"); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -108,21 +108,21 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo: "); + state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo"); /* For each output... */ for (auto elem : i->value->listItems()) { /* Evaluate the corresponding set. */ - std::string name(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo: ")); + std::string name(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo")); Bindings::iterator out = attrs->find(state->symbols.create(name)); if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output: "); + state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output"); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo: ")); + outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo")); } } else outputs.emplace("out", queryOutPath()); @@ -151,7 +151,7 @@ std::string DrvInfo::queryOutputName() const { if (outputName == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutputName); - outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo: ") : ""; + outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo") : ""; } return outputName; } @@ -163,7 +163,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo: "); + state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo"); meta = a->value->attrs; return meta; } @@ -364,7 +364,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`: ")) + if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`")) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 40bb925ae..3ec5c0850 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2805,7 +2805,7 @@ 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.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; diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 222d183e4..78320dc09 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_unsafeDiscardStringContext(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.unsafeDiscardStringContext: "); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardStringContext"); v.mkString(*s); } @@ -17,7 +17,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext: "); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext"); v.mkBool(!context.empty()); } @@ -33,7 +33,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); static void prim_unsafeDiscardOutputDependency(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.unsafeDiscardOutputDependency: "); + auto s = state.coerceToString(pos, *args[0], context, "While evaluating the argument passed to builtins.unsafeDiscardOutputDependency"); PathSet context2; for (auto & p : context) @@ -72,7 +72,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Strings outputs; }; PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext: "); + state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext"); auto contextInfos = std::map(); for (const auto & p : context) { Path drv; @@ -136,9 +136,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext); static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext: "); + auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext"); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext: "); + state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext"); auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); @@ -150,16 +150,16 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context: "); + state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context"); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context: ")) + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context")) context.insert(i.name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context: ")) { + if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context")) { if (!isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), @@ -172,7 +172,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context: "); + state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context"); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), @@ -180,7 +180,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context: "); + auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context"); context.insert(concatStrings("!", name, "!", i.name)); } } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index 7434b8101..dcaf3d362 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -22,18 +22,18 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial: ").toOwned(); + url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial: "); + auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial"); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial: "); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), @@ -48,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar }); } else - url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial: ").toOwned(); + url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial").toOwned(); // FIXME: git externals probably can be used to bypass the URI // whitelist. Ah well. diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 31a8907f1..2baa272cb 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -102,7 +102,7 @@ static void fetchTree( state.forceValue(*args[0], pos); if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree: "); + state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree"); fetchers::Attrs attrs; @@ -112,7 +112,7 @@ static void fetchTree( .msg = hintfmt("unexpected attribute 'type'"), .errPos = pos }); - type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree: "); + type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree"); } else if (!type) throw Error({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), @@ -151,7 +151,7 @@ static void fetchTree( input = fetchers::Input::fromAttrs(std::move(attrs)); } else { - auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher: ").toOwned(); + auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher").toOwned(); if (type == "git") { fetchers::Attrs attrs; @@ -198,11 +198,11 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, for (auto & attr : *args[0]->attrs) { std::string n(attr.name); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch: "); + url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch"); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch: "), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch"), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch: "); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), @@ -216,7 +216,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, .errPos = pos }); } else - url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch: "); + url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch"); url = resolveUri(*url); diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 2a038bb20..6bfd8e285 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) { - auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML: "); + auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML"); std::istringstream tomlStream(std::string{toml}); diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 1d66d457c..8ee3ba45b 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -107,7 +107,7 @@ struct CmdEval : MixJSON, InstallableCommand else if (raw) { stopProgressBar(); - std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output: "); + std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output"); } else if (json) { diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 9ba6e5be1..356f89713 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, std::function callback) { auto pos = vFlake.determinePos(noPos); - state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs: "); + state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs"); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); assert(aOutputs); - state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake: "); + state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake"); auto sHydraJobs = state.symbols.create("hydraJobs"); diff --git a/src/nix/main.cc b/src/nix/main.cc index 3ba7ce8b6..0c6286686 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -196,7 +196,7 @@ static void showHelp(std::vector subcommand, MultiCommand & topleve if (!attr) throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); - auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text: "); + auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text"); RunPager pager; std::cout << renderMarkdownToTerminal(markdown) << "\n"; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index a4a8ab605..4376da896 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url) Value vMirrors; // FIXME: use nixpkgs flake state.eval(state.parseExprFromString("import ", "."), vMirrors); - state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors: "); + state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors"); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) throw Error("unknown mirror name '%s'", mirrorName); - state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration: "); + state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration"); if (mirrorList->value->listSize() < 1) throw Error("mirror URL '%s' did not expand to anything", url); - std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror: ")); + std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror")); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); } @@ -196,27 +196,27 @@ static int main_nix_prefetch_url(int argc, char * * argv) Value vRoot; state->evalFile(path, vRoot); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); - state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch: "); + state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch"); /* Extract the URL. */ auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch: "); + state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch"); if (attr.value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list: "); + url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list"); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); if (!attr2) printInfo("warning: this does not look like a fetchurl call"); else - unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch: ") == "recursive"; + unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch") == "recursive"; /* Extract the name. */ if (!name) { auto attr3 = v.attrs->get(state->symbols.create("name")); if (!attr3) - name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch: "); + name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch"); } } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 038ed4911..0cb68552c 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -342,7 +342,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix) Expr * e = parseString(expr); Value v; e->eval(*state, *env, v); - state->forceAttrs(v, noPos, "nevermind, it is ignored anyway: "); + state->forceAttrs(v, noPos, "nevermind, it is ignored anyway"); for (auto & i : *v.attrs) { std::string name = i.name; @@ -461,7 +461,7 @@ bool NixRepl::processLine(std::string line) if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit: "); + auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit"); pos.file = state->symbols.create(*filename); } else if (v.isLambda()) { pos = v.lambda.fun->pos; @@ -675,7 +675,7 @@ void NixRepl::reloadFiles() void NixRepl::addAttrsToScope(Value & attrs) { - state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope: "); + state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope"); if (displ + attrs.attrs->size() >= envSize) throw Error("environment full; cannot add more variables"); @@ -780,7 +780,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m Bindings::iterator i = v.attrs->find(state->sDrvPath); PathSet context; if (i != v.attrs->end()) - str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation: ")); + str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation")); else str << "???"; str << "»"; diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index ae314a7c6..1f433a199 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Bindings & bindings(*state->allocBindings(0)); auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; - return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version: ")); + return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version")); } }; From ad3fadb95aaae97910082335800c2be57942e154 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 10:11:36 +0100 Subject: [PATCH 12/97] fixup! Merge remote-tracking branch 'origin/master' into coerce-string --- src/libexpr/eval.cc | 124 +++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 3ad4df77e..320fd2597 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -706,31 +706,66 @@ std::optional EvalState::getDoc(Value & v) evaluator. So here are some helper functions for throwing exceptions. */ -LocalNoInlineNoReturn(void throwTypeErrorWithTrace(const Pos & pos, const char * s, const std::string & s2, const Symbol & sym, const Pos & p2, const std::string & s3)) +LocalNoInlineNoReturn(void throwTypeErrorWithTrace( + const Pos & pos, + const char * s, + const std::string & s2, + const Symbol & sym, + const Pos & p2, + const char * s3)) { throw EvalError({ .msg = hintfmt(s, s2, sym), - .errPos = pos + .errPos = pos, }).addTrace(p2, s3); } +LocalNoInlineNoReturn(void throwTypeErrorWithTrace( + const Pos & pos, + const Suggestions & suggestions, + const char * s, + const std::string & s2, + const Symbol & sym, + const Pos & p2, + const char * s3)) +{ + throw EvalError({ + .msg = hintfmt(s, s2, sym), + .errPos = pos, + .suggestions = suggestions + }).addTrace(p2, s3); +} + +LocalNoInlineNoReturn(void throwTypeErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3)) +{ + throw TypeError(ErrorInfo { + .msg = hintfmt(s, s2), + }).addTrace(p2, s3); +} + +LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3)) +{ + throw EvalError(ErrorInfo { + .msg = hintfmt(s, s2), + }).addTrace(p2, s3); +} + +LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string_view & s2, const std::string_view & s3, const Pos & p2, const std::string_view s4)) +{ + throw EvalError(ErrorInfo { + .msg = hintfmt(s, s2, s3), + }).addTrace(p2, s4); +} + LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2)) { - throw EvalError(ErrorInfo { + throw EvalError({ .msg = hintfmt(s, s2), .errPos = pos, .suggestions = suggestions, }); } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2)) -{ - throw EvalError(ErrorInfo { - .msg = hintfmt(s, s2), - .errPos = pos - }); -} - LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const Value & v)) { throw AssertionError({ @@ -748,32 +783,16 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) +LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string_view & s1)) { - throw TypeError({ - .msg = hintfmt(s), - .errPos = pos - }); + throw EvalError(s, s1); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) +LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string_view & s1, const std::string_view & s2)) { - throw TypeError({ - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos - }); + throw EvalError(s, s1, s2); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2)) -{ - throw TypeError(ErrorInfo { - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos, - .suggestions = suggestions, - }); -} - - LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) { throw TypeError(s, showType(v)); @@ -1008,11 +1027,9 @@ void EvalState::cacheFile( fileParseCache[resolvedPath] = e; try { - // Enforce that 'flake.nix' is a direct attrset, not a - // computation. - if (mustBeTrivial && - !(dynamic_cast(e))) - throw EvalError("file '%s' must be an attribute set", path); + // Enforce that 'flake.nix' is a direct attrset, not a computation. + if (mustBeTrivial && !(dynamic_cast(e))) + throwEvalError("file '%s' must be an attribute set", path); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); @@ -1036,7 +1053,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std: Value v; e->eval(*this, env, v); if (v.type() != nBool) - throw TypeError("value is %1% while a Boolean was expected", showType(v)); + throwTypeError("value is %1% while a Boolean was expected", v); return v.boolean; } catch (Error & e) { e.addTrace(pos, errorCtx); @@ -1050,7 +1067,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos try { e->eval(*this, env, v); if (v.type() != nAttrs) - throw TypeError("value is %1% while a set was expected", showType(v)); + throwTypeError("value is %1% while a set was expected", v); } catch (Error & e) { e.addTrace(pos, errorCtx); throw; @@ -1857,7 +1874,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string_view & try { forceValue(v, pos); if (v.type() != nInt) - throw TypeError("value is %1% while an integer was expected", showType(v)); + throwTypeError("value is %1% while an integer was expected", v); return v.integer; } catch (Error & e) { e.addTrace(pos, errorCtx); @@ -1873,7 +1890,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string_vie if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throw TypeError("value is %1% while a float was expected", showType(v)); + throwTypeError("value is %1% while a float was expected", v); return v.fpoint; } catch (Error & e) { e.addTrace(pos, errorCtx); @@ -1887,7 +1904,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos, const std::string_view & e try { forceValue(v, pos); if (v.type() != nBool) - throw TypeError("value is %1% while a Boolean was expected", showType(v)); + throwTypeError("value is %1% while a Boolean was expected", v); return v.boolean; } catch (Error & e) { e.addTrace(pos, errorCtx); @@ -1907,7 +1924,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos, const std::string_view try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throw TypeError("value is %1% while a function was expected", showType(v)); + throwTypeError("value is %1% while a function was expected", v); } catch (Error & e) { e.addTrace(pos, errorCtx); throw; @@ -1920,7 +1937,7 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::s try { forceValue(v, pos); if (v.type() != nString) - throw TypeError("value is %1% while a string was expected", showType(v)); + throwTypeError("value is %1% while a string was expected", v); return v.string.s; } catch (Error & e) { e.addTrace(pos, errorCtx); @@ -1974,9 +1991,9 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, const s 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]); + throwEvalError("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]); + throwEvalError("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) { @@ -2035,9 +2052,8 @@ 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()) { - throw TypeError("cannot coerce %1% to a string", showType(v)).addTrace(pos, errorCtx); - } + if (i == v.attrs->end()) + throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(v), pos, errorCtx); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -2073,14 +2089,14 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } } - throw TypeError("cannot coerce %1% to a string", showType(v)).addTrace(pos, errorCtx); + throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(v), pos, errorCtx); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throw EvalError("file names are not allowed to end in '%1%'", drvExtension); + throwEvalError("file names are not allowed to end in '%1%'", drvExtension); Path dstPath; auto i = srcToStore.find(path); @@ -2105,7 +2121,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, cons { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throw EvalError("string '%1%' doesn't represent an absolute path", path).addTrace(pos, errorCtx); + throwEvalErrorWithTrace("string '%1%' doesn't represent an absolute path", path, pos, errorCtx); return path; } @@ -2115,7 +2131,7 @@ StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & con auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - throw EvalError("path '%1%' is not in the Nix store", path).addTrace(pos, errorCtx); + throwEvalErrorWithTrace("path '%1%' is not in the Nix store", path, pos, errorCtx); } @@ -2193,7 +2209,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::str return v1.fpoint == v2.fpoint; default: - throw EvalError("cannot compare %1% with %2%", showType(v1), showType(v2)).addTrace(pos, errorCtx); + throwEvalErrorWithTrace("cannot compare %1% with %2%", showType(v1), showType(v2), pos, errorCtx); } } @@ -2317,7 +2333,7 @@ void EvalState::printStats() std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string_view & errorCtx) const { - throw TypeError("cannot coerce %1% to a string", showType()).addTrace(pos, errorCtx); + throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(), pos, errorCtx); } From 9c42c005706a68f31a28398ce63b956ad0510549 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 10:22:47 +0100 Subject: [PATCH 13/97] Fix some error kind mismatches --- src/libexpr/eval.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 320fd2597..7b843aeaf 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -714,7 +714,7 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & p2, const char * s3)) { - throw EvalError({ + throw TypeError({ .msg = hintfmt(s, s2, sym), .errPos = pos, }).addTrace(p2, s3); @@ -729,7 +729,7 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & p2, const char * s3)) { - throw EvalError({ + throw TypeError({ .msg = hintfmt(s, s2, sym), .errPos = pos, .suggestions = suggestions @@ -768,7 +768,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & s LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const Value & v)) { - throw AssertionError({ + throw EvalError({ .msg = hintfmt(s, showType(v)), .errPos = pos }); From 37e84316c28662f23613896f3592260bdf358aca Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 14:48:49 +0100 Subject: [PATCH 14/97] Try to fix issues with macos clang --- src/libexpr/eval.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 7b843aeaf..dc52f8db7 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -712,7 +712,7 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const std::string & s2, const Symbol & sym, const Pos & p2, - const char * s3)) + const std::string_view & s3)) { throw TypeError({ .msg = hintfmt(s, s2, sym), @@ -727,7 +727,7 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const std::string & s2, const Symbol & sym, const Pos & p2, - const char * s3)) + const std::string_view & s3)) { throw TypeError({ .msg = hintfmt(s, s2, sym), From 726f5836d85518fcf97d124ec0d4f84b6d4b7232 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 15:22:25 +0100 Subject: [PATCH 15/97] Try to fix issues with macos clang, v2 --- src/libexpr/eval.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index dc52f8db7..990e98f9a 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -709,12 +709,12 @@ std::optional EvalState::getDoc(Value & v) LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & pos, const char * s, - const std::string & s2, + const std::string_view & s2, const Symbol & sym, const Pos & p2, const std::string_view & s3)) { - throw TypeError({ + throw TypeError(ErrorInfo { .msg = hintfmt(s, s2, sym), .errPos = pos, }).addTrace(p2, s3); @@ -724,12 +724,12 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & pos, const Suggestions & suggestions, const char * s, - const std::string & s2, + const std::string_view & s2, const Symbol & sym, const Pos & p2, const std::string_view & s3)) { - throw TypeError({ + throw TypeError(ErrorInfo { .msg = hintfmt(s, s2, sym), .errPos = pos, .suggestions = suggestions From c2b620f3ad80f3d5c091c955401445106d0579fe Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 15:35:24 +0100 Subject: [PATCH 16/97] Try to fix issues with macos clang, v3 --- src/libexpr/eval.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 990e98f9a..ad879a85d 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -759,7 +759,7 @@ LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::st LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2)) { - throw EvalError({ + throw EvalError(ErrorInfo { .msg = hintfmt(s, s2), .errPos = pos, .suggestions = suggestions, @@ -768,7 +768,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & s LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const Value & v)) { - throw EvalError({ + throw EvalError(ErrorInfo { .msg = hintfmt(s, showType(v)), .errPos = pos }); @@ -777,7 +777,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2)) { // p1 is where the error occurred; p2 is a position mentioned in the message. - throw EvalError({ + throw EvalError(ErrorInfo { .msg = hintfmt(s, sym, p2), .errPos = p1 }); From 963b8aa39b169bf5c054449ddce39d60faacf298 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 18 Mar 2022 23:17:50 +0100 Subject: [PATCH 17/97] Explain current error trace impl --- src/libutil/error.cc | 95 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index ae0bbc06f..c66dfa112 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -288,7 +288,100 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s "?" << std::endl; } - // traces + /* + * Traces + * ------ + * + * The semantics of traces is a bit weird. We have only one option to + * print them and to make them verbose (--show-trace). In the code they + * are always collected, but they are not printed by default. The code + * also collects more traces when the option is on. This means that there + * is no way to print the simplified traces at all. + * + * I (layus) designed the code to attach positions to a restricted set of + * messages. This means that we have a lot of traces with no position at + * all, including most of the base error messages. For example "type + * error: found a string while a set was expected" has no position, but + * will come with several traces detailing it's precise relation to the + * closest know position. This makes erroring without printing traces + * quite useless. + * + * This is why I introduced the idea to always print a few traces on + * error. The number 3 is quite arbitrary, and was selected so as not to + * clutter the console on error. For the same reason, a trace with an + * error position takes more space, and counts as two traces towards the + * limit. + * + * The rest is truncated, unless --show-trace is passed. This preserves + * the same bad semantics of --show-trace to both show the trace and + * augment it with new data. Not too sure what is the best course of + * action. + * + * The issue is that it is fundamentally hard to provide a trace for a + * lazy language. The trace will only cover the current spine of the + * evaluation, missing things that have been evaluated before. For + * example, most type errors are hard to inspect because there is not + * trace for the faulty value. These errors should really print the faulty + * value itself. + * + * In function calls, the --show-trace flag triggers extra traces for each + * function invocation. These work as scopes, allowing to follow the + * current spine of the evaluation graph. Without that flag, the error + * trace should restrict itself to a restricted prefix of that trace, + * until the first scope. If we ever get to such a precise error + * reporting, there would be no need to add an arbitrary limit here. We + * could always print the full trace, and it would just be small without + * the flag. + * + * One idea I had is for XxxError.addTrace() to perform nothing if one + * scope has already been traced. Alternatively, we could stop here when + * we encounter such a scope instead of after an arbitrary number of + * traces. This however requires to augment traces with the notion of + * "scope". + * + * This is particularly visible in code like evalAttrs(...) where we have + * to make a decision between the two following options. + * + * ``` long traces + * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) + * { + * try { + * e->eval(*this, env, v); + * if (v.type() != nAttrs) + * throwTypeError("value is %1% while a set was expected", v); + * } catch (Error & e) { + * e.addTrace(pos, errorCtx); + * throw; + * } + * } + * ``` + * + * ``` short traces + * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) + * { + * e->eval(*this, env, v); + * try { + * if (v.type() != nAttrs) + * throwTypeError("value is %1% while a set was expected", v); + * } catch (Error & e) { + * e.addTrace(pos, errorCtx); + * throw; + * } + * } + * ``` + * + * The second example can be rewritten more concisely, but kept in this + * form to highlight the symmetry. The first option adds more information, + * because whatever caused an error down the line, in the generic eval + * function, will get annotated with the code location that uses and + * required it. The second option is less verbose, but does not provide + * any context at all as to where and why a failing value was required. + * + * Scopes would fix that, by adding context only when --show-trace is + * passed, and keeping the trace terse otherwise. + * + */ + if (!einfo.traces.empty()) { unsigned int count = 0; for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { From acf990c9ea9de913a500cf2b7a7492eef3bcd7b5 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 28 Apr 2022 12:54:14 +0200 Subject: [PATCH 18/97] fix errors case and wording --- src/libcmd/installables.cc | 2 +- src/libexpr/attr-path.cc | 2 +- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval.cc | 112 +++++---- src/libexpr/flake/flake.cc | 2 +- src/libexpr/get-drvs.cc | 22 +- src/libexpr/primops.cc | 332 +++++++++++++------------- src/libexpr/primops/context.cc | 26 +- src/libexpr/primops/fetchMercurial.cc | 8 +- src/libexpr/primops/fetchTree.cc | 14 +- src/libexpr/primops/fromTOML.cc | 2 +- src/nix/flake.cc | 4 +- src/nix/main.cc | 2 +- src/nix/prefetch.cc | 16 +- src/nix/repl.cc | 6 +- src/nix/upgrade-nix.cc | 2 +- 16 files changed, 278 insertions(+), 276 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 40df12d1f..2f8283fd5 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -527,7 +527,7 @@ ref openEvalCache( auto vFlake = state.allocValue(); flake::callFlake(state, *lockedFlake, *vFlake); - state.forceAttrs(*vFlake, noPos, "While evaluating a cached flake"); + state.forceAttrs(*vFlake, noPos, "while parsing cached flake data"); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); assert(aOutputs); diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index ea356b48d..7b7bc01f0 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -118,7 +118,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) // FIXME: is it possible to extract the Pos object instead of doing this // toString + parsing? - auto pos = state.forceString(*v2, noPos, "While evaluating the meta.position attribute of a derivation"); + auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation"); auto colon = pos.rfind(':'); if (colon == std::string::npos) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index aa2bb7435..fd6dd0e40 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -336,7 +336,7 @@ Value & AttrCursor::getValue() if (!_value) { if (parent) { auto & vParent = parent->first->getValue(); - root->state.forceAttrs(vParent, noPos, "While evaluating the parent attr set"); + root->state.forceAttrs(vParent, noPos, "while searching for an attribute"); auto attr = vParent.attrs->get(parent->second); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index ad879a85d..1feb0bf6f 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -307,7 +307,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env) } else { Value nameValue; name.expr->eval(state, env, nameValue); - state.forceStringNoCtx(nameValue, noPos, "While evaluating an attribute name"); + state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name"); return state.symbols.create(nameValue.string.s); } } @@ -509,7 +509,7 @@ void EvalState::requireExperimentalFeatureOnEvaluation( if (!settings.isExperimentalFeatureEnabled(feature)) { throw EvalError({ .msg = hintfmt( - "Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.", + "cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.", feature, fName ), @@ -714,10 +714,12 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & p2, const std::string_view & s3)) { - throw TypeError(ErrorInfo { + auto e = TypeError(ErrorInfo { .msg = hintfmt(s, s2, sym), .errPos = pos, - }).addTrace(p2, s3); + }); + e.addTrace(p2, s3); + throw e; } LocalNoInlineNoReturn(void throwTypeErrorWithTrace( @@ -729,32 +731,40 @@ LocalNoInlineNoReturn(void throwTypeErrorWithTrace( const Pos & p2, const std::string_view & s3)) { - throw TypeError(ErrorInfo { + auto e = TypeError(ErrorInfo { .msg = hintfmt(s, s2, sym), .errPos = pos, .suggestions = suggestions - }).addTrace(p2, s3); + }); + e.addTrace(p2, s3); + throw e; } LocalNoInlineNoReturn(void throwTypeErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3)) { - throw TypeError(ErrorInfo { + auto e = TypeError(ErrorInfo { .msg = hintfmt(s, s2), - }).addTrace(p2, s3); + }); + e.addTrace(p2, s3); + throw e; } LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3)) { - throw EvalError(ErrorInfo { + auto e = EvalError(ErrorInfo { .msg = hintfmt(s, s2), - }).addTrace(p2, s3); + }) + e.addTrace(p2, s3); + throw e; } LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string_view & s2, const std::string_view & s3, const Pos & p2, const std::string_view s4)) { - throw EvalError(ErrorInfo { + auto e = EvalError(ErrorInfo { .msg = hintfmt(s, s2, s3), - }).addTrace(p2, s4); + }); + e.addTrace(p2, s4); + throw e; } LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2)) @@ -1047,7 +1057,7 @@ void EvalState::eval(Expr * e, Value & v) } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std::string_view & errorCtx) +inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, std::string_view errorCtx) { try { Value v; @@ -1062,7 +1072,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, const std: } -inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) +inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx) { try { e->eval(*this, env, v); @@ -1144,7 +1154,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Hence we need __overrides.) */ if (hasOverrides) { Value * vOverrides = (*v.attrs)[overrides->second.displ].value; - state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "While evaluating the `__overrides` attribute"); + state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute"); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); for (auto & i : *v.attrs) newBnds->push_back(i); @@ -1172,7 +1182,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) state.forceValue(nameVal, i.pos); if (nameVal.type() == nNull) continue; - state.forceStringNoCtx(nameVal, i.pos, "While evaluating the name of a dynamic attribute"); + state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute"); Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) @@ -1262,7 +1272,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) return; } } else { - state.forceAttrs(*vAttrs, pos, "While selecting an attribute"); + state.forceAttrs(*vAttrs, pos, "while selecting an attribute"); if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { std::set allAttrNames; for (auto & attr : *vAttrs->attrs) @@ -1365,7 +1375,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & else { try { - forceAttrs(*args[0], lambda.pos, "While evaluating the value passed for this lambda parameter"); + forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument"); } catch (Error & e) { e.addTrace(pos, "from call site"); throw; @@ -1383,7 +1393,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (!j) { if (!i.def) { throwTypeErrorWithTrace(lambda.pos, - "Function '%1%' called without required argument '%2%'", prettyLambdaName(lambda), i.name, + "function '%1%' called without required argument '%2%'", prettyLambdaName(lambda), i.name, pos, "from call site"); } env2.values[displ++] = i.def->maybeThunk(*this, env2); @@ -1405,7 +1415,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & formalNames.insert(formal.name); throwTypeErrorWithTrace(lambda.pos, Suggestions::bestMatches(formalNames, i.name), - "Function '%1%' called with unexpected argument '%2%'", prettyLambdaName(lambda), i.name, + "function '%1%' called with unexpected argument '%2%'", prettyLambdaName(lambda), i.name, pos, "from call site"); } abort(); // can't happen @@ -1420,7 +1430,7 @@ 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 the '%s' function", prettyLambdaName(lambda)); + addErrorTrace(e, lambda.pos, "while evaluating the '%s' function", prettyLambdaName(lambda)); if (pos) e.addTrace(pos, "from call site"); } throw; @@ -1448,7 +1458,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & try { vCur.primOp->fun(*this, pos, args, vCur); } catch (Error & e) { - addErrorTrace(e, pos, "While calling the '%1%' builtin", name); + addErrorTrace(e, pos, "while calling the '%1%' builtin", name); throw; } @@ -1492,7 +1502,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & try { primOp->primOp->fun(*this, noPos, vArgs, vCur); } catch (Error & e) { - addErrorTrace(e, pos, "While calling the '%1%' builtin", name); + addErrorTrace(e, pos, "while calling the '%1%' builtin", name); throw; } @@ -1510,7 +1520,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & 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)"); + e.addTrace(pos, "while calling a functor (an attribute set with a '__functor' attribute)"); throw; } nrArgs--; @@ -1612,13 +1622,13 @@ 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, "While evaluating a branch condition") ? then : else_)->eval(state, env, v); + (state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v); } void ExprAssert::eval(EvalState & state, Env & env, Value & v) { - if (!state.evalBool(env, cond, pos, "In the condition of the assert statement")) { + if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(out); throwAssertionError(pos, "assertion '%1%' failed", out.str()); @@ -1629,7 +1639,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(!state.evalBool(env, e, noPos, "In the argument of the not operator")); // XXX: FIXME: ! + v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: ! } @@ -1637,7 +1647,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, "While testing two values for equality")); + v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality")); } @@ -1645,33 +1655,33 @@ 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, "While testing two values for inequality")); + v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality")); } void ExprOpAnd::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(state.evalBool(env, e1, pos, "In the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "In the right operand of the AND (&&) operator")); + v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator")); } void ExprOpOr::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(state.evalBool(env, e1, pos, "In the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "In the right operand of the OR (||) operator")); + v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator")); } void ExprOpImpl::eval(EvalState & state, Env & env, Value & v) { - v.mkBool(!state.evalBool(env, e1, pos, "In the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "In the right operand of the IMPL (->) operator")); + v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator")); } void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) { Value v1, v2; - state.evalAttrs(env, e1, v1, pos, "In the left operand of the update (//) operator"); - state.evalAttrs(env, e2, v2, pos, "In the right operand of the update (//) operator"); + state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator"); + state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator"); state.nrOpUpdates++; @@ -1710,11 +1720,11 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) Value v1; e1->eval(state, env, v1); Value v2; e2->eval(state, env, v2); Value * lists[2] = { &v1, &v2 }; - state.concatLists(v, 2, lists, pos, "While evaluating one of the elements to concatenate"); + state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate"); } -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, const std::string_view & errorCtx) +void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, std::string_view errorCtx) { nrListConcats++; @@ -1811,7 +1821,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) /* 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, "While evaluating a path segment"); + 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)); } @@ -1869,7 +1879,7 @@ void EvalState::forceValueDeep(Value & v) } -NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string_view & errorCtx) +NixInt EvalState::forceInt(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, pos); @@ -1883,7 +1893,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos, const std::string_view & } -NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string_view & errorCtx) +NixFloat EvalState::forceFloat(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, pos); @@ -1899,7 +1909,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos, const std::string_vie } -bool EvalState::forceBool(Value & v, const Pos & pos, const std::string_view & errorCtx) +bool EvalState::forceBool(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, pos); @@ -1919,7 +1929,7 @@ bool EvalState::isFunctor(Value & fun) } -void EvalState::forceFunction(Value & v, const Pos & pos, const std::string_view & errorCtx) +void EvalState::forceFunction(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, pos); @@ -1932,7 +1942,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos, const std::string_view } -std::string_view EvalState::forceString(Value & v, const Pos & pos, const std::string_view & errorCtx) +std::string_view EvalState::forceString(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, pos); @@ -1977,7 +1987,7 @@ std::vector> Value::getContext() } -std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, const std::string_view & errorCtx) +std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, std::string_view errorCtx) { auto s = forceString(v, pos, errorCtx); copyContext(v, context); @@ -1985,7 +1995,7 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos } -std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, const std::string_view & errorCtx) +std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, std::string_view errorCtx) { try { auto s = forceString(v, pos, errorCtx); @@ -2022,14 +2032,14 @@ std::optional EvalState::tryAttrsToString(const Pos & pos, Value & Value v1; callFunction(*i->value, v, v1, pos); return coerceToString(pos, v1, context, coerceMore, copyToStore, - "While evaluating the result of the `toString` attribute").toOwned(); + "while evaluating the result of the `toString` attribute").toOwned(); } return {}; } BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, - bool coerceMore, bool copyToStore, bool canonicalizePath, const std::string_view & errorCtx) + bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx) { forceValue(v, pos); @@ -2075,7 +2085,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & for (auto [n, v2] : enumerate(v.listItems())) { try { result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath, - "While evaluating one element of the list"); + "while evaluating one element of the list"); } catch (Error & e) { e.addTrace(pos, errorCtx); throw; @@ -2117,7 +2127,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path) } -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx) +Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx) { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') @@ -2126,7 +2136,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, cons } -StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, const std::string_view & errorCtx) +StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx) { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) @@ -2135,7 +2145,7 @@ StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & con } -bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, const std::string_view & errorCtx) +bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, std::string_view errorCtx) { forceValue(v1, noPos); forceValue(v2, noPos); @@ -2331,7 +2341,7 @@ void EvalState::printStats() } -std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, const std::string_view & errorCtx) const +std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const { throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(), pos, errorCtx); } diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index fe2daaf21..8427cf213 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -708,7 +708,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va { state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.getFlake")); + std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake")); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 5ab920dcc..932ab7f1d 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -50,7 +50,7 @@ std::string DrvInfo::queryName() const if (name == "" && attrs) { auto i = attrs->find(state->sName); if (i == attrs->end()) throw TypeError("derivation name missing"); - name = state->forceStringNoCtx(*i->value, noPos, "While evaluating the name of a DrvInfo"); + name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation"); } return name; } @@ -60,7 +60,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "While evaluating the system of a DrvInfo"); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos, "while evaluating the 'system' attribute of a derivation"); } return system; } @@ -74,7 +74,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "Whyle evaluating the drv path of a DrvInfo")}; + drvPath = {state->coerceToStorePath(*i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")}; } return drvPath.value_or(std::nullopt); } @@ -94,7 +94,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the output path of a DrvInfo"); + outPath = state->coerceToStorePath(*i->pos, *i->value, context, "while evaluating the output path of a derivation"); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -108,23 +108,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos, "While evaluating the outputs of a DrvInfo"); + state->forceList(*i->value, *i->pos, "while evaluating the 'outputs' attribute of a derivation"); /* For each output... */ for (auto elem : i->value->listItems()) { - std::string output(state->forceStringNoCtx(*elem, *i->pos, "While evaluating the name of one output of a DrvInfo")); + std::string output(state->forceStringNoCtx(*elem, *i->pos, "while evaluating the name of an output of a derivation")); if (withPaths) { /* Evaluate the corresponding set. */ Bindings::iterator out = attrs->find(state->symbols.create(output)); if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, *i->pos, "While evaluating the description of a DrvInfo output"); + state->forceAttrs(*out->value, *i->pos, "while evaluating an output of a derivation"); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "While evaluating the outPath of an output path of a DrvInfo")); + outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation")); } else outputs.emplace(output, std::nullopt); } @@ -155,7 +155,7 @@ std::string DrvInfo::queryOutputName() const { if (outputName == "" && attrs) { Bindings::iterator i = attrs->find(state->sOutputName); - outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "While evaluating the output name of a DrvInfo") : ""; + outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : ""; } return outputName; } @@ -167,7 +167,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos, "While evaluating the `meta` attribute of a DrvInfo"); + state->forceAttrs(*a->value, *a->pos, "while evaluating the 'meta' attribute of a derivation"); meta = a->value->attrs; return meta; } @@ -368,7 +368,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "While evaluating the attribute `recurseForDerivations`")) + if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos, "while evaluating the attribute `recurseForDerivations`")) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 88b87a433..f99ab8ca8 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -100,15 +100,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea { PathSet context; - auto path = [&]() - { - try { - 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; - } - }(); + auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path"); try { StringMap rewrites = state.realiseContext(context); @@ -195,9 +187,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 +202,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 +306,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 +332,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) { @@ -351,11 +343,11 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) } PathSet context; auto program = state.coerceToString(pos, *elems[0], context, false, false, - "While evaluating the first element of the argument passed to builtins.exec").toOwned(); + "while evaluating the first element of the argument passed to builtins.exec").toOwned(); Strings commandArgs; for (unsigned int i = 1; i < args[0]->listSize(); ++i) { commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false, - "While evaluating an element of the argument passed to builtins.exec").toOwned()); + "while evaluating an element of the argument passed to builtins.exec").toOwned()); } try { auto _ = state.realiseContext(context); // FIXME: Handle CA derivations @@ -372,13 +364,13 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) try { parsed = state.parseExprFromString(std::move(output), pos.file); } catch (Error & e) { - e.addTrace(pos, "While parsing the output from '%1%'", program); + e.addTrace(pos, "while parsing the output from '%1%'", program); throw; } try { state.eval(parsed, v); } catch (Error & e) { - e.addTrace(pos, "While evaluating the output from '%1%'", program); + e.addTrace(pos, "while evaluating the output from '%1%'", program); throw; } } @@ -589,7 +581,7 @@ struct CompareValues } 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"); + return (*this)(v1->listElems()[i], v2->listElems()[i], "while comparing two lists"); } } default: @@ -630,12 +622,12 @@ static Bindings::iterator getAttr( static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], noPos, "While evaluating the first argument pased 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.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); - state.forceList(*startSet->value, noPos, "While evaluating the 'startSet' attribute passed as argument 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()) @@ -648,7 +640,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar /* Get the operator. */ 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, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"); /* Construct the closure by applying the operator to elements of `workSet', adding the result to `workSet', continuing until @@ -656,13 +648,13 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar ValueList res; // `doneKeys' doesn't need to be a GC root, because its values are // reachable from res. - auto cmp = CompareValues(state, noPos, "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, noPos, "While evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"); + state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"); 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); @@ -673,11 +665,11 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar /* Call the `operator' function with `e' as argument. */ 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"); + 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 : newElements.listItems()) { - state.forceValue(*elem, noPos); // "While evaluating one one of the elements returned by the `operator` passed to builtins.genericClosure"); + state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by the `operator` passed to builtins.genericClosure"); workSet.push_back(elem); } } @@ -705,7 +697,7 @@ static RegisterPrimOp primop_abort({ { PathSet context; auto s = state.coerceToString(pos, *args[0], context, - "While evaluating the error message passed to builtins.abort").toOwned(); + "while evaluating the error message passed to builtins.abort").toOwned(); throw Abort("evaluation aborted with the following error message: '%1%'", s); } }); @@ -724,7 +716,7 @@ static RegisterPrimOp primop_throw({ { PathSet context; auto s = state.coerceToString(pos, *args[0], context, - "While evaluating the error message passed to builtin.throw").toOwned(); + "while evaluating the error message passed to builtin.throw").toOwned(); throw ThrownError(s); } }); @@ -737,7 +729,7 @@ static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * a } catch (Error & e) { PathSet context; e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context, - "While evaluating the error message passed to builtins.addErrorContext").toOwned()); + "while evaluating the error message passed to builtins.addErrorContext").toOwned()); throw; } } @@ -751,7 +743,7 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { static void prim_ceil(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.ceil"); + "while evaluating the first argument passed to builtins.ceil"); v.mkInt(ceil(value)); } @@ -770,7 +762,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)); } @@ -827,7 +819,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("")); } @@ -925,7 +917,7 @@ 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.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict"); @@ -933,7 +925,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * 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; @@ -943,14 +935,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; @@ -1017,7 +1009,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (i->name == state.sContentAddressed) { contentAddressed = state.forceBool(*i->value, pos, - "While evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict"); + "while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict"); if (contentAddressed) settings.requireExperimentalFeature(Xp::CaDerivations); } @@ -1026,10 +1018,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * command-line arguments to the builder. */ else if (i->name == state.sArgs) { state.forceList(*i->value, pos, - "While evaluating the `args` attribute passed to builtins.derivationStrict"); + "while evaluating the `args` attribute passed to builtins.derivationStrict"); for (auto elem : i->value->listItems()) { auto s = state.coerceToString(posDrvName, *elem, context, true, - "While evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned(); + "while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned(); drv.args.push_back(s); } } @@ -1046,26 +1038,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); @@ -1278,7 +1270,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({ @@ -1302,7 +1294,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); } @@ -1333,7 +1325,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. */ @@ -1403,7 +1395,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({ @@ -1423,7 +1415,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); } @@ -1470,23 +1462,23 @@ 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.sPath, v2->attrs, "in an element of the __nixPath"); PathSet context; auto path = state.coerceToString(pos, *i->value, context, false, false, - "While evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned(); + "while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned(); try { auto rewrites = state.realiseContext(context); @@ -1502,7 +1494,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))); } @@ -1516,7 +1508,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({ @@ -1723,7 +1715,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) { @@ -1752,8 +1744,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; @@ -1910,7 +1902,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; @@ -1936,9 +1928,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); } @@ -1999,7 +1991,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; @@ -2010,15 +2002,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"); 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), @@ -2027,7 +2019,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value } if (path.empty()) throw EvalError({ - .msg = hintfmt("Missing required 'path' attribute in the first argument to builtins.path"), + .msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"), .errPos = pos }); if (name.empty()) @@ -2081,7 +2073,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()); @@ -2108,7 +2100,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()); @@ -2139,8 +2131,8 @@ 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.symbols.create(attr), args[1]->attrs, @@ -2167,8 +2159,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(); @@ -2185,8 +2177,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()); } @@ -2219,8 +2211,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 @@ -2228,7 +2220,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()); @@ -2267,18 +2259,18 @@ 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.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) { @@ -2317,8 +2309,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())); @@ -2343,14 +2335,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; @@ -2423,7 +2415,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()); @@ -2464,15 +2456,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) { @@ -2562,7 +2554,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), @@ -2575,7 +2567,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({ @@ -2610,7 +2602,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"), @@ -2641,14 +2633,14 @@ 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.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map"); state.mkList(v, args[1]->listSize()); for (unsigned int n = 0; n < v.listSize(); ++n) @@ -2677,14 +2669,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()]; @@ -2694,7 +2686,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; @@ -2722,9 +2714,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; } @@ -2744,8 +2736,8 @@ 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.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos, "While evaluating a value of the list 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"); } static RegisterPrimOp primop_concatLists({ @@ -2760,7 +2752,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()); } @@ -2777,8 +2769,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]; @@ -2810,13 +2802,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; @@ -2859,7 +2851,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({ @@ -2897,7 +2889,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) { @@ -2905,7 +2897,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) { @@ -2917,12 +2909,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, noPos, "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, noPos); - return state.forceBool(vBool, pos, "While evaluating the return value of the sorting function passed to builtins.sort"); + 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 @@ -2954,8 +2946,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(); @@ -2966,7 +2958,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); @@ -3014,15 +3006,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); @@ -3066,8 +3058,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]; @@ -3077,7 +3069,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; @@ -3116,11 +3108,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({ @@ -3137,11 +3129,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({ @@ -3158,11 +3150,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({ @@ -3179,7 +3171,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"), @@ -3187,10 +3179,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({ @@ -3213,8 +3205,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({ @@ -3228,8 +3220,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({ @@ -3243,8 +3235,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({ @@ -3288,7 +3280,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); } @@ -3322,10 +3314,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({ @@ -3359,7 +3351,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()); } @@ -3376,7 +3368,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({ @@ -3385,7 +3377,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)); } @@ -3424,14 +3416,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)) { @@ -3505,14 +3497,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(); @@ -3611,8 +3603,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()); @@ -3620,7 +3612,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); @@ -3639,8 +3631,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"), @@ -3650,18 +3642,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. @@ -3719,7 +3711,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); @@ -3743,8 +3735,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)); } @@ -3763,7 +3755,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/primops/context.cc b/src/libexpr/primops/context.cc index 78320dc09..517b93897 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_unsafeDiscardStringContext(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.unsafeDiscardStringContext"); + auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext"); v.mkString(*s); } @@ -17,7 +17,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.hasContext"); + state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext"); v.mkBool(!context.empty()); } @@ -33,7 +33,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); static void prim_unsafeDiscardOutputDependency(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.unsafeDiscardOutputDependency"); + auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency"); PathSet context2; for (auto & p : context) @@ -72,7 +72,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Strings outputs; }; PathSet context; - state.forceString(*args[0], context, pos, "While evaluating the argument passed to builtins.getContext"); + state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext"); auto contextInfos = std::map(); for (const auto & p : context) { Path drv; @@ -138,31 +138,31 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg PathSet context; auto orig = state.forceString(*args[0], context, pos, "while evaluating the first argument passed to builtins.appendContext"); - state.forceAttrs(*args[1], pos, "While evaluating the second argument passed to builtins.appendContext"); + state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext"); auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); for (auto & i : *args[1]->attrs) { if (!state.store->isStorePath(i.name)) throw EvalError({ - .msg = hintfmt("Context key '%s' is not a store path", i.name), + .msg = hintfmt("context key '%s' is not a store path", i.name), .errPos = *i.pos }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos, "While evaluating the value of a string context"); + state.forceAttrs(*i.value, *i.pos, "while evaluating the value of a string context"); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `path` attribute of a string context")) + if (state.forceBool(*iter->value, *iter->pos, "while evaluating the `path` attribute of a string context")) context.insert(i.name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos, "While evaluating the `allOutputs` attribute of a string context")) { + if (state.forceBool(*iter->value, *iter->pos, "while evaluating the `allOutputs` attribute of a string context")) { if (!isDerivation(i.name)) { throw EvalError({ - .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), .errPos = *i.pos }); } @@ -172,15 +172,15 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos, "While evaluating the `outputs` attribute of a string context"); + state.forceList(*iter->value, *iter->pos, "while evaluating the `outputs` attribute of a string context"); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ - .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", i.name), .errPos = *i.pos }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos, "While evaluating an output name within a string context"); + auto name = state.forceStringNoCtx(*elem, *iter->pos, "while evaluating an output name within a string context"); context.insert(concatStrings("!", name, "!", i.name)); } } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index dcaf3d362..00c7e6b1d 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -22,18 +22,18 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "While evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); + url = state.coerceToString(*attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `rev` attribute passed to builtins.fetchMercurial"); + auto value = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial"); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the `name` attribute passed to builtins.fetchMercurial"); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), @@ -48,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar }); } else - url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to builtins.fetchMercurial").toOwned(); + url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned(); // FIXME: git externals probably can be used to bypass the URI // whitelist. Ah well. diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 2baa272cb..25fb8d939 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -102,7 +102,7 @@ static void fetchTree( state.forceValue(*args[0], pos); if (args[0]->type() == nAttrs) { - state.forceAttrs(*args[0], pos, "While evaluating the argument passed to builtins.fetchTree"); + state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree"); fetchers::Attrs attrs; @@ -112,7 +112,7 @@ static void fetchTree( .msg = hintfmt("unexpected attribute 'type'"), .errPos = pos }); - type = state.forceStringNoCtx(*aType->value, *aType->pos, "While evaluating the `type` attribute passed to builtins.fetchTree"); + type = state.forceStringNoCtx(*aType->value, *aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree"); } else if (!type) throw Error({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), @@ -151,7 +151,7 @@ static void fetchTree( input = fetchers::Input::fromAttrs(std::move(attrs)); } else { - auto url = state.coerceToString(pos, *args[0], context, false, false, "While evaluating the first argument passed to the fetcher").toOwned(); + auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned(); if (type == "git") { fetchers::Attrs attrs; @@ -198,11 +198,11 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, for (auto & attr : *args[0]->attrs) { std::string n(attr.name); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the url we should fetch"); + url = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the url we should fetch"); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the sha256 of the content we should fetch"), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos, "While evaluating the name of the content we should fetch"); + name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the name of the content we should fetch"); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), @@ -216,7 +216,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, .errPos = pos }); } else - url = state.forceStringNoCtx(*args[0], pos, "While evaluating the url we should fetch"); + url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); url = resolveUri(*url); diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 6bfd8e285..336cfe5cc 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -7,7 +7,7 @@ namespace nix { static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) { - auto toml = state.forceStringNoCtx(*args[0], pos, "While evaluating the argument passed to builtins.fromTOML"); + auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML"); std::istringstream tomlStream(std::string{toml}); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 3cf764a20..e835b8052 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, std::function callback) { auto pos = vFlake.determinePos(noPos); - state.forceAttrs(vFlake, pos, "While evaluating a flake to get its outputs"); + state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs"); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); assert(aOutputs); - state.forceAttrs(*aOutputs->value, pos, "While evaluating the outputs of a flake"); + state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake"); auto sHydraJobs = state.symbols.create("hydraJobs"); diff --git a/src/nix/main.cc b/src/nix/main.cc index 0c6286686..0dc14e2e9 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -196,7 +196,7 @@ static void showHelp(std::vector subcommand, MultiCommand & topleve if (!attr) throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); - auto markdown = state.forceString(*attr->value, noPos, "While evaluating the lowdown help text"); + auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text"); RunPager pager; std::cout << renderMarkdownToTerminal(markdown) << "\n"; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index 4376da896..782149575 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url) Value vMirrors; // FIXME: use nixpkgs flake state.eval(state.parseExprFromString("import ", "."), vMirrors); - state.forceAttrs(vMirrors, noPos, "While evaluating the set of all mirrors"); + state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors"); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) throw Error("unknown mirror name '%s'", mirrorName); - state.forceList(*mirrorList->value, noPos, "While evaluating this mirror configuration"); + state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration"); if (mirrorList->value->listSize() < 1) throw Error("mirror URL '%s' did not expand to anything", url); - std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "While evaluating the first available mirror")); + std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror")); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); } @@ -196,27 +196,27 @@ static int main_nix_prefetch_url(int argc, char * * argv) Value vRoot; state->evalFile(path, vRoot); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); - state->forceAttrs(v, noPos, "While evaluating the source attribute to prefetch"); + state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch"); /* Extract the URL. */ auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos, "While evaluating the urls to prefetch"); + state->forceList(*attr.value, noPos, "while evaluating the urls to prefetch"); if (attr.value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0], noPos, "While evaluating the first url from the urls list"); + url = state->forceString(*attr.value->listElems()[0], noPos, "while evaluating the first url from the urls list"); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); if (!attr2) printInfo("warning: this does not look like a fetchurl call"); else - unpack = state->forceString(*attr2->value, noPos, "While evaluating the outputHashMode of the source to prefetch") == "recursive"; + unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive"; /* Extract the name. */ if (!name) { auto attr3 = v.attrs->get(state->symbols.create("name")); if (!attr3) - name = state->forceString(*attr3->value, noPos, "While evaluating the name of the source to prefetch"); + name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch"); } } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index b4a029ecc..7f7912de3 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -462,7 +462,7 @@ bool NixRepl::processLine(std::string line) if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context, "While evaluating the filename to edit"); + auto filename = state->coerceToString(noPos, v, context, "while evaluating the filename to edit"); pos.file = state->symbols.create(*filename); } else if (v.isLambda()) { pos = v.lambda.fun->pos; @@ -683,7 +683,7 @@ void NixRepl::reloadFiles() void NixRepl::addAttrsToScope(Value & attrs) { - state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "While evaluating an attribute set to be merged in the global scope"); + state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope"); if (displ + attrs.attrs->size() >= envSize) throw Error("environment full; cannot add more variables"); @@ -788,7 +788,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m Bindings::iterator i = v.attrs->find(state->sDrvPath); PathSet context; if (i != v.attrs->end()) - str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "While evaluating the drvPath of a derivation")); + str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context, "while evaluating the drvPath of a derivation")); else str << "???"; str << "»"; diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 1f433a199..0c317de16 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Bindings & bindings(*state->allocBindings(0)); auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; - return store->parseStorePath(state->forceString(*v2, noPos, "While evaluating the path tho latest nix version")); + return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version")); } }; From 5ef88457b8bdef957fcbb3cd0e7740595fad1949 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 28 Apr 2022 13:00:24 +0200 Subject: [PATCH 19/97] Better document error location indent --- src/libutil/error.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index c66dfa112..7dc8c1941 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -180,20 +180,24 @@ void printCodeLines(std::ostream & out, } } +// Enough indent to align with with the `... ` +// prepended to each element of the trace +#define ELLIPSIS_INDENT " " + 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(ELLIPSIS_INDENT "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(ELLIPSIS_INDENT "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(ELLIPSIS_INDENT "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos)); break; } default: From 402ee8ab64f9b11989cbdcf53f8ca513cb25e23f Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 28 Apr 2022 13:02:39 +0200 Subject: [PATCH 20/97] No point in passing string_views by reference --- src/libexpr/eval-inline.hh | 6 +++--- src/libexpr/eval.hh | 34 +++++++++++++++++----------------- src/libexpr/primops.cc | 2 +- src/libexpr/value.hh | 2 +- src/libutil/error.cc | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 77ed07b2e..025459c00 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -128,7 +128,7 @@ void EvalState::forceValue(Value & v, Callable getPos) [[gnu::always_inline]] -inline void EvalState::forceAttrs(Value & v, const Pos & pos, const std::string_view & errorCtx) +inline void EvalState::forceAttrs(Value & v, const Pos & pos, std::string_view errorCtx) { forceAttrs(v, [&]() { return pos; }, errorCtx); } @@ -136,7 +136,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos, const std::string_ template [[gnu::always_inline]] -inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string_view & errorCtx) +inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx) { try { forceValue(v, noPos); @@ -152,7 +152,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, const std::string_ [[gnu::always_inline]] -inline void EvalState::forceList(Value & v, const Pos & pos, const std::string_view & errorCtx) +inline void EvalState::forceList(Value & v, const Pos & pos, std::string_view errorCtx) { try { forceValue(v, noPos); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 18dbfc5a4..fd0961bdc 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -222,8 +222,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_view & errorCtx); - inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx); + inline bool evalBool(Env & env, Expr * e, const Pos & pos, std::string_view errorCtx); + inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, 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 @@ -239,20 +239,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_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); + NixInt forceInt(Value & v, const Pos & pos, std::string_view errorCtx); + NixFloat forceFloat(Value & v, const Pos & pos, std::string_view errorCtx); + bool forceBool(Value & v, const Pos & pos, std::string_view errorCtx); - void forceAttrs(Value & v, const Pos & pos, const std::string_view & errorCtx); + void forceAttrs(Value & v, const Pos & pos, std::string_view errorCtx); template - inline void forceAttrs(Value & v, Callable getPos, const std::string_view & errorCtx); + inline void forceAttrs(Value & v, Callable getPos, std::string_view 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); + inline void forceList(Value & v, const Pos & pos, std::string_view errorCtx); + void forceFunction(Value & v, const Pos & pos, std::string_view errorCtx); // either lambda or primop + std::string_view forceString(Value & v, const Pos & pos, std::string_view errorCtx); + std::string_view forceString(Value & v, PathSet & context, const Pos & pos, std::string_view errorCtx); + std::string_view forceStringNoCtx(Value & v, const Pos & pos, std::string_view errorCtx); /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ @@ -268,17 +268,17 @@ public: BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true, bool canonicalizePath = true, - const std::string_view & errorCtx = ""); + 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_view & errorCtx); + Path coerceToPath(const Pos & pos, Value & v, PathSet & context, 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_view & errorCtx); + StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx); public: @@ -334,7 +334,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_view & errorCtx); + bool eqValues(Value & v1, Value & v2, const Pos & pos, std::string_view errorCtx); bool isFunctor(Value & fun); @@ -369,7 +369,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_view & errorCtx); + void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, std::string_view errorCtx); /* Print statistics. */ void printStats(); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index f99ab8ca8..9360c2102 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -552,7 +552,7 @@ struct CompareValues return (*this)(v1, v2, errorCtx); } - bool operator () (Value * v1, Value * v2, const std::string_view & errorCtx) const + bool operator () (Value * v1, Value * v2, std::string_view errorCtx) const { try { if (v1->type() == nFloat && v2->type() == nInt) diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 403e38258..18e4218c5 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_view & errorCtx) const; + virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, 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 7dc8c1941..3644cada9 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -347,7 +347,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * to make a decision between the two following options. * * ``` long traces - * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) + * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx) * { * try { * e->eval(*this, env, v); @@ -361,7 +361,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * ``` * * ``` short traces - * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx) + * inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx) * { * e->eval(*this, env, v); * try { From f6baa4d18845297f3f7fc2434b7ade93a45718e7 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 28 Apr 2022 13:18:19 +0200 Subject: [PATCH 21/97] fixup! fix errors case and wording --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 1feb0bf6f..6d9129bd3 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -753,7 +753,7 @@ LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::st { auto e = EvalError(ErrorInfo { .msg = hintfmt(s, s2), - }) + }); e.addTrace(p2, s3); throw e; } From 9ff892aad4ee13532ea84cb6e4a2a53d70945efe Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 29 Apr 2022 11:24:48 +0200 Subject: [PATCH 22/97] Add release notes for error traces revamp --- doc/manual/src/release-notes/rl-next.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 7b3ad4e58..f312f2f86 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -14,3 +14,7 @@ * `nix build` has a new `--print-out-paths` flag to print the resulting output paths. This matches the default behaviour of `nix-build`. + +* Error traces have been reworked to provide detailed explanations and more + accurate error locations. A short excerpt of the trace is now shown by + default when an error occurs. From c7b901fd33e817bfd8306a0e1b4f021ee3a209c9 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sun, 11 Sep 2022 01:34:19 +0200 Subject: [PATCH 23/97] Cleanup error strings rebase --- .gitignore | 1 + src/libexpr/eval-cache.cc | 18 +- src/libexpr/eval-inline.hh | 24 +- src/libexpr/eval.cc | 442 ++++++++----------------------------- src/libexpr/eval.hh | 155 +++---------- src/libexpr/get-drvs.cc | 2 +- src/libexpr/parser.y | 6 +- src/libexpr/primops.cc | 15 +- 8 files changed, 154 insertions(+), 509 deletions(-) diff --git a/.gitignore b/.gitignore index 0c1b89ace..174ea95e0 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ nix-rust/target result .vscode/ +baseline diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 1a37f87f3..89afac0e8 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -571,14 +571,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); return v.type() == nString ? v.string.s : v.path; } @@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); } } @@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path, {}}; else - root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); } bool AttrCursor::getBool() @@ -626,14 +626,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); } } auto & v = forceValue(); if (v.type() != nBool) - root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); return v.boolean; } @@ -685,7 +685,7 @@ std::vector AttrCursor::getListOfStrings() std::vector res; for (auto & elem : v.listItems()) - res.push_back(std::string(root->state.forceStringNoCtx(*elem))); + res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching"))); if (root->db) cachedValue = {root->db->setListOfStrings(getKey(), res), res}; @@ -703,14 +703,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); } } auto & v = forceValue(); if (v.type() != nAttrs) - root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); + throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 5997525e4..43a4e8fa6 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -103,7 +103,7 @@ void EvalState::forceValue(Value & v, Callable getPos) else if (v.isApp()) callFunction(*v.app.left, *v.app.right, v, noPos); else if (v.isBlackhole()) - throwEvalError(getPos(), "infinite recursion encountered"); + throwError(getPos(), "infinite recursion encountered", "", "", 0, 0, 0, 0, noPos, "", 0, 0, 0); } @@ -118,15 +118,10 @@ template [[gnu::always_inline]] inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx) { - try { - forceValue(v, noPos); - if (v.type() != nAttrs) { - throwTypeError(noPos, "value is %1% while a set was expected", v); - } - } catch (Error & e) { + forceValue(v, noPos); + if (v.type() != nAttrs) { PosIdx pos = getPos(); - e.addTrace(positions[pos], errorCtx); - throw; + this->throwErrorWithTrace(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); } } @@ -134,14 +129,9 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e [[gnu::always_inline]] inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx) { - try { - forceValue(v, noPos); - if (!v.isList()) { - throwTypeError(noPos, "value is %1% while a list was expected", v); - } - } catch (Error & e) { - e.addTrace(positions[pos], errorCtx); - throw; + forceValue(v, noPos); + if (!v.isList()) { + this->throwErrorWithTrace(noPos, "value is %1% while a list was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); } } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 46e5dc41e..646e19bad 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -836,327 +836,64 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & evaluator. So here are some helper functions for throwing exceptions. */ -// *WithTrace -void EvalState::throwTypeErrorWithTrace( - const PosIdx pos, - const char * s, - const std::string_view s2, - const Symbol & sym, - const PosIdx p2, - const std::string_view s3) const +template +void EvalState::throwErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr) { - auto e = TypeError(ErrorInfo { - .msg = hintfmt(s, s2, symbols[sym]), + hintformat f(format); + if (!s1.empty()) { f = f % s1; } + if (!s2.empty()) { f = f % s2; } + if (sym1) { f = f % symbols[*sym1]; } + if (sym2) { f = f % symbols[*sym2]; } + if (val1) { f = f % showType(*val1); } + if (val2) { f = f % showType(*val2); } + if (pos1) { f = f % positions[pos1]; } + if (!s3.empty()) { f = f % s3; } + + auto e = ErrorType(ErrorInfo { + .msg = f, .errPos = positions[pos], + .suggestions = suggestions ? *suggestions : Suggestions(), }); - e.addTrace(positions[p2], s3); - throw e; + e.addTrace(positions[tracePos], traceStr); + debugThrow(e, env, expr); } -void EvalState::throwTypeErrorWithTrace( - const PosIdx pos, - const Suggestions & suggestions, - const char * s, - const std::string_view s2, - const Symbol & sym, - const PosIdx p2, - const std::string_view s3) const +template +void EvalState::throwError( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + Env * env, Expr * expr) { - auto e = TypeError(ErrorInfo { - .msg = hintfmt(s, s2, symbols[sym]), + hintformat f(format); + if (!s1.empty()) { f = f % s1; } + if (!s2.empty()) { f = f % s2; } + if (sym1) { f = f % symbols[*sym1]; } + if (sym2) { f = f % symbols[*sym2]; } + if (val1) { f = f % showType(*val1); } + if (val2) { f = f % showType(*val2); } + if (pos1) { f = f % positions[pos1]; } + if (!s3.empty()) { f = f % s3; } + + auto e = ErrorType(ErrorInfo { + .msg = f, .errPos = positions[pos], - .suggestions = suggestions + .suggestions = suggestions ? *suggestions : Suggestions(), }); - e.addTrace(positions[p2], s3); - throw e; -} - -void EvalState::throwTypeErrorWithTrace(const char * s, const std::string_view s2, const PosIdx p2, const std::string_view s3) const -{ - auto e = TypeError(ErrorInfo { - .msg = hintfmt(s, s2), - }); - e.addTrace(positions[p2], s3); - throw e; -} - -void EvalState::throwEvalErrorWithTrace(const char * s, const std::string_view s2, const PosIdx p2, const std::string_view s3) const -{ - auto e = EvalError(ErrorInfo { - .msg = hintfmt(s, s2), - }); - e.addTrace(positions[p2], s3); - throw e; -} - -void EvalState::throwEvalErrorWithTrace(const char * s, const std::string_view s2, const std::string_view s3, const PosIdx p2, const std::string_view s4) const -{ - auto e = EvalError(ErrorInfo { - .msg = hintfmt(s, s2, s3), - }); - e.addTrace(positions[p2], s4); - throw e; -} - - -// *WithoutTrace coerce-strings - -void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr) -{ - debugThrow(EvalError({ - .msg = hintfmt(s), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const -{ - throw EvalError(ErrorInfo { - .msg = hintfmt(s, s2), - .errPos = positions[pos], - .suggestions = suggestions, - }); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s) const -{ - throw EvalError(ErrorInfo { - .msg = hintfmt(s), - .errPos = positions[pos] - }); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const Value & v) const -{ - throw EvalError(ErrorInfo { - .msg = hintfmt(s, showType(v)), - .errPos = positions[pos] - }); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const -{ - throw EvalError(ErrorInfo { - .msg = hintfmt(s, s2), - .errPos = positions[pos] - }); -} - -void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const -{ - // p1 is where the error occurred; p2 is a position mentioned in the message. - throw EvalError(ErrorInfo { - .msg = hintfmt(s, symbols[sym], positions[p2]), - .errPos = positions[p1] - }); -} - -void EvalState::throwEvalError(const char * s, const std::string_view s1) const -{ - throw EvalError(s, s1); -} - -void EvalState::throwEvalError(const char * s, const std::string_view s1, const std::string_view s2) const -{ - throw EvalError(s, s1, s2); -} - -void EvalState::throwTypeError(const char * s, const Value & v) const -{ - throw TypeError(s, showType(v)); -} - -void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const -{ - throw TypeError(ErrorInfo { - .msg = hintfmt(s, showType(v)), - .errPos = positions[pos] - }); -} - -void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const -{ - throw AssertionError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }); -} - -void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const -{ - throw UndefinedVarError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }); -} - -void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const -{ - throw MissingArgumentError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }); -} - - -// master - -void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr) -{ - debugThrow(EvalError({ - .msg = hintfmt(s), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s) -{ - debugThrowLastTrace(EvalError({ - .msg = hintfmt(s), - .errPos = positions[pos] - })); -} - -void EvalState::throwEvalError(const char * s, const std::string & s2) -{ - debugThrowLastTrace(EvalError(s, s2)); -} - -void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const std::string & s2, Env & env, Expr & expr) -{ - debugThrow(EvalError(ErrorInfo{ - .msg = hintfmt(s, s2), - .errPos = positions[pos], - .suggestions = suggestions, - }), env, expr); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) -{ - debugThrowLastTrace(EvalError({ - .msg = hintfmt(s, s2), - .errPos = positions[pos] - })); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr) -{ - debugThrow(EvalError({ - .msg = hintfmt(s, s2), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwEvalError(const char * s, const std::string & s2, - const std::string & s3) -{ - debugThrowLastTrace(EvalError({ - .msg = hintfmt(s, s2), - .errPos = positions[noPos] - })); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, - const std::string & s3) -{ - debugThrowLastTrace(EvalError({ - .msg = hintfmt(s, s2), - .errPos = positions[pos] - })); -} - -void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, - const std::string & s3, Env & env, Expr & expr) -{ - debugThrow(EvalError({ - .msg = hintfmt(s, s2), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr) -{ - // p1 is where the error occurred; p2 is a position mentioned in the message. - debugThrow(EvalError({ - .msg = hintfmt(s, symbols[sym], positions[p2]), - .errPos = positions[p1] - }), env, expr); -} - -void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) -{ - debugThrowLastTrace(TypeError({ - .msg = hintfmt(s, showType(v)), - .errPos = positions[pos] - })); -} - -void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr) -{ - debugThrow(TypeError({ - .msg = hintfmt(s, showType(v)), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwTypeError(const PosIdx pos, const char * s) -{ - debugThrowLastTrace(TypeError({ - .msg = hintfmt(s), - .errPos = positions[pos] - })); -} - -void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, - const Symbol s2, Env & env, Expr &expr) -{ - debugThrow(TypeError({ - .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr) -{ - debugThrow(TypeError(ErrorInfo { - .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), - .errPos = positions[pos], - .suggestions = suggestions, - }), env, expr); -} - -void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr) -{ - debugThrow(TypeError({ - .msg = hintfmt(s, showType(v)), - .errPos = positions[expr.getPos()], - }), env, expr); -} - -void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) -{ - debugThrow(AssertionError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) -{ - debugThrow(UndefinedVarError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }), env, expr); -} - -void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr) -{ - debugThrow(MissingArgumentError({ - .msg = hintfmt(s, s1), - .errPos = positions[pos] - }), env, expr); + debugThrow(e, env, expr); } void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const @@ -1253,7 +990,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast(var)); + throwError(var.pos, "undefined variable '%1%'", "", "", &var.name, 0, 0, 0, noPos, "", 0, env, const_cast(&var)); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1403,7 +1140,7 @@ void EvalState::cacheFile( // computation. if (mustBeTrivial && !(dynamic_cast(e))) - throwEvalError("file '%s' must be an attribute set", path); + throwError(noPos, "file '%s' must be an attribute set", path, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); @@ -1427,7 +1164,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwTypeError("value is %1% while a Boolean was expected", v); + throwError(pos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1441,7 +1178,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - throwTypeError("value is %1% while a set was expected", v, env, *e); + throwError(pos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1550,7 +1287,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this); + state.throwError(i.pos, "dynamic attribute '%1%' already defined at %2%", "", "", &nameSym, 0, 0, 0, j->pos, "", 0, &env, this); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1652,10 +1389,11 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) std::set allAttrNames; for (auto & attr : *vAttrs->attrs) allAttrNames.insert(state.symbols[attr.name]); - state.throwEvalError( - pos, - Suggestions::bestMatches(allAttrNames, state.symbols[name]), - "attribute '%1%' missing", state.symbols[name], env, *this); + throw EvalError("foo"); + //tmp: state.throwEvalError( + // pos, + // Suggestions::bestMatches(allAttrNames, state.symbols[name]), + // "attribute '%1%' missing", state.symbols[name], env, *this); } } vAttrs = j->value; @@ -1750,7 +1488,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & try { forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument"); } catch (Error & e) { - e.addTrace(positions[pos], "from call site"); + if (pos) e.addTrace(positions[pos], "from call site"); throw; } @@ -1765,10 +1503,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { + throw EvalError("foo"); + /* throwTypeErrorWithTrace(lambda.pos, "function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), i.name, pos, "from call site", *fun.lambda.env, lambda); + */ } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1787,11 +1528,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & std::set formalNames; for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); + throw EvalError("foo"); + /* throwTypeErrorWithTrace(lambda.pos, Suggestions::bestMatches(formalNames, symbols[i.name]), "function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), i.name, pos, "from call site"); + */ } abort(); // can't happen } @@ -1816,7 +1560,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (loggerSettings.showTrace.get()) { addErrorTrace(e, lambda.pos, "while evaluating the '%s' function", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda")); - addErrorTrace(e, pos, "from call site%s", ""); + if (pos) addErrorTrace(e, pos, "from call site%s", ""); } throw; } @@ -1841,7 +1585,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (countCalls) primOpCalls[name]++; try { - vCur.primOp->fun(*this, pos, args, vCur); + vCur.primOp->fun(*this, noPos, args, vCur); } catch (Error & e) { addErrorTrace(e, pos, "while calling the '%1%' builtin", name); throw; @@ -1885,6 +1629,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (countCalls) primOpCalls[name]++; try { + // TODO: + // 1. Unify this and above code. Heavily redundant. + // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc) + // so the debugger allows to inspect the wrong parameters passed to the builtin. primOp->primOp->fun(*this, noPos, vArgs, vCur); } catch (Error & e) { addErrorTrace(e, pos, "while calling the '%1%' builtin", name); @@ -1913,7 +1661,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur); + throwError(pos, "attempt to call something which is not a function but %1%", "", "", 0, 0, &vCur, 0, noPos, "", 0, 0, 0); } vRes = vCur; @@ -1977,13 +1725,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { attrs.insert(*j); } else if (!i.def) { - throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') - + throwError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name], - *fun.lambda.env, *fun.lambda.fun); +https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", "", "", &i.name, 0, 0, 0, noPos, "", 0, + fun.lambda.env, fun.lambda.fun); } } } @@ -2016,7 +1763,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(state.symbols, out); - state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); + //tmp: state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); } body->eval(state, env, v); } @@ -2193,14 +1940,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this); + state.throwError(i_pos, "cannot add %1% to an integer", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this); + state.throwError(i_pos, "cannot add %1% to a float", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -2220,7 +1967,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this); + state.throwError(pos, "a string that refers to a store path cannot be appended to a path", "", "", 0, 0, 0, 0, noPos, "", 0, &env, this); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); @@ -2275,7 +2022,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - throwTypeError("value is %1% while an integer was expected", v); + throwError(noPos, "value is %1% while an integer was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); return v.integer; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2291,7 +2038,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwTypeError("value is %1% while a float was expected", v); + throwError(noPos, "value is %1% while a float was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); return v.fpoint; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2305,7 +2052,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - throwTypeError("value is %1% while a Boolean was expected", v); + throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2325,7 +2072,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwTypeError("value is %1% while a function was expected", v); + throwError(noPos, "value is %1% while a function was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -2338,7 +2085,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - throwTypeError("value is %1% while a string was expected", v); + throwError(noPos, "value is %1% while a string was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); return v.string.s; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2401,9 +2148,9 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s auto s = forceString(v, pos, errorCtx); if (v.string.context) { if (pos) - throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]); + throwError(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, 0, 0); else - throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]); + throwError(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, 0, 0); } return s; } catch (Error & e) { @@ -2463,7 +2210,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet return std::move(*maybeString); auto i = v.attrs->find(sOutPath); if (i == v.attrs->end()) - throwTypeErrorWithTrace("cannot coerce a set to a string", pos, errorCtx); + throwErrorWithTrace(noPos, "cannot coerce a set to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -2498,14 +2245,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet } } - throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(v), pos, errorCtx); + throwErrorWithTrace(noPos, "cannot coerce %1% to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throwEvalError("file names are not allowed to end in '%1%'", drvExtension); + throwError(noPos, "file names are not allowed to end in '%1%'", drvExtension, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); Path dstPath; auto i = srcToStore.find(path); @@ -2530,7 +2277,7 @@ Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throwEvalErrorWithTrace("string '%1%' doesn't represent an absolute path", path, pos, errorCtx); + throwErrorWithTrace(noPos, "string '%1%' doesn't represent an absolute path", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); return path; } @@ -2540,7 +2287,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & co auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - throwEvalErrorWithTrace("path '%1%' is not in the Nix store", path, pos, errorCtx); + throwErrorWithTrace(noPos, "path '%1%' is not in the Nix store", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); } @@ -2564,7 +2311,6 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v if (v1.type() != v2.type()) return false; switch (v1.type()) { - case nInt: return v1.integer == v2.integer; @@ -2618,7 +2364,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v return v1.fpoint == v2.fpoint; default: - throwEvalErrorWithTrace("cannot compare %1% with %2%", showType(v1), showType(v2), pos, errorCtx); + throwErrorWithTrace(noPos, "cannot compare %1% with %2%", "", "", 0, 0, &v1, &v2, noPos, "", 0, pos, errorCtx, 0, 0); } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 5fe33025c..fd5ac3e77 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -146,30 +146,27 @@ public: template [[gnu::noinline, gnu::noreturn]] - void debugThrow(E && error, const Env & env, const Expr & expr) + void debugThrowLastTrace(E && error) { - if (debugRepl) - runDebugRepl(&error, env, expr); - - throw std::move(error); + debugThrow(error, nullptr, nullptr); } template [[gnu::noinline, gnu::noreturn]] - void debugThrowLastTrace(E && e) + void debugThrow(E && error, const Env * env, const Expr * expr) { - // Call this in the situation where Expr and Env are inaccessible. - // The debugger will start in the last context that's in the - // DebugTrace stack. - if (debugRepl && !debugTraces.empty()) { - const DebugTrace & last = debugTraces.front(); - runDebugRepl(&e, last.env, last.expr); + if (debugRepl && ((env && expr) || !debugTraces.empty())) { + if (!env || !expr) { + const DebugTrace & last = debugTraces.front(); + env = &last.env; + expr = &last.expr; + } + runDebugRepl(&error, *env, *expr); } - throw std::move(e); + throw std::move(error); } - private: SrcToStore srcToStore; @@ -315,118 +312,30 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); - // coerce-strings + template [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const Value & v) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string_view s2) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string_view s2, const std::string_view s3) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const Value & v) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const Value & v) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const char * s, const Value & v) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeErrorWithTrace(const PosIdx, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeErrorWithTrace(const PosIdx, const nix::Suggestions&, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeErrorWithTrace(const char*, std::string_view, const Pos &, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwTypeErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwEvalErrorWithTrace(const char*, std::string_view, std::string_view, nix::PosIdx, std::string_view) const; - [[gnu::noinline, gnu::noreturn]] - void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const; - [[gnu::noinline, gnu::noreturn]] - void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const; - [[gnu::noinline, gnu::noreturn]] - void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const; + void throwErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr); - // origin/master + template [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string & s2); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string & s2); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string & s2, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string & s2, const std::string & s3, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const char * s, const std::string & s2, const std::string & s3); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, - Env & env, Expr & expr); - - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const Value & v); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const Value & v, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2, - Env & env, Expr & expr); - [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const char * s, const Value & v, - Env & env, Expr & expr); - - [[gnu::noinline, gnu::noreturn]] - void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, - Env & env, Expr & expr); - - [[gnu::noinline, gnu::noreturn]] - void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, - Env & env, Expr & expr); - - [[gnu::noinline, gnu::noreturn]] - void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, - Env & env, Expr & expr); + void throwError( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + Env * env, Expr * expr); [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 8a1f9311d..544f8efc4 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -137,7 +137,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall return outputs; Bindings::iterator i; - if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) { + if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) { Outputs result; auto out = outputs.find(queryOutputName()); if (out == outputs.end()) diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7768ed8df..a91b8b41f 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(CUR_POS, $2, $4, $6); } + : IF expr THEN expr ELSE expr { $$ = new ExprIf(makeCurPos(@2, data), $2, $4, $6); } | expr_op ; @@ -788,13 +788,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c if (hasPrefix(path, "nix/")) return concatStrings(corepkgsPrefix, path.substr(4)); - debugThrowLastTrace(ThrownError({ + debugThrow(ThrownError({ .msg = hintfmt(evalSettings.pureEval ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", path), .errPos = positions[pos] - })); + }), 0, 0); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 6b7c20fd5..5ce0f4015 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -561,7 +561,7 @@ struct CompareValues return v1->integer < v2->fpoint; if (v1->type() != v2->type()) state.debugThrowLastTrace(EvalError({ - ."cannot compare %1% with %2%", showType(*v1), showType(*v2))); + .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), .errPos = std::nullopt, })); switch (v1->type()) { @@ -586,7 +586,7 @@ struct CompareValues } default: state.debugThrowLastTrace(EvalError({ - .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), + .msg = hintfmt("%scannot compare %s with %s: nix does not define an ordering for that type", errorCtx, showType(*v1), showType(*v2)), .errPos = std::nullopt, })); } @@ -617,12 +617,11 @@ static Bindings::iterator getAttr( .msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], errorCtx), .errPos = state.positions[attrSet->pos], }); - // TODO XXX - // Adding another trace for the function name to make it clear - // which call received wrong arguments. - //e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName)); - //state.debugThrowLastTrace(e); - } + // TODO XXX + // Adding another trace for the function name to make it clear + // which call received wrong arguments. + //e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName)); + //state.debugThrowLastTrace(e); } return value; } From e412bb6d30fb33e0b80928437a38ea5cf37ce78c Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sun, 11 Sep 2022 22:58:59 +0200 Subject: [PATCH 24/97] fix remaining fixtures --- src/libexpr/eval.cc | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 646e19bad..27b333807 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1389,11 +1389,10 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) std::set allAttrNames; for (auto & attr : *vAttrs->attrs) allAttrNames.insert(state.symbols[attr.name]); - throw EvalError("foo"); - //tmp: state.throwEvalError( - // pos, - // Suggestions::bestMatches(allAttrNames, state.symbols[name]), - // "attribute '%1%' missing", state.symbols[name], env, *this); + auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); + state.throwError(pos, "attribute '%1%' missing", + "", "", &name, 0, 0, 0, noPos, "", &suggestions, + &env, this); } } vAttrs = j->value; @@ -1503,13 +1502,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throw EvalError("foo"); - /* - throwTypeErrorWithTrace(lambda.pos, + throwErrorWithTrace(lambda.pos, "function '%1%' called without required argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), - i.name, pos, "from call site", *fun.lambda.env, lambda); - */ + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", + &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1528,14 +1524,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & std::set formalNames; for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); - throw EvalError("foo"); - /* - throwTypeErrorWithTrace(lambda.pos, - Suggestions::bestMatches(formalNames, symbols[i.name]), + auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); + throwErrorWithTrace(lambda.pos, "function '%1%' called with unexpected argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), - i.name, pos, "from call site"); - */ + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", + &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); } abort(); // can't happen } @@ -1763,7 +1756,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(state.symbols, out); - //tmp: state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); + state.throwError(pos, "assertion '%1%' failed", out.str(), "", 0, 0, 0, 0, noPos, "", 0, &env, this); } body->eval(state, env, v); } From 96f2dd99d39da61706895b05ed864558679fac79 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sun, 11 Sep 2022 23:09:36 +0200 Subject: [PATCH 25/97] fix remaining foo stuff --- src/libexpr/eval-cache.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 89afac0e8..4eb3fb53e 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -571,14 +571,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); + root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); return v.type() == nString ? v.string.s : v.path; } @@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); } } @@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path, {}}; else - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); + root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); } bool AttrCursor::getBool() @@ -626,14 +626,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); } } auto & v = forceValue(); if (v.type() != nBool) - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); return v.boolean; } @@ -703,14 +703,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); } } auto & v = forceValue(); if (v.type() != nAttrs) - throw EvalError("foo"); //root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); + root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); std::vector attrs; for (auto & attr : *getValue().attrs) From b945b844a9ce8479872f6280aedde27e2974b7f3 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Mon, 17 Oct 2022 03:05:02 +0200 Subject: [PATCH 26/97] Initial frames support --- src/libexpr/eval.cc | 43 ++++++++++++++++++++++++++++++++++++------ src/libexpr/eval.hh | 15 ++++++++++++++- src/libexpr/primops.cc | 6 +++--- src/libutil/error.cc | 9 +++++++-- src/libutil/error.hh | 3 ++- 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 27b333807..050b49833 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -836,6 +836,37 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & evaluator. So here are some helper functions for throwing exceptions. */ +template +void EvalState::throwFrameErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr) +{ + hintformat f(format); + if (!s1.empty()) { f = f % s1; } + if (!s2.empty()) { f = f % s2; } + if (sym1) { f = f % symbols[*sym1]; } + if (sym2) { f = f % symbols[*sym2]; } + if (val1) { f = f % showType(*val1); } + if (val2) { f = f % showType(*val2); } + if (pos1) { f = f % positions[pos1]; } + if (!s3.empty()) { f = f % s3; } + + auto e = ErrorType(ErrorInfo { + .msg = f, + .errPos = positions[pos], + .suggestions = suggestions ? *suggestions : Suggestions(), + }); + e.addTrace(positions[tracePos], traceStr, true); + debugThrow(e, env, expr); +} + template void EvalState::throwErrorWithTrace( PosIdx pos, const char* format, @@ -901,9 +932,9 @@ void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) e.addTrace(std::nullopt, s, s2); } -void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const +void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const { - e.addTrace(positions[pos], s, s2); + e.addTrace(positions[pos], hintfmt(s, s2), frame); } static std::unique_ptr makeDebugTraceStacker( @@ -1164,7 +1195,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwError(pos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1178,7 +1209,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - throwError(pos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + throwError(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1502,7 +1533,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throwErrorWithTrace(lambda.pos, + throwFrameErrorWithTrace(lambda.pos, "function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); @@ -1525,7 +1556,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - throwErrorWithTrace(lambda.pos, + throwFrameErrorWithTrace(lambda.pos, "function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index fd5ac3e77..3d72d3fe1 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -312,6 +312,19 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); + template + [[gnu::noinline, gnu::noreturn]] + void throwFrameErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr); + template [[gnu::noinline, gnu::noreturn]] void throwErrorWithTrace( @@ -340,7 +353,7 @@ public: [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; [[gnu::noinline]] - void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; + void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const; public: /* Return true iff the value `v' denotes a derivation (i.e. a diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9be4bbf6a..625b0aa4a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1171,9 +1171,9 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * } } catch (Error & e) { - e.addTrace(state.positions[posDrvName], - "while evaluating the attribute '%1%' of the derivation '%2%'", - key, drvName); + e.addTrace(state.positions[noPos], + hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName), + true); throw; } } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 5f86e1e76..cf4f4a56f 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -9,9 +9,9 @@ namespace nix { const std::string nativeSystem = SYSTEM; -void BaseError::addTrace(std::optional e, hintformat hint) +void BaseError::addTrace(std::optional e, hintformat hint, bool frame) { - err.traces.push_front(Trace { .pos = e, .hint = hint }); + err.traces.push_front(Trace { .pos = e, .hint = hint, .frame = frame }); } // c++ std::exception descendants must have a 'const char* what()' function. @@ -382,6 +382,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * */ + bool frameOnly = false; if (!einfo.traces.empty()) { unsigned int count = 0; for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { @@ -391,7 +392,11 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s } if (iter->hint.str().empty()) continue; + if (frameOnly && !iter->frame) continue; + count++; + frameOnly = iter->frame; + oss << "\n" << "… " << iter->hint.str() << "\n"; if (iter->pos.has_value() && (*iter->pos)) { diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 50335676e..bf99581e2 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -110,6 +110,7 @@ void printAtPos(const ErrPos & pos, std::ostream & out); struct Trace { std::optional pos; hintformat hint; + bool frame; }; struct ErrorInfo { @@ -188,7 +189,7 @@ public: addTrace(e, hintfmt(std::string(fs), args...)); } - void addTrace(std::optional e, hintformat hint); + void addTrace(std::optional e, hintformat hint, bool frame = false); bool hasTrace() const { return !err.traces.empty(); } }; From 520404f450e7a0dbaa77ef6352677d992920e34d Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 20 Oct 2022 13:55:15 +0200 Subject: [PATCH 27/97] Revert custom position of 'if' blocks --- src/libexpr/parser.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index a91b8b41f..0cf0d56f0 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 ; From 512f6be9b5dae74d3a6112f90090bec442d2a86a Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 20 Oct 2022 14:17:05 +0200 Subject: [PATCH 28/97] Reword incomparable types message --- src/libexpr/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 625b0aa4a..508608183 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -586,7 +586,7 @@ struct CompareValues } default: state.debugThrowLastTrace(EvalError({ - .msg = hintfmt("%scannot compare %s with %s: nix does not define an ordering for that type", errorCtx, showType(*v1), showType(*v2)), + .msg = hintfmt("%scannot compare %s with %s; values of that type are incomparable", errorCtx, showType(*v1), showType(*v2)), .errPos = std::nullopt, })); } From 31ce52a045ac384b542f8409f20c0461ab6263ac Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 20 Oct 2022 14:18:35 +0200 Subject: [PATCH 29/97] Fix context message being printed twice with forceStringNoCtx --- src/libexpr/eval.cc | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 050b49833..0febff022 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2168,19 +2168,11 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const PosI std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx) { - try { - auto s = forceString(v, pos, errorCtx); - if (v.string.context) { - if (pos) - throwError(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, 0, 0); - else - throwError(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, 0, 0); - } - return s; - } catch (Error & e) { - e.addTrace(positions[pos], errorCtx); - throw; + auto s = forceString(v, pos, errorCtx); + if (v.string.context) { + throwErrorWithTrace(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); } + return s; } From 4a909c142c93f43a0d1913b54bc9f7b690f172aa Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Thu, 20 Oct 2022 14:25:11 +0200 Subject: [PATCH 30/97] Rollback unneeded throwFrameErrorWithTrace function --- src/libexpr/eval.cc | 44 ++++++++------------------------------------ src/libexpr/eval.hh | 13 ------------- 2 files changed, 8 insertions(+), 49 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0febff022..0a9ac68be 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -836,37 +836,6 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & evaluator. So here are some helper functions for throwing exceptions. */ -template -void EvalState::throwFrameErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr) -{ - hintformat f(format); - if (!s1.empty()) { f = f % s1; } - if (!s2.empty()) { f = f % s2; } - if (sym1) { f = f % symbols[*sym1]; } - if (sym2) { f = f % symbols[*sym2]; } - if (val1) { f = f % showType(*val1); } - if (val2) { f = f % showType(*val2); } - if (pos1) { f = f % positions[pos1]; } - if (!s3.empty()) { f = f % s3; } - - auto e = ErrorType(ErrorInfo { - .msg = f, - .errPos = positions[pos], - .suggestions = suggestions ? *suggestions : Suggestions(), - }); - e.addTrace(positions[tracePos], traceStr, true); - debugThrow(e, env, expr); -} - template void EvalState::throwErrorWithTrace( PosIdx pos, const char* format, @@ -1533,7 +1502,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throwFrameErrorWithTrace(lambda.pos, + throwErrorWithTrace(lambda.pos, "function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); @@ -1556,7 +1525,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - throwFrameErrorWithTrace(lambda.pos, + throwErrorWithTrace(lambda.pos, "function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); @@ -1582,9 +1551,12 @@ 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 the '%s' function", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda")); - if (pos) addErrorTrace(e, pos, "from call site%s", ""); + addErrorTrace(e, + lambda.pos, + "while evaluating the '%s' function", + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + true); + if (pos) addErrorTrace(e, pos, "from call site%s", "", true); } throw; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 3d72d3fe1..234b5c06b 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -312,19 +312,6 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); - template - [[gnu::noinline, gnu::noreturn]] - void throwFrameErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr); - template [[gnu::noinline, gnu::noreturn]] void throwErrorWithTrace( From 8c3afd2d685bdd616dcdd856803e571578685426 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 22 Oct 2022 23:37:54 +0200 Subject: [PATCH 31/97] Introduce an Error builder to tackle complexity --- mk/lib.mk | 2 + src/libexpr/eval-cache.cc | 16 +-- src/libexpr/eval-inline.hh | 6 +- src/libexpr/eval.cc | 202 ++++++++++++++++++------------------- src/libexpr/eval.hh | 73 +++++++++----- 5 files changed, 160 insertions(+), 139 deletions(-) diff --git a/mk/lib.mk b/mk/lib.mk index 92f0983d5..49f505ff8 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -87,6 +87,8 @@ endif # Pass -g if we want debug info. BUILD_DEBUG ?= 1 +GLOBAL_CXXFLAGS += -gdwarf-4 + ifeq ($(BUILD_DEBUG), 1) GLOBAL_CFLAGS += -g GLOBAL_CXXFLAGS += -g diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 4eb3fb53e..df5348148 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -571,14 +571,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); return v.type() == nString ? v.string.s : v.path; } @@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } @@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path, {}}; else - root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); } bool AttrCursor::getBool() @@ -626,14 +626,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nBool) - root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); return v.boolean; } @@ -703,14 +703,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nAttrs) - root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 43a4e8fa6..279f5b8be 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -103,7 +103,7 @@ void EvalState::forceValue(Value & v, Callable getPos) else if (v.isApp()) callFunction(*v.app.left, *v.app.right, v, noPos); else if (v.isBlackhole()) - throwError(getPos(), "infinite recursion encountered", "", "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("infinite recursion encountered").atPos(getPos()).debugThrow(); } @@ -121,7 +121,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e forceValue(v, noPos); if (v.type() != nAttrs) { PosIdx pos = getPos(); - this->throwErrorWithTrace(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + this->error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } @@ -131,7 +131,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e { forceValue(v, noPos); if (!v.isList()) { - this->throwErrorWithTrace(noPos, "value is %1% while a list was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + this->error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0a9ac68be..bd102b186 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -418,6 +418,59 @@ static Strings parseNixPath(const std::string & s) return res; } +template +ErrorBuilder::ErrorBuilder(EvalState & s): + state(s), + info(ErrorInfo { .msg = hintformat(""), .errPos = std::nullopt, }) +{} + +template +ErrorBuilder & ErrorBuilder::atPos(PosIdx pos) +{ + info.errPos = state.positions[pos]; + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text) +{ + info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false }); + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text) +{ + info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true }); + return *this; +} + +template +ErrorBuilder & ErrorBuilder::suggestions(Suggestions & s) { + info.suggestions = s; + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr) { + // NOTE: This is abusing side-effects. + // TODO: check compatibility with nested debugger calls. + state.debugTraces.push_front(DebugTrace { + .pos = std::nullopt, + .expr = expr, + .env = env, + .hint = hintformat("Fake frame for debugg{ing,er} purposes"), + .isError = true + }); + return *this; +} + +template +void ErrorBuilder::debugThrow() { + // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() + state.debugThrowLastTrace(ErrorType(info)); +} + EvalState::EvalState( const Strings & _searchPath, @@ -831,71 +884,6 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & } } -/* Every "format" object (even temporary) takes up a few hundred bytes - of stack space, which is a real killer in the recursive - evaluator. So here are some helper functions for throwing - exceptions. */ - -template -void EvalState::throwErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr) -{ - hintformat f(format); - if (!s1.empty()) { f = f % s1; } - if (!s2.empty()) { f = f % s2; } - if (sym1) { f = f % symbols[*sym1]; } - if (sym2) { f = f % symbols[*sym2]; } - if (val1) { f = f % showType(*val1); } - if (val2) { f = f % showType(*val2); } - if (pos1) { f = f % positions[pos1]; } - if (!s3.empty()) { f = f % s3; } - - auto e = ErrorType(ErrorInfo { - .msg = f, - .errPos = positions[pos], - .suggestions = suggestions ? *suggestions : Suggestions(), - }); - e.addTrace(positions[tracePos], traceStr); - debugThrow(e, env, expr); -} - -template -void EvalState::throwError( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - Env * env, Expr * expr) -{ - hintformat f(format); - if (!s1.empty()) { f = f % s1; } - if (!s2.empty()) { f = f % s2; } - if (sym1) { f = f % symbols[*sym1]; } - if (sym2) { f = f % symbols[*sym2]; } - if (val1) { f = f % showType(*val1); } - if (val2) { f = f % showType(*val2); } - if (pos1) { f = f % positions[pos1]; } - if (!s3.empty()) { f = f % s3; } - - auto e = ErrorType(ErrorInfo { - .msg = f, - .errPos = positions[pos], - .suggestions = suggestions ? *suggestions : Suggestions(), - }); - debugThrow(e, env, expr); -} - void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { e.addTrace(std::nullopt, s, s2); @@ -990,7 +978,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - throwError(var.pos, "undefined variable '%1%'", "", "", &var.name, 0, 0, 0, noPos, "", 0, env, const_cast(&var)); + error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1140,7 +1128,7 @@ void EvalState::cacheFile( // computation. if (mustBeTrivial && !(dynamic_cast(e))) - throwError(noPos, "file '%s' must be an attribute set", path, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("file '%s' must be an attribute set", path).debugThrow(); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); @@ -1164,7 +1152,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1178,7 +1166,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - throwError(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1287,7 +1275,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - state.throwError(i.pos, "dynamic attribute '%1%' already defined at %2%", "", "", &nameSym, 0, 0, 0, j->pos, "", 0, &env, this); + state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1390,9 +1378,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) for (auto & attr : *vAttrs->attrs) allAttrNames.insert(state.symbols[attr.name]); auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); - state.throwError(pos, "attribute '%1%' missing", - "", "", &name, 0, 0, 0, noPos, "", &suggestions, - &env, this); + state.error("attribute '%1%' missing", state.symbols[name]) + .atPos(pos).suggestions(suggestions).withFrame(env, *this).debugThrow(); } } vAttrs = j->value; @@ -1502,10 +1489,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throwErrorWithTrace(lambda.pos, - "function '%1%' called without required argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", - &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); + error("function '%1%' called without required argument '%2%'", + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + symbols[i.name]) + .atPos(lambda.pos) + .withTrace(pos, "from call site") + .withFrame(*fun.lambda.env, lambda) + .debugThrow(); } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1525,10 +1515,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - throwErrorWithTrace(lambda.pos, - "function '%1%' called with unexpected argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", - &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); + error("function '%1%' called with unexpected argument '%2%'", + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + symbols[i.name]) + .atPos(lambda.pos) + .withTrace(pos, "from call site") + .suggestions(suggestions) + .withFrame(*fun.lambda.env, lambda) + .debugThrow(); } abort(); // can't happen } @@ -1553,8 +1547,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (loggerSettings.showTrace.get()) { addErrorTrace(e, lambda.pos, - "while evaluating the '%s' function", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + "while evaluating %s", + lambda.name + ? concatStrings("'", symbols[lambda.name], "'") + : "anonymous lambda", true); if (pos) addErrorTrace(e, pos, "from call site%s", "", true); } @@ -1657,7 +1653,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - throwError(pos, "attempt to call something which is not a function but %1%", "", "", 0, 0, &vCur, 0, noPos, "", 0, 0, 0); + error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); } vRes = vCur; @@ -1721,12 +1717,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { attrs.insert(*j); } else if (!i.def) { - throwError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') + error(R"(cannot evaluate a function that has an argument without a value ('%1%') Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", "", "", &i.name, 0, 0, 0, noPos, "", 0, - fun.lambda.env, fun.lambda.fun); +https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name]) + .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow(); } } } @@ -1759,7 +1755,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(state.symbols, out); - state.throwError(pos, "assertion '%1%' failed", out.str(), "", 0, 0, 0, 0, noPos, "", 0, &env, this); + state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); } body->eval(state, env, v); } @@ -1936,14 +1932,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - state.throwError(i_pos, "cannot add %1% to an integer", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); + state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - state.throwError(i_pos, "cannot add %1% to a float", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); + state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -1963,7 +1959,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - state.throwError(pos, "a string that refers to a store path cannot be appended to a path", "", "", 0, 0, 0, 0, noPos, "", 0, &env, this); + state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); @@ -2018,7 +2014,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - throwError(noPos, "value is %1% while an integer was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while an integer was expected", showType(v)).debugThrow(); return v.integer; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2034,7 +2030,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwError(noPos, "value is %1% while a float was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a float was expected", showType(v)).debugThrow(); return v.fpoint; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2048,7 +2044,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a Boolean was expected", showType(v)).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2068,7 +2064,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwError(noPos, "value is %1% while a function was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a function was expected", showType(v)).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -2081,7 +2077,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - throwError(noPos, "value is %1% while a string was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a string was expected", showType(v)).debugThrow(); return v.string.s; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2142,7 +2138,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s { auto s = forceString(v, pos, errorCtx); if (v.string.context) { - throwErrorWithTrace(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow(); } return s; } @@ -2198,7 +2194,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet return std::move(*maybeString); auto i = v.attrs->find(sOutPath); if (i == v.attrs->end()) - throwErrorWithTrace(noPos, "cannot coerce a set to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -2233,14 +2229,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet } } - throwErrorWithTrace(noPos, "cannot coerce %1% to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throwError(noPos, "file names are not allowed to end in '%1%'", drvExtension, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); Path dstPath; auto i = srcToStore.find(path); @@ -2265,7 +2261,7 @@ Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throwErrorWithTrace(noPos, "string '%1%' doesn't represent an absolute path", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); return path; } @@ -2275,7 +2271,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & co auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - throwErrorWithTrace(noPos, "path '%1%' is not in the Nix store", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } @@ -2352,7 +2348,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v return v1.fpoint == v2.fpoint; default: - throwErrorWithTrace(noPos, "cannot compare %1% with %2%", "", "", 0, 0, &v1, &v2, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 234b5c06b..ff407d090 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -87,6 +87,47 @@ struct DebugTrace { void debugError(Error * e, Env & env, Expr & expr); +template +class ErrorBuilder +{ + + EvalState & state; + ErrorInfo info; + + public: + [[gnu::noinline]] + ErrorBuilder(EvalState & s); + + [[gnu::noinline]] + ErrorBuilder & atPos(PosIdx pos); + + template + [[gnu::noinline]] + ErrorBuilder & msg(const std::string & fs, const Args & ... args) + { + hintformat f(fs); + formatHelper(f, args...); + info.msg = f; + return *this; + } + + [[gnu::noinline]] + ErrorBuilder & withTrace(PosIdx pos, const std::string_view text); + + [[gnu::noinline]] + ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text); + + [[gnu::noinline]] + ErrorBuilder & suggestions(Suggestions & s); + + [[gnu::noinline]] + ErrorBuilder & withFrame(const Env & e, const Expr & ex); + + [[gnu::noinline, gnu::noreturn]] + void debugThrow(); +}; + + class EvalState : public std::enable_shared_from_this { public: @@ -167,6 +208,13 @@ public: throw std::move(error); } + template + ErrorBuilder & error(const std::string & fs, const Args & ... args) { + ErrorBuilder * errorBuilder = new ErrorBuilder(*this); + errorBuilder->msg(fs, args ...); + return *errorBuilder; + } + private: SrcToStore srcToStore; @@ -312,31 +360,6 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); - template - [[gnu::noinline, gnu::noreturn]] - void throwErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr); - - template - [[gnu::noinline, gnu::noreturn]] - void throwError( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - Env * env, Expr * expr); - [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; [[gnu::noinline]] From 2a7348f986f8c2c9d6419ea44f7a6b19a667c7d4 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 22 Oct 2022 23:38:50 +0200 Subject: [PATCH 32/97] Fixup merge typo --- doc/manual/src/release-notes/rl-next.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 9c43132d4..a08dc9c9b 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -7,5 +7,3 @@ * Error traces have been reworked to provide detailed explanations and more accurate error locations. A short excerpt of the trace is now shown by default when an error occurs. - ->>>>>>> origin/master From 3cd1c3b9888ee4dfee0f0694061e6976949d5245 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 22 Oct 2022 23:41:05 +0200 Subject: [PATCH 33/97] Revert local settings merged by error --- .gitignore | 1 - mk/lib.mk | 2 -- 2 files changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index d7f310396..8e0db013f 100644 --- a/.gitignore +++ b/.gitignore @@ -129,4 +129,3 @@ nix-rust/target result .vscode/ -baseline diff --git a/mk/lib.mk b/mk/lib.mk index 49f505ff8..92f0983d5 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -87,8 +87,6 @@ endif # Pass -g if we want debug info. BUILD_DEBUG ?= 1 -GLOBAL_CXXFLAGS += -gdwarf-4 - ifeq ($(BUILD_DEBUG), 1) GLOBAL_CFLAGS += -g GLOBAL_CXXFLAGS += -g From 8bd8583bc7a430eeee0f5d5e5cb502158419a500 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sun, 23 Oct 2022 00:11:44 +0200 Subject: [PATCH 34/97] Try to please clang with convoluted templates --- src/libexpr/eval.cc | 6 ------ src/libexpr/eval.hh | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index bd102b186..3b8c0b9c1 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -465,12 +465,6 @@ ErrorBuilder & ErrorBuilder::withFrame(const Env & env, co return *this; } -template -void ErrorBuilder::debugThrow() { - // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() - state.debugThrowLastTrace(ErrorType(info)); -} - EvalState::EvalState( const Strings & _searchPath, diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index ff407d090..5e88eb950 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -124,7 +124,11 @@ class ErrorBuilder ErrorBuilder & withFrame(const Env & e, const Expr & ex); [[gnu::noinline, gnu::noreturn]] - void debugThrow(); + void ErrorBuilder::debugThrow() { + // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() + state.debugThrowLastTrace(ErrorType(info)); + } + }; From e93bf69b448d4f4ce6c3fe7b7acfa904afe058c0 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Tue, 25 Oct 2022 01:46:10 +0200 Subject: [PATCH 35/97] Rework error throwing, and test it --- src/libexpr/eval-cache.cc | 16 +++--- src/libexpr/eval-inline.hh | 6 +- src/libexpr/eval.cc | 87 ++++++++++++---------------- src/libexpr/eval.hh | 63 ++++++++++---------- src/libexpr/primops.cc | 16 ++---- src/libexpr/tests/error_traces.cc | 96 +++++++++++++++++++++++++++++++ src/libexpr/tests/primops.cc | 6 ++ src/libutil/error.hh | 2 + 8 files changed, 190 insertions(+), 102 deletions(-) create mode 100644 src/libexpr/tests/error_traces.cc diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index df5348148..9a8a7fe62 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -571,14 +571,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); return v.type() == nString ? v.string.s : v.path; } @@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } @@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path, {}}; else - root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); } bool AttrCursor::getBool() @@ -626,14 +626,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nBool) - root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); return v.boolean; } @@ -703,14 +703,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nAttrs) - root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 279f5b8be..f0da688db 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -103,7 +103,7 @@ void EvalState::forceValue(Value & v, Callable getPos) else if (v.isApp()) callFunction(*v.app.left, *v.app.right, v, noPos); else if (v.isBlackhole()) - error("infinite recursion encountered").atPos(getPos()).debugThrow(); + error("infinite recursion encountered").atPos(getPos()).template debugThrow(); } @@ -121,7 +121,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e forceValue(v, noPos); if (v.type() != nAttrs) { PosIdx pos = getPos(); - this->error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); + error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } @@ -131,7 +131,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e { forceValue(v, noPos); if (!v.isList()) { - this->error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); + error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 3b8c0b9c1..0ff8bba4d 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -418,41 +418,30 @@ static Strings parseNixPath(const std::string & s) return res; } -template -ErrorBuilder::ErrorBuilder(EvalState & s): - state(s), - info(ErrorInfo { .msg = hintformat(""), .errPos = std::nullopt, }) -{} - -template -ErrorBuilder & ErrorBuilder::atPos(PosIdx pos) +ErrorBuilder & ErrorBuilder::atPos(PosIdx pos) { info.errPos = state.positions[pos]; return *this; } -template -ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text) +ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text) { - info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false }); + info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false }); return *this; } -template -ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text) +ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text) { - info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true }); + info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true }); return *this; } -template -ErrorBuilder & ErrorBuilder::suggestions(Suggestions & s) { +ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s) { info.suggestions = s; return *this; } -template -ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr) { +ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr) { // NOTE: This is abusing side-effects. // TODO: check compatibility with nested debugger calls. state.debugTraces.push_front(DebugTrace { @@ -972,7 +961,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); + error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1122,7 +1111,7 @@ void EvalState::cacheFile( // computation. if (mustBeTrivial && !(dynamic_cast(e))) - error("file '%s' must be an attribute set", path).debugThrow(); + error("file '%s' must be an attribute set", path).debugThrow(); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); @@ -1146,7 +1135,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow(); + error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1160,7 +1149,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow(); + error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1269,7 +1258,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); + state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1372,8 +1361,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) for (auto & attr : *vAttrs->attrs) allAttrNames.insert(state.symbols[attr.name]); auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); - state.error("attribute '%1%' missing", state.symbols[name]) - .atPos(pos).suggestions(suggestions).withFrame(env, *this).debugThrow(); + state.error("attribute '%1%' missing", state.symbols[name]) + .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow(); } } vAttrs = j->value; @@ -1483,13 +1472,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - error("function '%1%' called without required argument '%2%'", + error("function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), symbols[i.name]) .atPos(lambda.pos) .withTrace(pos, "from call site") .withFrame(*fun.lambda.env, lambda) - .debugThrow(); + .debugThrow(); } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1509,14 +1498,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - error("function '%1%' called with unexpected argument '%2%'", + error("function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), symbols[i.name]) .atPos(lambda.pos) .withTrace(pos, "from call site") - .suggestions(suggestions) + .withSuggestions(suggestions) .withFrame(*fun.lambda.env, lambda) - .debugThrow(); + .debugThrow(); } abort(); // can't happen } @@ -1647,7 +1636,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); + error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); } vRes = vCur; @@ -1711,12 +1700,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { attrs.insert(*j); } else if (!i.def) { - error(R"(cannot evaluate a function that has an argument without a value ('%1%') + error(R"(cannot evaluate a function that has an argument without a value ('%1%') Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name]) - .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow(); + .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow(); } } } @@ -1749,7 +1738,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(state.symbols, out); - state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); + state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); } body->eval(state, env, v); } @@ -1926,14 +1915,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); + state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); + state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -1953,7 +1942,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); + state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); @@ -2008,7 +1997,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - error("value is %1% while an integer was expected", showType(v)).debugThrow(); + error("value is %1% while an integer was expected", showType(v)).debugThrow(); return v.integer; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2024,7 +2013,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - error("value is %1% while a float was expected", showType(v)).debugThrow(); + error("value is %1% while a float was expected", showType(v)).debugThrow(); return v.fpoint; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2038,7 +2027,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - error("value is %1% while a Boolean was expected", showType(v)).debugThrow(); + error("value is %1% while a Boolean was expected", showType(v)).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2058,7 +2047,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - error("value is %1% while a function was expected", showType(v)).debugThrow(); + error("value is %1% while a function was expected", showType(v)).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -2071,7 +2060,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - error("value is %1% while a string was expected", showType(v)).debugThrow(); + error("value is %1% while a string was expected", showType(v)).debugThrow(); return v.string.s; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2132,7 +2121,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s { auto s = forceString(v, pos, errorCtx); if (v.string.context) { - error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow(); + error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow(); } return s; } @@ -2188,7 +2177,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet return std::move(*maybeString); auto i = v.attrs->find(sOutPath); if (i == v.attrs->end()) - error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); + error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -2223,14 +2212,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet } } - error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); + error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); + error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); Path dstPath; auto i = srcToStore.find(path); @@ -2255,7 +2244,7 @@ Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); + error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); return path; } @@ -2265,7 +2254,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & co auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); + error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } @@ -2342,7 +2331,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v return v1.fpoint == v2.fpoint; default: - error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); + error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 5e88eb950..1f610a02f 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -87,48 +87,40 @@ struct DebugTrace { void debugError(Error * e, Env & env, Expr & expr); -template class ErrorBuilder { - + private: EvalState & state; ErrorInfo info; + ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { } + public: - [[gnu::noinline]] - ErrorBuilder(EvalState & s); - - [[gnu::noinline]] - ErrorBuilder & atPos(PosIdx pos); - template - [[gnu::noinline]] - ErrorBuilder & msg(const std::string & fs, const Args & ... args) + [[nodiscard, gnu::noinline]] + static ErrorBuilder * create(EvalState & s, const Args & ... args) { - hintformat f(fs); - formatHelper(f, args...); - info.msg = f; - return *this; + return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) }); } - [[gnu::noinline]] - ErrorBuilder & withTrace(PosIdx pos, const std::string_view text); + [[nodiscard, gnu::noinline]] + ErrorBuilder & atPos(PosIdx pos); - [[gnu::noinline]] - ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text); + [[nodiscard, gnu::noinline]] + ErrorBuilder & withTrace(PosIdx pos, const std::string_view text); - [[gnu::noinline]] - ErrorBuilder & suggestions(Suggestions & s); + [[nodiscard, gnu::noinline]] + ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text); - [[gnu::noinline]] - ErrorBuilder & withFrame(const Env & e, const Expr & ex); + [[nodiscard, gnu::noinline]] + ErrorBuilder & withSuggestions(Suggestions & s); + [[nodiscard, gnu::noinline]] + ErrorBuilder & withFrame(const Env & e, const Expr & ex); + + template [[gnu::noinline, gnu::noreturn]] - void ErrorBuilder::debugThrow() { - // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() - state.debugThrowLastTrace(ErrorType(info)); - } - + void debugThrow(); }; @@ -212,10 +204,12 @@ public: throw std::move(error); } - template - ErrorBuilder & error(const std::string & fs, const Args & ... args) { - ErrorBuilder * errorBuilder = new ErrorBuilder(*this); - errorBuilder->msg(fs, args ...); + ErrorBuilder * errorBuilder; + + template + [[nodiscard, gnu::noinline]] + ErrorBuilder & error(const Args & ... args) { + errorBuilder = ErrorBuilder::create(*this, args...); return *errorBuilder; } @@ -648,6 +642,13 @@ extern EvalSettings evalSettings; static const std::string corepkgsPrefix{"/__corepkgs__/"}; +template +void ErrorBuilder::debugThrow() +{ + // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() + state.debugThrowLastTrace(ErrorType(info)); +} + } #include "eval-inline.hh" diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 508608183..b95f54851 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -560,10 +560,7 @@ struct CompareValues if (v1->type() == nInt && v2->type() == nFloat) return v1->integer < v2->fpoint; if (v1->type() != v2->type()) - state.debugThrowLastTrace(EvalError({ - .msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)), - .errPos = std::nullopt, - })); + state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); switch (v1->type()) { case nInt: return v1->integer < v2->integer; @@ -581,14 +578,11 @@ struct CompareValues } 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"); + return (*this)(v1->listElems()[i], v2->listElems()[i], "while comparing two list elements"); } } default: - state.debugThrowLastTrace(EvalError({ - .msg = hintfmt("%scannot compare %s with %s; values of that type are incomparable", errorCtx, showType(*v1), showType(*v2)), - .errPos = std::nullopt, - })); + state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); } } catch (Error & e) { e.addTrace(std::nullopt, errorCtx); @@ -614,7 +608,7 @@ static Bindings::iterator getAttr( Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { throw TypeError({ - .msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], errorCtx), + .msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)), .errPos = state.positions[attrSet->pos], }); // TODO XXX @@ -628,7 +622,7 @@ static Bindings::iterator getAttr( static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) { - state.forceAttrs(*args[0], noPos, "while evaluating the first argument pased to builtins.genericClosure"); + state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure"); /* Get the start set. */ Bindings::iterator startSet = getAttr(state, state.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure"); diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc new file mode 100644 index 000000000..6905dd561 --- /dev/null +++ b/src/libexpr/tests/error_traces.cc @@ -0,0 +1,96 @@ +#include +#include + +#include "libexprtests.hh" + +namespace nix { + + using namespace testing; + + // Testing eval of PrimOp's + class ErrorTraceTest : public LibExprTest { }; + +#define ASSERT_TRACE1(args, type, message) \ + ASSERT_THROW( \ + try { \ + eval("builtins." args); \ + } catch (BaseError & e) { \ + ASSERT_EQ(PrintToString(e.info().msg), \ + PrintToString(message)); \ + auto trace = e.info().traces.rbegin(); \ + ASSERT_EQ(PrintToString(trace->hint), \ + PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \ + throw; \ + } \ + , type \ + ) + +#define ASSERT_TRACE2(args, type, message, context) \ + ASSERT_THROW( \ + try { \ + eval("builtins." args); \ + } catch (BaseError & e) { \ + ASSERT_EQ(PrintToString(e.info().msg), \ + PrintToString(message)); \ + auto trace = e.info().traces.rbegin(); \ + ASSERT_EQ(PrintToString(trace->hint), \ + PrintToString(context)); \ + ++trace; \ + ASSERT_EQ(PrintToString(trace->hint), \ + PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \ + throw; \ + } \ + , type \ + ) + + //TEST_F(ErrorTraceTest, genericClosure) { + //ASSERT_THROW( + //try { + //eval("builtins.genericClosure 1 1"); + //} catch (BaseError & e) { + //ASSERT_EQ(PrintToString(e.info().msg), PrintToString(hintfmt("value is %s while a set was expected", "an integer"))); + //throw; + //}, TypeError); + //} + + TEST_F(ErrorTraceTest, genericClosure) { \ + ASSERT_TRACE2("genericClosure 1", + TypeError, + hintfmt("value is %s while a set was expected", "an integer"), + hintfmt("while evaluating the first argument passed to builtins.genericClosure")); + + ASSERT_TRACE1("genericClosure {}", + TypeError, + hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure"))); + + ASSERT_TRACE2("genericClosure { startSet = 1; }", + TypeError, + hintfmt("value is %s while a list was expected", "an integer"), + hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure")); + // Okay: "genericClosure { startSet = []; }" + ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }", + TypeError, + hintfmt("value is %s while a function was expected", "a Boolean"), + hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure")); + + ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }", + TypeError, + hintfmt("value is %s while a list was expected", "a Boolean"), + hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming + + ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }", + TypeError, + hintfmt("value is %s while a set was expected", "a Boolean"), + hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure")); + + ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }", + TypeError, + hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure"))); + + ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }", + EvalError, + hintfmt("cannot compare %s with %s", "a string", "an integer"), + hintfmt("while comparing the `key` attributes of two genericClosure elements")); + } + +} /* namespace nix */ diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 16cf66d2c..a4102753c 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -836,4 +836,10 @@ namespace nix { for (const auto [n, elem] : enumerate(v.listItems())) ASSERT_THAT(*elem, IsStringEq(expected[n])); } + + TEST_F(PrimOpTest, genericClosure_not_strict) { + // Operator should not be used when startSet is empty + auto v = eval("builtins.genericClosure { startSet = []; }"); + ASSERT_THAT(v, IsListOfSize(0)); + } } /* namespace nix */ diff --git a/src/libutil/error.hh b/src/libutil/error.hh index bf99581e2..6db77bcbf 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -192,6 +192,8 @@ public: void addTrace(std::optional e, hintformat hint, bool frame = false); bool hasTrace() const { return !err.traces.empty(); } + + const ErrorInfo & info() { return err; }; }; #define MakeError(newClass, superClass) \ From d6f5734c630f893e8695bd37ca2b0dbf2bb24daa Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Tue, 25 Oct 2022 01:57:18 +0200 Subject: [PATCH 36/97] Complete genericClosure tests --- src/libexpr/tests/error_traces.cc | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc index 6905dd561..8741ecdd2 100644 --- a/src/libexpr/tests/error_traces.cc +++ b/src/libexpr/tests/error_traces.cc @@ -43,16 +43,6 @@ namespace nix { , type \ ) - //TEST_F(ErrorTraceTest, genericClosure) { - //ASSERT_THROW( - //try { - //eval("builtins.genericClosure 1 1"); - //} catch (BaseError & e) { - //ASSERT_EQ(PrintToString(e.info().msg), PrintToString(hintfmt("value is %s while a set was expected", "an integer"))); - //throw; - //}, TypeError); - //} - TEST_F(ErrorTraceTest, genericClosure) { \ ASSERT_TRACE2("genericClosure 1", TypeError, @@ -67,7 +57,9 @@ namespace nix { TypeError, hintfmt("value is %s while a list was expected", "an integer"), hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure")); + // Okay: "genericClosure { startSet = []; }" + ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }", TypeError, hintfmt("value is %s while a function was expected", "a Boolean"), @@ -91,6 +83,12 @@ namespace nix { EvalError, hintfmt("cannot compare %s with %s", "a string", "an integer"), hintfmt("while comparing the `key` attributes of two genericClosure elements")); + + ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }", + TypeError, + hintfmt("value is %s while a set was expected", "a Boolean"), + hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure")); + } } /* namespace nix */ From be8744f937150c65ffa5d98ffdc74c502dbcdd5e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 19 Sep 2022 22:21:40 +0200 Subject: [PATCH 37/97] manual: architecture overview these changes were not merged properly and had to be reverted. see merge commit d8e54d19f71f78540dd967b2e42be6a5d8a0b1bb for full history leading up to here. --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/architecture/architecture.md | 77 +++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 doc/manual/src/architecture/architecture.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index 908e7e3d9..b386aa14b 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -59,6 +59,7 @@ @manpages@ - [Files](command-ref/files.md) - [nix.conf](command-ref/conf-file.md) +- [Architecture](architecture/architecture.md) - [Glossary](glossary.md) - [Contributing](contributing/contributing.md) - [Hacking](contributing/hacking.md) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md new file mode 100644 index 000000000..24150fc2b --- /dev/null +++ b/doc/manual/src/architecture/architecture.md @@ -0,0 +1,77 @@ +# Architecture + +This chapter describes how Nix works. +It should help users understand why Nix behaves as it does, and it should help developers understand how to modify Nix and how to write similar tools. + +## Overview + +Nix consists of [hierarchical layers]. + +[hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers + +``` ++---------------------------------------------------------------+ +| Nix | +| [ commmand line interface ]------, | +| | | | +| evaluates | | +| | manages | +| V | | +| [ configuration language ] | | +| | | | +| +----------------------------|-------------------V----------+ | +| | store evaluates to | | +| | | | | +| | referenced by V builds | | +| | [ build input ] ---> [ build plan ] ---> [ build result ] | | +| | | | +| +-----------------------------------------------------------+ | ++---------------------------------------------------------------+ +``` + +At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. + +Below that is the [Nix language](../language/index.md), a [purely functional] configuration language. +It is used to compose expressions which ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. + +[purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming + +Command line interface and Nix language are what users interact with most. + +> **Note** +> The Nix language itself does not have a notion of *packages* or *configurations*. +> As far as we are concerned here, the inputs and results of a build plan are just data. + +Underlying these is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +It can also execute build plans to produce new data. + +A build plan is a series of *build tasks*. +Each build task has a special build input, which is used as *build instructions*. +The result of a build task can be input to another build task. + +> **Important** +> A build task in Nix is called [derivation](../glossary#gloss-derivation). + +``` ++----------------------------------------------------------------------------------+ +| store .............................................. | +| : build plan : | +| : : | +| [ build input ]---instructions-, : | +| : | : | +| : v : | +| [ build input ]--------->[ build task ]-instructions-, : | +| : | : | +| : v : | +| [ build input ]---instructions-, [ build task ]--->[ build result ] | +| : | ^ : | +| : v | : | +| [ build input ]--------->[ build task ]--------------' : | +| : ^ : | +| : | : | +| [ build input ]----------------' : | +| : : | +| :............................................: | ++----------------------------------------------------------------------------------+ +``` + From b5728ace5d5250a6d52df05802c9fc325153dc2c Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 23 Sep 2022 14:48:54 +0200 Subject: [PATCH 38/97] add articles --- doc/manual/src/architecture/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 24150fc2b..972ff2d58 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -36,7 +36,7 @@ It is used to compose expressions which ultimately evaluate to self-contained *b [purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming -Command line interface and Nix language are what users interact with most. +The command line interface and the Nix language are what users interact with most. > **Note** > The Nix language itself does not have a notion of *packages* or *configurations*. From 98447c1a7f1538f4d3132c0b79ad5b67e136af85 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 23 Sep 2022 14:49:47 +0200 Subject: [PATCH 39/97] clarify subject of sentence Co-authored-by: Bryan Honof --- doc/manual/src/architecture/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 972ff2d58..f113cbf4d 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -42,7 +42,7 @@ The command line interface and the Nix language are what users interact with mos > The Nix language itself does not have a notion of *packages* or *configurations*. > As far as we are concerned here, the inputs and results of a build plan are just data. -Underlying these is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +Underlying the command line interface and the Nix language is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. It can also execute build plans to produce new data. A build plan is a series of *build tasks*. From 3d716df7ce0639e61170ea1e18a370138dce8e5b Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Sat, 15 Oct 2022 23:46:32 +0200 Subject: [PATCH 40/97] make diagrams compatible with svgbob this will at some point enable rendering them nicely for the web --- doc/manual/src/architecture/architecture.md | 53 ++++++++++++--------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index f113cbf4d..0df4fbc68 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -11,20 +11,25 @@ Nix consists of [hierarchical layers]. ``` +---------------------------------------------------------------+ -| Nix | -| [ commmand line interface ]------, | +| Nix .-------------------------. | +| | commmand line interface |------. | +| '-------------------------' | | | | | | | evaluates | | | | manages | | V | | -| [ configuration language ] | | +| .-------------------------. | | +| | configuration language | | | +| '-------------------------' | | | | | | -| +----------------------------|-------------------V----------+ | -| | store evaluates to | | -| | | | | +| evaluates to | | +| | V | +| +----------------------------|------------------------------+ | +| | store | | | | | referenced by V builds | | -| | [ build input ] ---> [ build plan ] ---> [ build result ] | | -| | | | +| | .-------------. .------------. .--------------. | | +| | | build input |----->| build plan | ---->| build result | | | +| | '-------------' '------------' '--------------' | | | +-----------------------------------------------------------+ | +---------------------------------------------------------------+ ``` @@ -54,24 +59,28 @@ The result of a build task can be input to another build task. ``` +----------------------------------------------------------------------------------+ -| store .............................................. | +| store - - - - - - - - - - - - - - - - - - - - - - | | : build plan : | -| : : | -| [ build input ]---instructions-, : | -| : | : | +| .-------------. : : | +| | build input |---instructions-. : | +| '-------------' : | : | | : v : | -| [ build input ]--------->[ build task ]-instructions-, : | -| : | : | +| .-------------. : .------------. : | +| | build input |--------->| build task |-instructions-. : | +| '-------------' : '------------' | : | | : v : | -| [ build input ]---instructions-, [ build task ]--->[ build result ] | -| : | ^ : | -| : v | : | -| [ build input ]--------->[ build task ]--------------' : | +| .-------------. : .------------. : .--------------. | +| | build input |---instructions-. | build task |--->| build result | | +| '-------------' : | '------------' : '--------------' | +| : v ^ : | +| .-------------. : .------------. | : | +| | build input |--------->| build task |--------------' : | +| '-------------' : '------------' : | | : ^ : | -| : | : | -| [ build input ]----------------' : | -| : : | -| :............................................: | +| .-------------. : | : | +| | build input |----------------' : | +| '-------------' : : | +| :_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _: | +----------------------------------------------------------------------------------+ ``` From 9d20a056c82b129ab2584169e692c7f3e324fa29 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 01:36:17 +0100 Subject: [PATCH 41/97] remove external link the language has its own overview page where its properties are described in sufficient detail. --- doc/manual/src/architecture/architecture.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 0df4fbc68..4b0704894 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -36,10 +36,8 @@ Nix consists of [hierarchical layers]. At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. -Below that is the [Nix language](../language/index.md), a [purely functional] configuration language. -It is used to compose expressions which ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. - -[purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming +Below that is the [Nix language](../language/index.md), the configuration language for Nix. +Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. The command line interface and the Nix language are what users interact with most. From bcd298d39bffbb1a79ae5ce2c4eec8c45fa9f2a0 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 15 Nov 2022 17:57:40 +0100 Subject: [PATCH 42/97] libstore/derivation-goal: Elaborate a TODO for performance concern --- src/libstore/build/derivation-goal.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 00e375fe9..2f22dbcec 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -538,7 +538,8 @@ void DerivationGoal::inputsRealised() However, the impure derivations feature still relies on this fragile way of doing things, because its builds do not have a representation in the store, which is a usability problem - in itself */ + in itself. When implementing this logic entirely with lookups + make sure that they're cached. */ if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) { worker.store.computeFSClosure(*outPath, inputPaths); } From 9c90452f9d211ebac3657745283fae0f00e2bd71 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Sat, 26 Nov 2022 21:06:29 +0100 Subject: [PATCH 43/97] bump nixpkgs to 22.11 beta --- flake.lock | 8 ++++---- flake.nix | 14 +++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/flake.lock b/flake.lock index a66c9cb1b..be19cca6b 100644 --- a/flake.lock +++ b/flake.lock @@ -18,16 +18,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1657693803, - "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=", + "lastModified": 1669425120, + "narHash": "sha256-m/sEyGBDAq+Th4NVaPRhrJ5sljReCebYiQcbDlqp0ww=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2", + "rev": "e22d9c397e5e6d92771cc1534e7769f2167c2952", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.05-small", + "ref": "nixos-22.11-small", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index cc2a48d9c..e0e0e27fe 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "The purely functional package manager"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05-small"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11-small"; inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2"; inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; }; @@ -108,7 +108,7 @@ ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)]; buildDeps = - [ (curl.override { patchNetrcRegression = true; }) + [ curl bzip2 xz brotli editline openssl sqlite libarchive @@ -127,13 +127,9 @@ }); propagatedDeps = - [ ((boehmgc.override { + [ (boehmgc.override { enableLargeConfig = true; - }).overrideAttrs(o: { - patches = (o.patches or []) ++ [ - ./boehmgc-coroutine-sp-fallback.diff - ]; - })) + }) nlohmann_json ]; }; @@ -364,7 +360,7 @@ buildInputs = [ nix - (curl.override { patchNetrcRegression = true; }) + curl bzip2 xz pkgs.perl From ff62f6a84b6a845d11ea2cd4551bfb3536249755 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Fri, 2 Dec 2022 20:24:05 +0100 Subject: [PATCH 44/97] tests/fetchGitSubmodules: fix for newer Git --- tests/fetchGitSubmodules.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/fetchGitSubmodules.sh b/tests/fetchGitSubmodules.sh index 5f104355f..50da4cb97 100644 --- a/tests/fetchGitSubmodules.sh +++ b/tests/fetchGitSubmodules.sh @@ -14,6 +14,15 @@ subRepo=$TEST_ROOT/gitSubmodulesSub rm -rf ${rootRepo} ${subRepo} $TEST_HOME/.cache/nix +# Submodules can't be fetched locally by default, which can cause +# information leakage vulnerabilities, but for these tests our +# submodule is intentionally local and it's all trusted, so we +# disable this restriction. Setting it per repo is not sufficient, as +# the repo-local config does not apply to the commands run from +# outside the repos by Nix. +export XDG_CONFIG_HOME=$TEST_HOME/.config +git config --global protocol.file.allow always + initGitRepo() { git init $1 git -C $1 config user.email "foobar@example.com" From e0ab2069c975abf80db9fdca23f0c164df921829 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 5 Dec 2022 16:55:55 +0100 Subject: [PATCH 45/97] Consistent capitalisation --- src/nix/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 5dedf02c2..23fef7c68 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -111,7 +111,7 @@ struct CmdEval : MixJSON, InstallableCommand else if (raw) { stopProgressBar(); - std::cout << *state->coerceToString(noPos, *v, context, "While generating the eval command output"); + std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output"); } else if (json) { From 25e87c3c9131613385b71ffa5adae6e9e3644129 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Thu, 8 Dec 2022 12:59:20 +0100 Subject: [PATCH 46/97] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/e22d9c397e5e6d92771cc1534e7769f2167c2952' (2022-11-26) → 'github:NixOS/nixpkgs/04a75b2eecc0acf6239acf9dd04485ff8d14f425' (2022-12-08) NixOS 22.11 is now no longer beta. --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index be19cca6b..4490b5ead 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1669425120, - "narHash": "sha256-m/sEyGBDAq+Th4NVaPRhrJ5sljReCebYiQcbDlqp0ww=", + "lastModified": 1670461440, + "narHash": "sha256-jy1LB8HOMKGJEGXgzFRLDU1CBGL0/LlkolgnqIsF0D8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e22d9c397e5e6d92771cc1534e7769f2167c2952", + "rev": "04a75b2eecc0acf6239acf9dd04485ff8d14f425", "type": "github" }, "original": { From 4ed8bb1cb132a6671a51e96abbd8241fbe01d6ef Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 12 Dec 2022 14:29:02 +0100 Subject: [PATCH 47/97] suggestions from review --- doc/manual/src/architecture/architecture.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 4b0704894..3a7d1d6f5 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -15,14 +15,14 @@ Nix consists of [hierarchical layers]. | | commmand line interface |------. | | '-------------------------' | | | | | | -| evaluates | | +| calls | | | | manages | | V | | | .-------------------------. | | -| | configuration language | | | +| | language evaluator | | | | '-------------------------' | | | | | | -| evaluates to | | +| produces | | | | V | | +----------------------------|------------------------------+ | | | store | | | @@ -36,7 +36,7 @@ Nix consists of [hierarchical layers]. At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. -Below that is the [Nix language](../language/index.md), the configuration language for Nix. +Below that is the evaluator for the [Nix language](../language/index.md), the configuration language for Nix. Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. The command line interface and the Nix language are what users interact with most. From 09830ab829f57c20189afe0ee9e7721c274d98ee Mon Sep 17 00:00:00 2001 From: TIAN Yuanhao Date: Wed, 14 Dec 2022 03:03:12 +0000 Subject: [PATCH 48/97] Avoid poly_user_note_set twice f06f810 incorrectly introduces a boolean flip, resulting in a senseless poly_user_note_set even though the user comment has been set correctly. --- scripts/install-multi-user.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index ec82e0560..f149ea0d7 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -575,7 +575,7 @@ EOF # to extract _just_ the user's note, instead it is prefixed with # some plist junk. This was causing the user note to always be set, # even if there was no reason for it. - if ! poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then + if poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then row " Note" "Nix build user $coreid" else poly_user_note_set "$username" "Nix build user $coreid" From 97b2a336ff3c24c56235110f3c457b88f5a51f33 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Wed, 14 Dec 2022 22:23:58 +0000 Subject: [PATCH 49/97] configure.ac: don't clobber CFLAGS=/CXXFLAGS= and allow users to pass in custom flags Reported-by: 0n-s Bug: https://github.com/trofi/nix-guix-gentoo/issues/26 --- configure.ac | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure.ac b/configure.ac index c0e989d85..1b0d6fd27 100644 --- a/configure.ac +++ b/configure.ac @@ -41,8 +41,6 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')]) test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var -CFLAGS= -CXXFLAGS= AC_PROG_CC AC_PROG_CXX AC_PROG_CPP From 1c40182b12d5fd462c891b597e1a3f9b912502d5 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Mon, 19 Dec 2022 13:35:05 +0100 Subject: [PATCH 50/97] fixup: remove boehmgc patch --- boehmgc-coroutine-sp-fallback.diff | 77 ------------------------------ 1 file changed, 77 deletions(-) delete mode 100644 boehmgc-coroutine-sp-fallback.diff diff --git a/boehmgc-coroutine-sp-fallback.diff b/boehmgc-coroutine-sp-fallback.diff deleted file mode 100644 index 8fdafbecb..000000000 --- a/boehmgc-coroutine-sp-fallback.diff +++ /dev/null @@ -1,77 +0,0 @@ -diff --git a/darwin_stop_world.c b/darwin_stop_world.c -index 3dbaa3fb..36a1d1f7 100644 ---- a/darwin_stop_world.c -+++ b/darwin_stop_world.c -@@ -352,6 +352,7 @@ GC_INNER void GC_push_all_stacks(void) - int nthreads = 0; - word total_size = 0; - mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ; -+ size_t stack_limit; - if (!EXPECT(GC_thr_initialized, TRUE)) - GC_thr_init(); - -@@ -407,6 +408,19 @@ GC_INNER void GC_push_all_stacks(void) - GC_push_all_stack_sections(lo, hi, p->traced_stack_sect); - } - if (altstack_lo) { -+ // When a thread goes into a coroutine, we lose its original sp until -+ // control flow returns to the thread. -+ // While in the coroutine, the sp points outside the thread stack, -+ // so we can detect this and push the entire thread stack instead, -+ // as an approximation. -+ // We assume that the coroutine has similarly added its entire stack. -+ // This could be made accurate by cooperating with the application -+ // via new functions and/or callbacks. -+ stack_limit = pthread_get_stacksize_np(p->id); -+ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack -+ altstack_lo = altstack_hi - stack_limit; -+ } -+ - total_size += altstack_hi - altstack_lo; - GC_push_all_stack(altstack_lo, altstack_hi); - } -diff --git a/pthread_stop_world.c b/pthread_stop_world.c -index 4b2c429..1fb4c52 100644 ---- a/pthread_stop_world.c -+++ b/pthread_stop_world.c -@@ -673,6 +673,8 @@ GC_INNER void GC_push_all_stacks(void) - struct GC_traced_stack_sect_s *traced_stack_sect; - pthread_t self = pthread_self(); - word total_size = 0; -+ size_t stack_limit; -+ pthread_attr_t pattr; - - if (!EXPECT(GC_thr_initialized, TRUE)) - GC_thr_init(); -@@ -722,6 +724,31 @@ GC_INNER void GC_push_all_stacks(void) - hi = p->altstack + p->altstack_size; - /* FIXME: Need to scan the normal stack too, but how ? */ - /* FIXME: Assume stack grows down */ -+ } else { -+ if (pthread_getattr_np(p->id, &pattr)) { -+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!"); -+ } -+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) { -+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!"); -+ } -+ if (pthread_attr_destroy(&pattr)) { -+ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!"); -+ } -+ // When a thread goes into a coroutine, we lose its original sp until -+ // control flow returns to the thread. -+ // While in the coroutine, the sp points outside the thread stack, -+ // so we can detect this and push the entire thread stack instead, -+ // as an approximation. -+ // We assume that the coroutine has similarly added its entire stack. -+ // This could be made accurate by cooperating with the application -+ // via new functions and/or callbacks. -+ #ifndef STACK_GROWS_UP -+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack -+ lo = hi - stack_limit; -+ } -+ #else -+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix." -+ #endif - } - GC_push_all_stack_sections(lo, hi, traced_stack_sect); - # ifdef STACK_GROWS_UP From c162c90b432590e1dbdeb5f4361bf4b48de82bac Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 09:59:59 +0100 Subject: [PATCH 51/97] add more explanation to diagrams this is to help reading the diagrams, otherwise arrows and labels were reported as being ambiguous. --- doc/manual/src/architecture/architecture.md | 125 ++++++++++++-------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 3a7d1d6f5..2d1b26558 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -9,18 +9,29 @@ Nix consists of [hierarchical layers]. [hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers +The following [concept map] shows its main components (rectangles), the objects they operate on (rounded rectangles), and their interactions (connecting phrases): + +[concept map]: https://en.m.wikipedia.org/wiki/Concept_map + ``` -+---------------------------------------------------------------+ -| Nix .-------------------------. | -| | commmand line interface |------. | -| '-------------------------' | | -| | | | -| calls | | -| | manages | -| V | | -| .-------------------------. | | -| | language evaluator | | | -| '-------------------------' | | + + .----------------. + | Nix expression |----------. + '----------------' | + | passed to + | | ++----------|-------------------|--------------------------------+ +| Nix | V | +| | +-------------------------+ | +| | | commmand line interface |------. | +| | +-------------------------+ | | +| | | | | +| evaluated by calls manages | +| | | | | +| | V | | +| | +--------------------+ | | +| '-------->| language evaluator | | | +| +--------------------+ | | | | | | | produces | | | | V | @@ -28,57 +39,77 @@ Nix consists of [hierarchical layers]. | | store | | | | | referenced by V builds | | | | .-------------. .------------. .--------------. | | -| | | build input |----->| build plan | ---->| build result | | | +| | | build input |----->| build plan |----->| build result | | | | | '-------------' '------------' '--------------' | | -| +-----------------------------------------------------------+ | -+---------------------------------------------------------------+ +| +-------------------------------------------------|---------+ | ++---------------------------------------------------|-----------+ + | + represented as + | + V + .---------------. + | file | + '---------------' ``` -At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. +At the top is the [command line interface](../command-ref/command-ref.md) that drives the underlying layers. -Below that is the evaluator for the [Nix language](../language/index.md), the configuration language for Nix. -Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. +The [Nix language](../language/index.md) evaluator transforms Nix expressions into self-contained *build plans*, which are used to derive *build results* from referenced *build inputs*. -The command line interface and the Nix language are what users interact with most. +The command line interface and Nix expressions are what users deal with most. > **Note** > The Nix language itself does not have a notion of *packages* or *configurations*. > As far as we are concerned here, the inputs and results of a build plan are just data. -Underlying the command line interface and the Nix language is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. -It can also execute build plans to produce new data. +Underlying the command line interface and the Nix language evaluator is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +It can also execute build plans to produce new data, which are made available to the operating system as files. -A build plan is a series of *build tasks*. -Each build task has a special build input, which is used as *build instructions*. -The result of a build task can be input to another build task. +A build plan itself is a series of *build tasks*, together with their build inputs. > **Important** > A build task in Nix is called [derivation](../glossary#gloss-derivation). +Each build task has a special build input executed as *build instructions* in order to perform the build. +The result of a build task can be input to another build task. + +The following [data flow diagram] shows a build plan for illustration. +Build inputs used as instructions to a build task are marked accordingly: + +[data flow diagram]: https://en.m.wikipedia.org/wiki/Data-flow_diagram + ``` -+----------------------------------------------------------------------------------+ -| store - - - - - - - - - - - - - - - - - - - - - - | -| : build plan : | -| .-------------. : : | -| | build input |---instructions-. : | -| '-------------' : | : | -| : v : | -| .-------------. : .------------. : | -| | build input |--------->| build task |-instructions-. : | -| '-------------' : '------------' | : | -| : v : | -| .-------------. : .------------. : .--------------. | -| | build input |---instructions-. | build task |--->| build result | | -| '-------------' : | '------------' : '--------------' | -| : v ^ : | -| .-------------. : .------------. | : | -| | build input |--------->| build task |--------------' : | -| '-------------' : '------------' : | -| : ^ : | -| .-------------. : | : | -| | build input |----------------' : | -| '-------------' : : | -| :_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _: | -+----------------------------------------------------------------------------------+ ++--------------------------------------------------------------------+ +| build plan | +| | +| .-------------. | +| | build input |---------. | +| '-------------' | | +| instructions | +| | | +| v | +| .-------------. .----------. | +| | build input |-->( build task )-------. | +| '-------------' '----------' | | +| instructions | +| | | +| v | +| .-------------. .----------. .--------------. | +| | build input |---------. ( build task )--->| build result | | +| '-------------' | '----------' '--------------' | +| instructions ^ | +| | | | +| v | | +| .-------------. .----------. | | +| | build input |-->( build task )-------' | +| '-------------' '----------' | +| ^ | +| | | +| | | +| .-------------. | | +| | build input |---------' | +| '-------------' | +| | ++--------------------------------------------------------------------+ ``` From 7e31a991db9fbc7881e5f96a10eea0c030bc8f6e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 14:28:43 +0100 Subject: [PATCH 52/97] make relative links explicit --- doc/manual/src/glossary.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index b13709f8a..aa775b163 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -3,18 +3,18 @@ - [derivation]{#gloss-derivation}\ A description of a build task. The result of a derivation is a store object. Derivations are typically specified in Nix expressions - using the [`derivation` primitive](language/derivations.md). These are + using the [`derivation` primitive](./language/derivations.md). These are translated into low-level *store derivations* (implicitly by `nix-env` and `nix-build`, or explicitly by `nix-instantiate`). - [content-addressed derivation]{#gloss-content-addressed-derivation}\ A derivation which has the - [`__contentAddressed`](language/advanced-attributes.md#adv-attr-__contentAddressed) + [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed) attribute set to `true`. - [fixed-output derivation]{#gloss-fixed-output-derivation}\ A derivation which includes the - [`outputHash`](language/advanced-attributes.md#adv-attr-outputHash) attribute. + [`outputHash`](./language/advanced-attributes.md#adv-attr-outputHash) attribute. - [store]{#gloss-store}\ The location in the file system where store objects live. Typically @@ -79,7 +79,7 @@ - [substituter]{#gloss-substituter}\ A *substituter* is an additional store from which Nix will copy store objects it doesn't have. For details, see the - [`substituters` option](command-ref/conf-file.html#conf-substituters). + [`substituters` option](./command-ref/conf-file.md#conf-substituters). - [purity]{#gloss-purity}\ The assumption that equal Nix derivations when run always produce @@ -139,7 +139,7 @@ An automatically generated store object that consists of a set of symlinks to “active” applications, i.e., other store paths. These are generated automatically by - [`nix-env`](command-ref/nix-env.md). See *profiles*. + [`nix-env`](./command-ref/nix-env.md). See *profiles*. - [profile]{#gloss-profile}\ A symlink to the current *user environment* of a user, e.g., @@ -150,7 +150,9 @@ store. It can contain regular files, directories and symbolic links. NARs are generated and unpacked using `nix-store --dump` and `nix-store --restore`. + - [`∅`]{#gloss-emtpy-set}\ The empty set symbol. In the context of profile history, this denotes a package is not present in a particular version of the profile. + - [`ε`]{#gloss-epsilon}\ The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. From 1437582ccd3c4af51f34e43a77df5c8622e24d6c Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 20 Dec 2022 16:29:32 +0100 Subject: [PATCH 53/97] doc/book.toml: Improve config (#7300) * doc/book.toml: Improve config - `title` value will be added to the HTML - here - `git-repository-url` adds a link to the GitHub repo in the top right corner - `edit-url-template` adds an edit link, inviting contributions Co-authored-by: Valentin Gagarin --- doc/manual/book.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/manual/book.toml b/doc/manual/book.toml index 5f78a7614..46ced7ff7 100644 --- a/doc/manual/book.toml +++ b/doc/manual/book.toml @@ -1,6 +1,11 @@ +[book] +title = "Nix Reference Manual" + [output.html] additional-css = ["custom.css"] additional-js = ["redirects.js"] +edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}" +git-repository-url = "https://github.com/NixOS/nix" [preprocessor.anchors] renderers = ["html"] From 0251d44cc2e991608ea39f2274ad2d197c668468 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 15 Dec 2022 20:17:08 -0500 Subject: [PATCH 54/97] Make `./mk/run-test.sh` work by itself; add `mk/debug-test.sh` First, logic is consolidated in the shell script instead of being spread between them and makefiles. That makes understanding what is going on a little easier. This would not be super interesting by itself, but it gives us a way to debug tests more easily. *That* in turn I hope is much more compelling. See the updated manual for details. Co-authored-by: Valentin Gagarin Co-authored-by: Eelco Dolstra Co-authored-by: Robert Hensing --- doc/manual/src/contributing/hacking.md | 75 +++++++++++++++++++++++++- mk/common-test.sh | 11 ++++ mk/debug-test.sh | 11 ++++ mk/{run_test.sh => run-test.sh} | 17 +++--- mk/tests.mk | 6 ++- tests/local.mk | 2 - 6 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 mk/common-test.sh create mode 100755 mk/debug-test.sh rename mk/{run_test.sh => run-test.sh} (79%) diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md index 9f7d5057b..b75d2554c 100644 --- a/doc/manual/src/contributing/hacking.md +++ b/doc/manual/src/contributing/hacking.md @@ -99,8 +99,79 @@ You can run the whole testsuite with `make check`, or the tests for a specific c ### Functional tests The functional tests reside under the `tests` directory and are listed in `tests/local.mk`. -The whole testsuite can be run with `make install && make installcheck`. -Individual tests can be run with `make tests/{testName}.sh.test`. +Each test is a bash script. + +The whole testsuite can be run with: + +```shell-session +$ make install && make installcheck +ran test tests/foo.sh... [PASS] +ran test tests/bar.sh... [PASS] +... +``` + +Individual tests can be run with `make`: + +```shell-session +$ make tests/${testName}.sh.test +ran test tests/${testName}.sh... [PASS] +``` + +or without `make`: + +```shell-session +$ ./mk/run-test.sh tests/${testName}.sh +ran test tests/${testName}.sh... [PASS] +``` + +To see the complet eoutput, one can also run: + +```shell-session +$ ./mk/debug-test.sh tests/${testName}.sh ++ foo +output from foo ++ bar +output from bar +... +``` + +The test script will be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. + +#### Debugging failing functional tests + +When a functional test fails, it usually does so somewhere in the middle of the script. + +To figure out what's wrong, it is convenient to run the test regularly up to the failing `nix` command, and then run that command with a debugger like GDB. + +For example, if the script looks like: + +```bash +foo +nix blah blub +bar +``` +one would could edit it like so: + +```diff + foo +-nix blah blub ++gdb --args nix blah blub + bar +``` + +Then, when one runs the script with `./mk/debug-test.sh`, it will drop them into GDB once the script reaches that point: + +```shell-session +$ ./mk/debug-test.sh tests/${testName}.sh +... ++ gdb blash blub +GNU gdb (GDB) 12.1 +... +(gdb) +``` + +One can debug the Nix invocation in all the usual ways. +(For exampling running `run` will run the Nix invocation.) ### Integration tests diff --git a/mk/common-test.sh b/mk/common-test.sh new file mode 100644 index 000000000..0a2e4c1c2 --- /dev/null +++ b/mk/common-test.sh @@ -0,0 +1,11 @@ +TESTS_ENVIRONMENT=("TEST_NAME=${test%.*}" 'NIX_REMOTE=') + +: ${BASH:=/usr/bin/env bash} + +init_test () { + cd tests && env "${TESTS_ENVIRONMENT[@]}" $BASH -e init.sh 2>/dev/null > /dev/null +} + +run_test_proper () { + cd $(dirname $test) && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test) +} diff --git a/mk/debug-test.sh b/mk/debug-test.sh new file mode 100755 index 000000000..6299e68a0 --- /dev/null +++ b/mk/debug-test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -eu + +test=$1 + +dir="$(dirname "${BASH_SOURCE[0]}")" +source "$dir/common-test.sh" + +(init_test) +run_test_proper diff --git a/mk/run_test.sh b/mk/run-test.sh similarity index 79% rename from mk/run_test.sh rename to mk/run-test.sh index 7e95df2ac..219c8577f 100755 --- a/mk/run_test.sh +++ b/mk/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -u @@ -7,7 +7,12 @@ green="" yellow="" normal="" -post_run_msg="ran test $1..." +test=$1 + +dir="$(dirname "${BASH_SOURCE[0]}")" +source "$dir/common-test.sh" + +post_run_msg="ran test $test..." if [ -t 1 ]; then red="" green="" @@ -16,12 +21,12 @@ if [ -t 1 ]; then fi run_test () { - (cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null) - log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)" + (init_test 2>/dev/null > /dev/null) + log="$(run_test_proper 2>&1)" status=$? } -run_test "$1" +run_test # Hack: Retry the test if it fails with “unexpected EOF reading a line” as these # appear randomly without anyone knowing why. @@ -32,7 +37,7 @@ if [[ $status -ne 0 && $status -ne 99 && \ ]]; then echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)" echo "$log" | sed 's/^/ /' - run_test "$1" + run_test fi if [ $status -eq 0 ]; then diff --git a/mk/tests.mk b/mk/tests.mk index a2e30a378..3ebbd86e3 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -8,7 +8,11 @@ define run-install-test .PHONY: $1.test $1.test: $1 $(test-deps) - @env TEST_NAME=$(basename $1) TESTS_ENVIRONMENT="$(tests-environment)" mk/run_test.sh $1 < /dev/null + @env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null + + .PHONY: $1.test-debug + $1.test-debug: $1 $(test-deps) + @env BASH=$(bash) $(bash) mk/debug-test.sh $1 < /dev/null endef diff --git a/tests/local.mk b/tests/local.mk index 2f7f76261..bba6ad9c9 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -121,8 +121,6 @@ endif install-tests += $(foreach x, $(nix_tests), tests/$(x)) -tests-environment = NIX_REMOTE= $(bash) -e - clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix test-deps += tests/common.sh tests/config.nix tests/ca/config.nix From bc8ab21c5acbc9b94199be97dc66722cc5cf2252 Mon Sep 17 00:00:00 2001 From: mupdt <25388474+mupdt@users.noreply.github.com> Date: Wed, 21 Dec 2022 04:50:40 -0500 Subject: [PATCH 55/97] [PDT] TDE-3114: prevent a race-condition when creating the S3 cache --- src/libstore/nar-info-disk-cache.cc | 42 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc index f4ea739b0..3e0689534 100644 --- a/src/libstore/nar-info-disk-cache.cc +++ b/src/libstore/nar-info-disk-cache.cc @@ -166,16 +166,37 @@ public: return i->second; } + std::optional queryCacheRaw(State & state, const std::string & uri) + { + auto i = state.caches.find(uri); + if (i == state.caches.end()) { + auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl)); + if (!queryCache.next()) + return std::nullopt; + state.caches.emplace(uri, + Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)}); + } + return getCache(state, uri); + } + void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override { retrySQLite([&]() { auto state(_state.lock()); + SQLiteTxn txn(state->db); - // FIXME: race + // To avoid the race, we have to check if maybe someone hasn't yet created + // the cache for this URI in the meantime. + auto cache(queryCacheRaw(*state, uri)); + + if (cache) + return; state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec(); assert(sqlite3_changes(state->db) == 1); state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority}; + + txn.commit(); }); } @@ -183,21 +204,12 @@ public: { return retrySQLite>([&]() -> std::optional { auto state(_state.lock()); - - auto i = state->caches.find(uri); - if (i == state->caches.end()) { - auto queryCache(state->queryCache.use()(uri)(time(0) - cacheInfoTtl)); - if (!queryCache.next()) - return std::nullopt; - state->caches.emplace(uri, - Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)}); - } - - auto & cache(getCache(*state, uri)); - + auto cache(queryCacheRaw(*state, uri)); + if (!cache) + return std::nullopt; return CacheInfo { - .wantMassQuery = cache.wantMassQuery, - .priority = cache.priority + .wantMassQuery = cache->wantMassQuery, + .priority = cache->priority }; }); } From 62f4f883a7f63380be578ebb6167641ec111b59e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:11:31 +0100 Subject: [PATCH 56/97] define "store derivation" --- doc/manual/src/glossary.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index aa775b163..117b43a88 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -7,6 +7,18 @@ translated into low-level *store derivations* (implicitly by `nix-env` and `nix-build`, or explicitly by `nix-instantiate`). + [derivation]: #gloss-derivation + + - [store derivation]{#gloss-store-derivation}\ + A [derivation] represented as a `.drv` file in the [store]. + It has a [store path], like any [store object]. + + Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv` + + See [`nix show-derivation`](./command-ref/new-cli/nix3-show-derivation.md) (experimental) for displaying the contents of store derivations. + + [store derivation]: #gloss-store-derivation + - [content-addressed derivation]{#gloss-content-addressed-derivation}\ A derivation which has the [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed) @@ -34,6 +46,8 @@ directory on another machine, accessed via `ssh` or served by the `nix-serve` Perl script. + [store]: #gloss-store + - [chroot store]{#gloss-chroot-store}\ A local store whose canonical path is anything other than `/nix/store`. @@ -49,6 +63,10 @@ The location in the file system of a store object, i.e., an immediate child of the Nix store directory. + Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1` + + [store path]: #gloss-store-path + - [store object]{#gloss-store-object}\ A file that is an immediate child of the Nix store directory. These can be regular files, but also entire directory trees. Store objects @@ -56,6 +74,8 @@ derivation outputs (objects produced by running a build task), or derivations (files describing a build task). + [store object]: #gloss-store-object + - [input-addressed store object]{#gloss-input-addressed-store-object}\ A store object produced by building a non-[content-addressed](#gloss-content-addressed-derivation), From 3a66d82e1da1302586055a70903de2f9a7425087 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:26:05 +0100 Subject: [PATCH 57/97] update description of "store derivation" in installables section a store derivation is not a store path itself, it has a store path. --- doc/manual/src/glossary.md | 4 +++- src/nix/nix.md | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 117b43a88..0fcc2b07b 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -144,7 +144,9 @@ references `R` then `R` is also in the closure of `P`. - [output path]{#gloss-output-path}\ - A store path produced by a derivation. + A [store path] produced by a [derivation]. + + [output path]: #gloss-output-path - [deriver]{#gloss-deriver}\ The deriver of an *output path* is the store diff --git a/src/nix/nix.md b/src/nix/nix.md index 723d3c87e..43de6869b 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -115,12 +115,12 @@ the Nix store. Here are the recognised types of installables: * **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv` - Store derivations are store paths with extension `.drv` and are a - low-level representation of a build-time dependency graph used - internally by Nix. By default, if you pass a store derivation to a - `nix` subcommand, it will operate on the *output paths* of the - derivation. For example, `nix path-info` prints information about - the output paths: + Store derivations are a low-level, internal representation of build tasks in Nix, and have store paths with extension `.drv`. + By default, if you pass the path to a store derivation to a `nix` subcommand, the command will operate on the [output path]s of the derivation. + + [output path]: ../../glossary.md#gloss-output-path + + For example, `nix path-info` prints information about the output paths: ```console # nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv From 7797661a70bcd6a7caf756964785ea7727ed1be0 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:37:40 +0100 Subject: [PATCH 58/97] link "store derivation" to glossary definition --- doc/manual/src/command-ref/nix-build.md | 4 +++- doc/manual/src/command-ref/nix-copy-closure.md | 4 +++- doc/manual/src/command-ref/nix-env.md | 8 +++++--- doc/manual/src/command-ref/nix-instantiate.md | 10 +++++----- doc/manual/src/command-ref/nix-store.md | 14 ++++++++------ src/libcmd/installables.cc | 2 +- src/nix/nix.md | 4 +++- src/nix/path-info.md | 4 +++- src/nix/show-derivation.md | 6 ++++-- src/nix/store-copy-log.md | 4 +++- 10 files changed, 38 insertions(+), 22 deletions(-) diff --git a/doc/manual/src/command-ref/nix-build.md b/doc/manual/src/command-ref/nix-build.md index 3a47feaae..937b046b8 100644 --- a/doc/manual/src/command-ref/nix-build.md +++ b/doc/manual/src/command-ref/nix-build.md @@ -37,10 +37,12 @@ directory containing at least a file named `default.nix`. `nix-build` is essentially a wrapper around [`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix -expression to a low-level store derivation) and [`nix-store +expression to a low-level [store derivation]) and [`nix-store --realise`](nix-store.md#operation---realise) (to build the store derivation). +[store derivation]: ../glossary.md#gloss-store-derivation + > **Warning** > > The result of the build is automatically registered as a root of the diff --git a/doc/manual/src/command-ref/nix-copy-closure.md b/doc/manual/src/command-ref/nix-copy-closure.md index 9a29030bd..83e8a2936 100644 --- a/doc/manual/src/command-ref/nix-copy-closure.md +++ b/doc/manual/src/command-ref/nix-copy-closure.md @@ -47,7 +47,9 @@ authentication, you can avoid typing the passphrase with `ssh-agent`. Enable compression of the SSH connection. - `--include-outputs`\ - Also copy the outputs of store derivations included in the closure. + Also copy the outputs of [store derivation]s included in the closure. + + [store derivation]: ../../glossary.md#gloss-store-derivation - `--use-substitutes` / `-s`\ Attempt to download missing paths on the target machine using Nix’s diff --git a/doc/manual/src/command-ref/nix-env.md b/doc/manual/src/command-ref/nix-env.md index a5df35d77..f4fa5b50c 100644 --- a/doc/manual/src/command-ref/nix-env.md +++ b/doc/manual/src/command-ref/nix-env.md @@ -205,10 +205,12 @@ a number of possible ways: unambiguous way, which is necessary if there are multiple derivations with the same name. - - If *args* are store derivations, then these are + - If *args* are [store derivation]s, then these are [realised](nix-store.md#operation---realise), and the resulting output paths are installed. + [store derivation]: ../glossary.md#gloss-store-derivation + - If *args* are store paths that are not store derivations, then these are [realised](nix-store.md#operation---realise) and installed. @@ -280,7 +282,7 @@ To copy the store path with symbolic name `gcc` from another profile: $ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc ``` -To install a specific store derivation (typically created by +To install a specific [store derivation] (typically created by `nix-instantiate`): ```console @@ -665,7 +667,7 @@ derivation is shown unless `--no-name` is specified. Print the `system` attribute of the derivation. - `--drv-path`\ - Print the path of the store derivation. + Print the path of the [store derivation]. - `--out-path`\ Print the output path of the derivation. diff --git a/doc/manual/src/command-ref/nix-instantiate.md b/doc/manual/src/command-ref/nix-instantiate.md index 8f143729e..432fb2608 100644 --- a/doc/manual/src/command-ref/nix-instantiate.md +++ b/doc/manual/src/command-ref/nix-instantiate.md @@ -17,13 +17,14 @@ # Description -The command `nix-instantiate` generates [store -derivations](../glossary.md) from (high-level) Nix expressions. It -evaluates the Nix expressions in each of *files* (which defaults to +The command `nix-instantiate` produces [store derivation]s from (high-level) Nix expressions. +It evaluates the Nix expressions in each of *files* (which defaults to *./default.nix*). Each top-level expression should evaluate to a derivation, a list of derivations, or a set of derivations. The paths of the resulting store derivations are printed on standard output. +[store derivation]: ../glossary.md#gloss-store-derivation + If *files* is the character `-`, then a Nix expression will be read from standard input. @@ -79,8 +80,7 @@ standard input. # Examples -Instantiating store derivations from a Nix expression, and building them -using `nix-store`: +Instantiate [store derivation]s from a Nix expression, and build them using `nix-store`: ```console $ nix-instantiate test.nix (instantiate) diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index b712a7463..acf29e4aa 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -137,8 +137,10 @@ or. ## Examples -This operation is typically used to build store derivations produced by -[`nix-instantiate`](nix-instantiate.md): +This operation is typically used to build [store derivation]s produced by +[`nix-instantiate`](./nix-instantiate.md): + +[store derivation]: ../glossary.md#gloss-store-derivation ```console $ nix-store -r $(nix-instantiate ./test.nix) @@ -298,7 +300,7 @@ symlink. ## Common query options - `--use-output`; `-u`\ - For each argument to the query that is a store derivation, apply the + For each argument to the query that is a [store derivation], apply the query to the output path of the derivation instead. - `--force-realise`; `-f`\ @@ -318,7 +320,7 @@ symlink. This query has one option: - `--include-outputs` - Also include the existing output paths of store derivations, + Also include the existing output paths of [store derivation]s, and their closures. This query can be used to implement various kinds of deployment. A @@ -372,12 +374,12 @@ symlink. Prints the references graph of the store paths *paths* in the [GraphML](http://graphml.graphdrawing.org/) file format. This can be used to visualise dependency graphs. To obtain a build-time - dependency graph, apply this to a store derivation. To obtain a + dependency graph, apply this to a [store derivation]. To obtain a runtime dependency graph, apply it to an output path. - `--binding` *name*; `-b` *name*\ Prints the value of the attribute *name* (i.e., environment - variable) of the store derivations *paths*. It is an error for a + variable) of the [store derivation]s *paths*. It is an error for a derivation to not have the specified attribute. - `--hash`\ diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 5cdd3e12c..d2600ca91 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -168,7 +168,7 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) addFlag({ .longName = "derivation", - .description = "Operate on the store derivation rather than its outputs.", + .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.", .category = installablesCategory, .handler = {&operateOn, OperateOn::Derivation}, }); diff --git a/src/nix/nix.md b/src/nix/nix.md index 43de6869b..9641cccb2 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -202,9 +202,11 @@ operate are determined as follows: a command like `nix shell nixpkgs#libxml2` will provide only those two outputs by default. - Note that a store derivation (given by `.drv` file store path) doesn't have + Note that a [store derivation] (given by its `.drv` file store path) doesn't have any attributes like `meta`, and thus this case doesn't apply to it. + [store derivation]: ../../glossary.md#gloss-store-derivation + * Otherwise, Nix will use all outputs of the derivation. # Nix stores diff --git a/src/nix/path-info.md b/src/nix/path-info.md index 7a1714ba4..b30898ac0 100644 --- a/src/nix/path-info.md +++ b/src/nix/path-info.md @@ -68,7 +68,9 @@ R""( ] ``` -* Print the path of the store derivation produced by `nixpkgs#hello`: +* Print the path of the [store derivation] produced by `nixpkgs#hello`: + + [store derivation]: ../../glossary.md#gloss-store-derivation ```console # nix path-info --derivation nixpkgs#hello diff --git a/src/nix/show-derivation.md b/src/nix/show-derivation.md index aa863899c..2cd93aa62 100644 --- a/src/nix/show-derivation.md +++ b/src/nix/show-derivation.md @@ -2,9 +2,11 @@ R""( # Examples -* Show the store derivation that results from evaluating the Hello +* Show the [store derivation] that results from evaluating the Hello package: + [store derivation]: ../../glossary.md#gloss-store-derivation + ```console # nix show-derivation nixpkgs#hello { @@ -37,7 +39,7 @@ R""( # Description This command prints on standard output a JSON representation of the -store derivations to which *installables* evaluate. Store derivations +[store derivation]s to which *installables* evaluate. Store derivations are used internally by Nix. They are store paths with extension `.drv` that represent the build-time dependency graph to which a Nix expression evaluates. diff --git a/src/nix/store-copy-log.md b/src/nix/store-copy-log.md index 19ae57079..0937250f2 100644 --- a/src/nix/store-copy-log.md +++ b/src/nix/store-copy-log.md @@ -18,7 +18,9 @@ R""( (The flag `--substituters ''` avoids querying `https://cache.nixos.org` for the log.) -* To copy the log for a specific store derivation via SSH: +* To copy the log for a specific [store derivation] via SSH: + + [store derivation]: ../../glossary.md#gloss-store-derivation ```console # nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv From a33e45b60be5d8dc73bd800defee364d90a8d738 Mon Sep 17 00:00:00 2001 From: mupdt <25388474+mupdt@users.noreply.github.com> Date: Wed, 21 Dec 2022 07:01:57 -0500 Subject: [PATCH 59/97] primops `storeDir` test uses `settings.nixStore` --- src/libexpr/tests/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 49fbc5e98..bcdc7086b 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -604,7 +604,7 @@ namespace nix { TEST_F(PrimOpTest, storeDir) { auto v = eval("builtins.storeDir"); - ASSERT_THAT(v, IsStringEq("/nix/store")); + ASSERT_THAT(v, IsStringEq(settings.nixStore)); } TEST_F(PrimOpTest, nixVersion) { From d034ed1891e66d63f1eae90444a516a931ebadc2 Mon Sep 17 00:00:00 2001 From: rski Date: Fri, 23 Dec 2022 02:33:32 +0200 Subject: [PATCH 60/97] src/libstore: Print the reason opening the DB failed Without this, the error is lost, and it makes for a hard to debug situation. Also remove some of the busyness inside the sqlite_open_v2 args. The errcode returned is not the extended one. The only way to make open return an extended code, would be to add SQLITE_OPEN_EXRESCODE to the flags. In the future it might be worth making this change, which would also simplify the existing SQLiteError code. --- src/libstore/sqlite.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 6c350888f..353dff9fa 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -47,9 +47,13 @@ SQLite::SQLite(const Path & path, bool create) // `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem // for Linux (WSL) where useSQLiteWAL should be false by default. const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile"; - if (sqlite3_open_v2(path.c_str(), &db, - SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), vfs) != SQLITE_OK) - throw Error("cannot open SQLite database '%s'", path); + int flags = SQLITE_OPEN_READWRITE; + if (create) flags |= SQLITE_OPEN_CREATE; + int ret = sqlite3_open_v2(path.c_str(), &db, flags, vfs); + if (ret != SQLITE_OK) { + const char * err = sqlite3_errstr(ret); + throw Error("cannot open SQLite database '%s': %s", path, err); + } if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) SQLiteError::throw_(db, "setting timeout"); From a3a0e414c2d3b06e23b3ad83a2bc90ad09376090 Mon Sep 17 00:00:00 2001 From: Akhil Date: Fri, 23 Dec 2022 14:06:51 +0530 Subject: [PATCH 61/97] Deletes build users and group --- doc/manual/src/installation/installing-binary.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index a9378681d..53fdbe31a 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -120,10 +120,10 @@ sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defe Remove build users and their group: ```console -for i in $(seq 30001 30032); do - sudo userdel $i +for i in $(seq 1 32); do + sudo userdel nixbld$i done -sudo groupdel 30000 +sudo groupdel nixbld ``` There may also be references to Nix in From 64c60f7241217cf4ba652f6502935ac7d523fb55 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 23 Dec 2022 15:32:54 +0100 Subject: [PATCH 62/97] Fix CanonPath::dirOf() returning a string_view of a temporary https://hydra.nixos.org/build/202837872 --- src/libutil/canon-path.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh index c5e7f0596..9d5984584 100644 --- a/src/libutil/canon-path.hh +++ b/src/libutil/canon-path.hh @@ -110,7 +110,7 @@ public: std::optional dirOf() const { if (isRoot()) return std::nullopt; - return path.substr(0, path.rfind('/')); + return ((std::string_view) path).substr(0, path.rfind('/')); } std::optional baseName() const From c164d304f3cd6c5e536e33435084c030f018c2ab Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 23 Dec 2022 16:28:26 +0100 Subject: [PATCH 63/97] nix develop: Set personality This makes 'nix develop' set the Linux personality in the same way that the actual build does, allowing a command like 'nix develop nix#devShells.i686-linux.default' on x86_64-linux to work correctly. --- doc/manual/src/release-notes/rl-next.md | 6 +++ src/libstore/build/derivation-goal.cc | 1 - src/libstore/build/local-derivation-goal.cc | 31 +-------------- src/libstore/build/personality.cc | 44 +++++++++++++++++++++ src/libstore/build/personality.hh | 11 ++++++ src/nix/develop.cc | 10 ++++- src/nix/run.cc | 13 +++++- src/nix/run.hh | 3 +- 8 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 src/libstore/build/personality.cc create mode 100644 src/libstore/build/personality.hh diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 6c169bd09..906b048f1 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -25,3 +25,9 @@ $ nix-build glibc^dev` ``` does already. + +* On Linux, `nix develop` now sets the + [*personality*](https://man7.org/linux/man-pages/man2/personality.2.html) + for the development shell in the same way as the actual build of the + derivation. This makes shells for `i686-linux` derivations work + correctly on `x86_64-linux`. diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index d3b995a4f..173058d1b 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index dccd096ec..9d869d513 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -15,6 +15,7 @@ #include "callback.hh" #include "json-utils.hh" #include "cgroup.hh" +#include "personality.hh" #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include #include @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -1964,33 +1963,7 @@ void LocalDerivationGoal::runChild() /* Close all other file descriptors. */ closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}); -#if __linux__ - /* Change the personality to 32-bit if we're doing an - i686-linux build on an x86_64-linux machine. */ - struct utsname utsbuf; - uname(&utsbuf); - if ((drv->platform == "i686-linux" - && (settings.thisSystem == "x86_64-linux" - || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) - || drv->platform == "armv7l-linux" - || drv->platform == "armv6l-linux") - { - if (personality(PER_LINUX32) == -1) - throw SysError("cannot set 32-bit personality"); - } - - /* Impersonate a Linux 2.6 machine to get some determinism in - builds that depend on the kernel version. */ - if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) { - int cur = personality(0xffffffff); - if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); - } - - /* Disable address space randomization for improved - determinism. */ - int cur = personality(0xffffffff); - if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); -#endif + setPersonality(drv->platform); /* Disable core dumps by default. */ struct rlimit limit = { 0, RLIM_INFINITY }; diff --git a/src/libstore/build/personality.cc b/src/libstore/build/personality.cc new file mode 100644 index 000000000..4ad477869 --- /dev/null +++ b/src/libstore/build/personality.cc @@ -0,0 +1,44 @@ +#include "personality.hh" +#include "globals.hh" + +#if __linux__ +#include +#include +#endif + +#include + +namespace nix { + +void setPersonality(std::string_view system) +{ +#if __linux__ + /* Change the personality to 32-bit if we're doing an + i686-linux build on an x86_64-linux machine. */ + struct utsname utsbuf; + uname(&utsbuf); + if ((system == "i686-linux" + && (std::string_view(SYSTEM) == "x86_64-linux" + || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) + || system == "armv7l-linux" + || system == "armv6l-linux") + { + if (personality(PER_LINUX32) == -1) + throw SysError("cannot set 32-bit personality"); + } + + /* Impersonate a Linux 2.6 machine to get some determinism in + builds that depend on the kernel version. */ + if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) { + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); + } + + /* Disable address space randomization for improved + determinism. */ + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); +#endif +} + +} diff --git a/src/libstore/build/personality.hh b/src/libstore/build/personality.hh new file mode 100644 index 000000000..30e4f4062 --- /dev/null +++ b/src/libstore/build/personality.hh @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace nix { + +void setPersonality(std::string_view system); + +} + + diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 6c3a9c6c6..1d90d1dac 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -164,6 +164,14 @@ struct BuildEnvironment { return vars == other.vars && bashFunctions == other.bashFunctions; } + + std::string getSystem() const + { + if (auto v = get(vars, "system")) + return getString(*v); + else + return settings.thisSystem; + } }; const static std::string getEnvSh = @@ -570,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment } } - runProgramInStore(store, shell, args); + runProgramInStore(store, shell, args, buildEnvironment.getSystem()); } }; diff --git a/src/nix/run.cc b/src/nix/run.cc index 45d2dfd0d..6fca68047 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -9,6 +9,7 @@ #include "fs-accessor.hh" #include "progress-bar.hh" #include "eval.hh" +#include "build/personality.hh" #if __linux__ #include @@ -24,7 +25,8 @@ namespace nix { void runProgramInStore(ref store, const std::string & program, - const Strings & args) + const Strings & args, + std::optional system) { stopProgressBar(); @@ -44,7 +46,7 @@ void runProgramInStore(ref store, throw Error("store '%s' is not a local store so it does not support command execution", store->getUri()); if (store->storeDir != store2->getRealStoreDir()) { - Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program }; + Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program }; for (auto & arg : args) helperArgs.push_back(arg); execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data()); @@ -52,6 +54,9 @@ void runProgramInStore(ref store, throw SysError("could not execute chroot helper"); } + if (system) + setPersonality(*system); + execvp(program.c_str(), stringsToCharPtrs(args).data()); throw SysError("unable to execute '%s'", program); @@ -199,6 +204,7 @@ void chrootHelper(int argc, char * * argv) int p = 1; std::string storeDir = argv[p++]; std::string realStoreDir = argv[p++]; + std::string system = argv[p++]; std::string cmd = argv[p++]; Strings args; while (p < argc) @@ -262,6 +268,9 @@ void chrootHelper(int argc, char * * argv) writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1)); writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1)); + if (system != "") + setPersonality(system); + execvp(cmd.c_str(), stringsToCharPtrs(args).data()); throw SysError("unable to exec '%s'", cmd); diff --git a/src/nix/run.hh b/src/nix/run.hh index 6180a87dd..fed360158 100644 --- a/src/nix/run.hh +++ b/src/nix/run.hh @@ -6,6 +6,7 @@ namespace nix { void runProgramInStore(ref store, const std::string & program, - const Strings & args); + const Strings & args, + std::optional system = std::nullopt); } From efbd1d15c64a10068bdb8aed92b408a05f3b69a8 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 23 Dec 2022 08:59:36 -0800 Subject: [PATCH 64/97] Apply suggestions from code review Co-authored-by: Valentin Gagarin --- doc/manual/src/contributing/hacking.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md index b75d2554c..c9da1962f 100644 --- a/doc/manual/src/contributing/hacking.md +++ b/doc/manual/src/contributing/hacking.md @@ -101,7 +101,7 @@ You can run the whole testsuite with `make check`, or the tests for a specific c The functional tests reside under the `tests` directory and are listed in `tests/local.mk`. Each test is a bash script. -The whole testsuite can be run with: +The whole test suite can be run with: ```shell-session $ make install && make installcheck @@ -124,7 +124,7 @@ $ ./mk/run-test.sh tests/${testName}.sh ran test tests/${testName}.sh... [PASS] ``` -To see the complet eoutput, one can also run: +To see the complete output, one can also run: ```shell-session $ ./mk/debug-test.sh tests/${testName}.sh @@ -135,7 +135,7 @@ output from bar ... ``` -The test script will be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. +The test script will then be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. #### Debugging failing functional tests @@ -150,7 +150,7 @@ foo nix blah blub bar ``` -one would could edit it like so: +edit it like so: ```diff foo @@ -159,7 +159,7 @@ one would could edit it like so: bar ``` -Then, when one runs the script with `./mk/debug-test.sh`, it will drop them into GDB once the script reaches that point: +Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point: ```shell-session $ ./mk/debug-test.sh tests/${testName}.sh @@ -171,7 +171,7 @@ GNU gdb (GDB) 12.1 ``` One can debug the Nix invocation in all the usual ways. -(For exampling running `run` will run the Nix invocation.) +For example, enter `run` to start the Nix invocation. ### Integration tests From bcc09902725350c8f77d4c9051c5c575e82a5a64 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Sat, 24 Dec 2022 09:14:09 +0000 Subject: [PATCH 65/97] tests: switch to non-deprecated nix.settings.* module parameters Without the change checks issue the fllowing warning: $ nix flake check trace: warning: The option `nix.useSandbox' defined in `makeTest parameters' has been renamed to `nix.settings.sandbox'. trace: warning: The option `nix.useSandbox' defined in `makeTest parameters' has been renamed to `nix.settings.sandbox'. trace: warning: The option `nix.maxJobs' defined in `makeTest parameters' has been renamed to `nix.settings.max-jobs'. ... --- tests/containers.nix | 2 +- tests/github-flakes.nix | 2 +- tests/nix-copy-closure.nix | 2 +- tests/nss-preload.nix | 6 +++--- tests/remote-builds.nix | 6 +++--- tests/setuid.nix | 2 +- tests/sourcehut-flakes.nix | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/containers.nix b/tests/containers.nix index 59e953c3b..f31f22cf6 100644 --- a/tests/containers.nix +++ b/tests/containers.nix @@ -20,7 +20,7 @@ makeTest ({ (import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = '' extra-experimental-features = nix-command auto-allocate-uids cgroups diff --git a/tests/github-flakes.nix b/tests/github-flakes.nix index 43a4f1432..a8b036b17 100644 --- a/tests/github-flakes.nix +++ b/tests/github-flakes.nix @@ -149,7 +149,7 @@ makeTest ( virtualisation.diskSize = 2048; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = "experimental-features = nix-command flakes"; networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = [ "channels.nixos.org" "api.github.com" "github.com" ]; diff --git a/tests/nix-copy-closure.nix b/tests/nix-copy-closure.nix index ba8b2cfc9..2dc164ae4 100644 --- a/tests/nix-copy-closure.nix +++ b/tests/nix-copy-closure.nix @@ -15,7 +15,7 @@ makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pk { config, lib, pkgs, ... }: { virtualisation.writableStore = true; virtualisation.additionalPaths = [ pkgA pkgD.drvPath ]; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; }; server = diff --git a/tests/nss-preload.nix b/tests/nss-preload.nix index 64b655ba2..5a6ff3f68 100644 --- a/tests/nss-preload.nix +++ b/tests/nss-preload.nix @@ -98,9 +98,9 @@ rec { { address = "192.168.0.10"; prefixLength = 24; } ]; - nix.sandboxPaths = lib.mkForce []; - nix.binaryCaches = lib.mkForce []; - nix.useSandbox = lib.mkForce true; + nix.settings.extra-sandbox-paths = lib.mkForce []; + nix.settings.substituters = lib.mkForce []; + nix.settings.sandbox = lib.mkForce true; }; }; diff --git a/tests/remote-builds.nix b/tests/remote-builds.nix index 7b2e6f708..9f88217fe 100644 --- a/tests/remote-builds.nix +++ b/tests/remote-builds.nix @@ -16,7 +16,7 @@ let { config, pkgs, ... }: { services.openssh.enable = true; virtualisation.writableStore = true; - nix.useSandbox = true; + nix.settings.sandbox = true; }; # Trivial Nix expression to build remotely. @@ -44,7 +44,7 @@ in client = { config, lib, pkgs, ... }: - { nix.maxJobs = 0; # force remote building + { nix.settings.max-jobs = 0; # force remote building nix.distributedBuilds = true; nix.buildMachines = [ { hostName = "builder1"; @@ -62,7 +62,7 @@ in ]; virtualisation.writableStore = true; virtualisation.additionalPaths = [ config.system.build.extraUtils ]; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; programs.ssh.extraConfig = "ConnectTimeout 30"; }; }; diff --git a/tests/setuid.nix b/tests/setuid.nix index a83b1fc3a..82efd6d54 100644 --- a/tests/setuid.nix +++ b/tests/setuid.nix @@ -13,7 +13,7 @@ makeTest { nodes.machine = { config, lib, pkgs, ... }: { virtualisation.writableStore = true; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ]; virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ]; }; diff --git a/tests/sourcehut-flakes.nix b/tests/sourcehut-flakes.nix index daa259dd6..b77496ab6 100644 --- a/tests/sourcehut-flakes.nix +++ b/tests/sourcehut-flakes.nix @@ -108,7 +108,7 @@ makeTest ( virtualisation.diskSize = 2048; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = '' experimental-features = nix-command flakes flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json From a6e9d9cb2f1738a4e713806e9a80438bf716c272 Mon Sep 17 00:00:00 2001 From: Philipp Otterbein Date: Sat, 24 Dec 2022 12:09:06 +0100 Subject: [PATCH 66/97] remove function makeImmutableStringWithLen --- src/libexpr/eval.cc | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index aee0636b0..da17cca80 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -67,22 +67,19 @@ static char * dupString(const char * s) // When there's no need to write to the string, we can optimize away empty // string allocations. -// This function handles makeImmutableStringWithLen(null, 0) by returning the -// empty string. -static const char * makeImmutableStringWithLen(const char * s, size_t size) +// This function handles makeImmutableString(std::string_view()) by returning +// the empty string. +static const char * makeImmutableString(std::string_view s) { + const size_t size = s.size(); if (size == 0) return ""; auto t = allocString(size + 1); - memcpy(t, s, size); - t[size] = 0; + memcpy(t, s.data(), size); + t[size] = '\0'; return t; } -static inline const char * makeImmutableString(std::string_view s) { - return makeImmutableStringWithLen(s.data(), s.size()); -} - RootValue allocRootValue(Value * v) { From 8af839f48c795370df704c8dd40544c88950c1ed Mon Sep 17 00:00:00 2001 From: Philipp Otterbein Date: Sat, 24 Dec 2022 12:19:53 +0100 Subject: [PATCH 67/97] remove undefined function --- src/libexpr/eval.hh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index cf307d820..983491a31 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -60,7 +60,6 @@ void copyContext(const Value & v, PathSet & context); typedef std::map SrcToStore; -std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v); std::string printValue(const EvalState & state, const Value & v); std::ostream & operator << (std::ostream & os, const ValueType t); From aba6eb348e0ed8177da1e7f1df46ba00d20eab96 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 19 Dec 2022 14:06:07 +0100 Subject: [PATCH 68/97] libstore: Make sure that initNix has been called Prevent bugs like https://github.com/cachix/cachix/pull/477 --- src/libexpr/tests/libexprtests.hh | 1 + src/libmain/shared.cc | 1 + src/libstore/globals.cc | 14 ++++++++++++++ src/libstore/globals.hh | 8 ++++++++ src/libstore/store-api.cc | 1 + 5 files changed, 25 insertions(+) diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh index 4f6915882..4a03d4316 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexprtests.hh @@ -12,6 +12,7 @@ namespace nix { class LibExprTest : public ::testing::Test { public: static void SetUpTestSuite() { + initLibStore(); initGC(); } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index a58428762..99e3d6f56 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -235,6 +235,7 @@ void initNix() #endif preloadNSS(); + initLibStore(); } diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index b7f55cae7..130c5b670 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -291,4 +291,18 @@ void initPlugins() settings.pluginFiles.pluginsLoaded = true; } +static bool initLibStoreDone = false; + +void assertLibStoreInitialized() { + if (!initLibStoreDone) { + printError("The program must call nix::initNix() before calling any libstore library functions."); + abort(); + }; +} + +void initLibStore() { + initLibStoreDone = true; +} + + } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 274a15dd7..22458efcf 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -984,4 +984,12 @@ std::vector getUserConfigFiles(); extern const std::string nixVersion; +/* NB: This is not sufficient. You need to call initNix() */ +void initLibStore(); + +/* It's important to initialize before doing _anything_, which is why we + call upon the programmer to handle this correctly. However, we only add + this in a key locations, so as not to litter the code. */ +void assertLibStoreInitialized(); + } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 80b60ca1b..426230ca5 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -458,6 +458,7 @@ Store::Store(const Params & params) : StoreConfig(params) , state({(size_t) pathInfoCacheSize}) { + assertLibStoreInitialized(); } From 336908cf4ccb9708398f4bdc398e90c02a400a04 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 11 Nov 2021 10:31:19 +0100 Subject: [PATCH 69/97] Optimize intersectAttrs performance Always traverse the shortest set. --- src/libexpr/primops.cc | 62 +++++++++++++++++++++++-- tests/lang/eval-okay-intersectAttrs.exp | 1 + tests/lang/eval-okay-intersectAttrs.nix | 50 ++++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 tests/lang/eval-okay-intersectAttrs.exp create mode 100644 tests/lang/eval-okay-intersectAttrs.nix diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7efe50324..983cdfe8b 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2448,12 +2448,62 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * a state.forceAttrs(*args[0], pos); state.forceAttrs(*args[1], pos); - auto attrs = state.buildBindings(std::min(args[0]->attrs->size(), args[1]->attrs->size())); + Bindings &left = *args[0]->attrs; + Bindings &right = *args[1]->attrs; - for (auto & i : *args[0]->attrs) { - Bindings::iterator j = args[1]->attrs->find(i.name); - if (j != args[1]->attrs->end()) - attrs.insert(*j); + auto attrs = state.buildBindings(std::min(left.size(), right.size())); + + // The current implementation has good asymptotic complexity and is reasonably + // simple. Further optimization may be possible, but does not seem productive, + // considering the state of eval performance in 2022. + // + // I have looked for reusable and/or standard solutions and these are my + // findings: + // + // STL + // === + // std::set_intersection is not suitable, as it only performs a simultaneous + // linear scan; not taking advantage of random access. This is O(n + m), so + // linear in the largest set, which is not acceptable for callPackage in Nixpkgs. + // + // Simultaneous scan, with alternating simple binary search + // === + // One alternative algorithm scans the attrsets simultaneously, jumping + // forward using `lower_bound` in case of inequality. This should perform + // well on very similar sets, having a local and predictable access pattern. + // On dissimilar sets, it seems to need more comparisons than the current + // algorithm, as few consecutive attrs match. `lower_bound` could take + // advantage of the decreasing remaining search space, but this causes + // the medians to move, which can mean that they don't stay in the cache + // like they would with the current naive `find`. + // + // Double binary search + // === + // The optimal algorithm may be "Double binary search", which doesn't + // scan at all, but rather divides both sets simultaneously. + // See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al. + // https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf + // The only downsides I can think of are not having a linear access pattern + // for similar sets, and having to maintain a more intricate algorithm. + // + // Adaptive + // === + // Finally one could run try a simultaneous scan, count misses and fall back + // to double binary search when the counter hit some threshold and/or ratio. + + if (left.size() < right.size()) { + for (auto & l : left) { + Bindings::iterator r = right.find(l.name); + if (r != right.end()) + attrs.insert(*r); + } + } + else { + for (auto & r : right) { + Bindings::iterator l = left.find(r.name); + if (l != left.end()) + attrs.insert(r); + } } v.mkAttrs(attrs.alreadySorted()); @@ -2465,6 +2515,8 @@ static RegisterPrimOp primop_intersectAttrs({ .doc = R"( Return a set consisting of the attributes in the set *e2* which have the same name as some attribute in *e1*. + + Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size. )", .fun = prim_intersectAttrs, }); diff --git a/tests/lang/eval-okay-intersectAttrs.exp b/tests/lang/eval-okay-intersectAttrs.exp new file mode 100644 index 000000000..50445bc0e --- /dev/null +++ b/tests/lang/eval-okay-intersectAttrs.exp @@ -0,0 +1 @@ +[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ] diff --git a/tests/lang/eval-okay-intersectAttrs.nix b/tests/lang/eval-okay-intersectAttrs.nix new file mode 100644 index 000000000..39d49938c --- /dev/null +++ b/tests/lang/eval-okay-intersectAttrs.nix @@ -0,0 +1,50 @@ +let + alphabet = + { a = "a"; + b = "b"; + c = "c"; + d = "d"; + e = "e"; + f = "f"; + g = "g"; + h = "h"; + i = "i"; + j = "j"; + k = "k"; + l = "l"; + m = "m"; + n = "n"; + o = "o"; + p = "p"; + q = "q"; + r = "r"; + s = "s"; + t = "t"; + u = "u"; + v = "v"; + w = "w"; + x = "x"; + y = "y"; + z = "z"; + }; + foo = { + inherit (alphabet) f o b a r z q u x; + aa = throw "aa"; + }; + alphabetFail = builtins.mapAttrs throw alphabet; +in +[ (builtins.intersectAttrs { a = abort "l1"; } { b = abort "r1"; }) + (builtins.intersectAttrs { a = abort "l2"; } { a = 1; }) + (builtins.intersectAttrs alphabetFail { a = 1; }) + (builtins.intersectAttrs { a = abort "laa"; } alphabet) + (builtins.intersectAttrs alphabetFail { m = 1; }) + (builtins.intersectAttrs { m = abort "lam"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; }) + (builtins.intersectAttrs { n = abort "lan"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; p = 2; }) + (builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; p = 2; }) + (builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet) + (builtins.intersectAttrs alphabetFail alphabet) + (builtins.intersectAttrs alphabet foo == builtins.intersectAttrs foo alphabet) +] From f5db3a74c4abaec797e6ee74f4be348df55aca45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 22:00:45 +0000 Subject: [PATCH 70/97] Bump zeebe-io/backport-action from 0.0.9 to 1.0.1 Bumps [zeebe-io/backport-action](https://github.com/zeebe-io/backport-action) from 0.0.9 to 1.0.1. - [Release notes](https://github.com/zeebe-io/backport-action/releases) - [Commits](https://github.com/zeebe-io/backport-action/compare/v0.0.9...v1.0.1) --- updated-dependencies: - dependency-name: zeebe-io/backport-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 7568145b6..9f8d14509 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - name: Create backport PRs # should be kept in sync with `version` - uses: zeebe-io/backport-action@v0.0.9 + uses: zeebe-io/backport-action@v1.0.1 with: # Config README: https://github.com/zeebe-io/backport-action#backport-action github_token: ${{ secrets.GITHUB_TOKEN }} From 81c3f99b3668a1ea6a37792a5bcc3bc6f39729a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Sat, 17 Dec 2022 18:31:00 +0100 Subject: [PATCH 71/97] Release shared lock before acquiring exclusive lock In principle, this should avoid deadlocks where two instances of Nix are holding a shared lock on big-lock and are both waiting to get an exclusive lock. However, it seems like `flock(2)` is supposed to do this automatically, so it's not clear whether this is actually where the problem comes from. --- src/libstore/local-store.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index b67668e52..3bab10af9 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -91,6 +91,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) if (!lockFile(lockFd.get(), ltWrite, false)) { printInfo("waiting for exclusive access to the Nix store for ca drvs..."); + lockFile(lockFd.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks lockFile(lockFd.get(), ltWrite, true); } @@ -299,6 +300,7 @@ LocalStore::LocalStore(const Params & params) if (!lockFile(globalLock.get(), ltWrite, false)) { printInfo("waiting for exclusive access to the Nix store..."); + lockFile(globalLock.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks lockFile(globalLock.get(), ltWrite, true); } From d5d2f50ebbe5ec2b8a9777d4184eb3e604a1f8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Wed, 28 Dec 2022 17:09:20 +0100 Subject: [PATCH 72/97] doc: sandbox-paths computes closures --- src/libstore/globals.hh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 274a15dd7..f4d53757c 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -329,7 +329,7 @@ public: Whether to execute builds inside cgroups. This is only supported on Linux. - Cgroups are required and enabled automatically for derivations + Cgroups are required and enabled automatically for derivations that require the `uid-range` system feature. > **Warning** @@ -491,6 +491,9 @@ public: for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will only be mounted in the sandbox if it exists in the host filesystem. + If the source is in the Nix store, then its closure will be added to + the sandbox as well. + Depending on how Nix was built, the default value for this option may be empty or provide `/bin/sh` as a bind-mount of `bash`. )", From 84b08937259745ca743c0b305fc63ab1e1a20d83 Mon Sep 17 00:00:00 2001 From: Steven Shaw Date: Sun, 1 Jan 2023 12:37:43 +1000 Subject: [PATCH 73/97] Fix error message --- src/libstore/builtins/buildenv.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc index 47458a388..b1fbda13d 100644 --- a/src/libstore/builtins/buildenv.cc +++ b/src/libstore/builtins/buildenv.cc @@ -95,7 +95,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, throw Error( "files '%1%' and '%2%' have the same priority %3%; " "use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' " - "or type 'nix profile install --help' if using 'nix profile' to find out how" + "or type 'nix profile install --help' if using 'nix profile' to find out how " "to change the priority of one of the conflicting packages" " (0 being the highest priority)", srcFile, readLink(dstFile), priority); From 9cdf8ededb2049833213bf498b27ed9537358707 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 2 Jan 2023 13:37:59 +0100 Subject: [PATCH 74/97] remove redundant re-definition of store derivations --- src/nix/nix.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nix/nix.md b/src/nix/nix.md index 9641cccb2..db60c59ff 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -115,8 +115,7 @@ the Nix store. Here are the recognised types of installables: * **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv` - Store derivations are a low-level, internal representation of build tasks in Nix, and have store paths with extension `.drv`. - By default, if you pass the path to a store derivation to a `nix` subcommand, the command will operate on the [output path]s of the derivation. + By default, if you pass a [store derivation] path to a `nix` subcommand, the command will operate on the [output path]s of the derivation. [output path]: ../../glossary.md#gloss-output-path From e0c4a95611db4fde942b4e3ee4b6f8e07f956248 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 00:31:48 +0100 Subject: [PATCH 75/97] antiquotation -> string interpolation as proposed by @mkaito[1] and @tazjin[2] and discussed with @edolstra and Nix maintainers [1]: https://github.com/NixOS/nix.dev/pull/267#issuecomment-1270076332 [2]: https://github.com/NixOS/nix.dev/pull/267#issuecomment-1270201979 Co-authored-by: John Ericson Co-authored-by: Eelco Dolstra --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/glossary.md | 11 +- .../src/language/string-interpolation.md | 82 ++++++++++++++ doc/manual/src/language/values.md | 101 +++++++----------- doc/manual/src/release-notes/rl-next.md | 17 +-- src/libexpr/primops.cc | 4 +- tests/lang/eval-okay-ind-string.nix | 2 +- 7 files changed, 136 insertions(+), 82 deletions(-) create mode 100644 doc/manual/src/language/string-interpolation.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index 6a514fa2c..24dbb739f 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -29,6 +29,7 @@ - [Nix Language](language/index.md) - [Data Types](language/values.md) - [Language Constructs](language/constructs.md) + - [String interpolation](language/string-interpolation.md) - [Operators](language/operators.md) - [Derivations](language/derivations.md) - [Advanced Attributes](language/advanced-attributes.md) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 0fcc2b07b..e63f6becc 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -60,7 +60,7 @@ cache](https://cache.nixos.org). - [store path]{#gloss-store-path}\ - The location in the file system of a store object, i.e., an + The location of a [store object] in the file system, i.e., an immediate child of the Nix store directory. Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1` @@ -178,3 +178,12 @@ - [`ε`]{#gloss-epsilon}\ The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. + + - [string interpolation]{#gloss-string-interpolation}\ + Expanding expressions enclosed in `${ }` within a [string], [path], or [attribute name]. + + See [String interpolation](./language/string-interpolation.md) for details. + + [string]: ./language/values.md#type-string + [path]: ./language/values.md#type-path + [attribute name]: ./language/values.md#attribute-set diff --git a/doc/manual/src/language/string-interpolation.md b/doc/manual/src/language/string-interpolation.md new file mode 100644 index 000000000..ddc6b8230 --- /dev/null +++ b/doc/manual/src/language/string-interpolation.md @@ -0,0 +1,82 @@ +# String interpolation + +String interpolation is a language feature where a [string], [path], or [attribute name] can contain expressions enclosed in `${ }` (dollar-sign with curly brackets). + +Such a string is an *interpolated string*, and an expression inside is an *interpolated expression*. + +Interpolated expressions must evaluate to one of the following: + +- a [string] +- a [path] +- a [derivation] + +[string]: ./values.md#type-string +[path]: ./values.md#type-path +[attribute name]: ./values.md#attribute-set +[derivation]: ../glossary.md#gloss-derivation + +## Examples + +### String + +Rather than writing + +```nix +"--with-freetype2-library=" + freetype + "/lib" +``` + +(where `freetype` is a [derivation]), you can instead write + +```nix +"--with-freetype2-library=${freetype}/lib" +``` + +The latter is automatically translated to the former. + +A more complicated example (from the Nix expression for [Qt](http://www.trolltech.com/products/qt)): + +```nix +configureFlags = " + -system-zlib -system-libpng -system-libjpeg + ${if openglSupport then "-dlopen-opengl + -L${mesa}/lib -I${mesa}/include + -L${libXmu}/lib -I${libXmu}/include" else ""} + ${if threadSupport then "-thread" else "-no-thread"} +"; +``` + +Note that Nix expressions and strings can be arbitrarily nested; +in this case the outer string contains various interpolated expressions that themselves contain strings (e.g., `"-thread"`), some of which in turn contain interpolated expressions (e.g., `${mesa}`). + +### Path + +Rather than writing + +```nix +./. + "/" + foo + "-" + bar + ".nix" +``` + +or + +```nix +./. + "/${foo}-${bar}.nix" +``` + +you can instead write + +```nix +./${foo}-${bar}.nix +``` + +### Attribute name + +Attribute names can be created dynamically with string interpolation: + +```nix +let name = "foo"; in +{ + ${name} = "bar"; +} +``` + + { foo = "bar"; } diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md index 6fc8c0369..08baa8f95 100644 --- a/doc/manual/src/language/values.md +++ b/doc/manual/src/language/values.md @@ -13,41 +13,9 @@ returns and tabs can be written as `\n`, `\r` and `\t`, respectively. - You can include the result of an expression into a string by - enclosing it in `${...}`, a feature known as *antiquotation*. The - enclosed expression must evaluate to something that can be coerced - into a string (meaning that it must be a string, a path, or a - derivation). For instance, rather than writing + You can include the results of other expressions into a string by enclosing them in `${ }`, a feature known as [string interpolation]. - ```nix - "--with-freetype2-library=" + freetype + "/lib" - ``` - - (where `freetype` is a derivation), you can instead write the more - natural - - ```nix - "--with-freetype2-library=${freetype}/lib" - ``` - - The latter is automatically translated to the former. A more - complicated example (from the Nix expression for - [Qt](http://www.trolltech.com/products/qt)): - - ```nix - configureFlags = " - -system-zlib -system-libpng -system-libjpeg - ${if openglSupport then "-dlopen-opengl - -L${mesa}/lib -I${mesa}/include - -L${libXmu}/lib -I${libXmu}/include" else ""} - ${if threadSupport then "-thread" else "-no-thread"} - "; - ``` - - Note that Nix expressions and strings can be arbitrarily nested; in - this case the outer string contains various antiquotations that - themselves contain strings (e.g., `"-thread"`), some of which in - turn contain expressions (e.g., `${mesa}`). + [string interpolation]: ./string-interpolation.md The second way to write string literals is as an *indented string*, which is enclosed between pairs of *double single-quotes*, like so: @@ -75,7 +43,7 @@ Note that the whitespace and newline following the opening `''` is ignored if there is no non-whitespace text on the initial line. - Antiquotation (`${expr}`) is supported in indented strings. + Indented strings support [string interpolation]. Since `${` and `''` have special meaning in indented strings, you need a way to quote them. `$` can be escaped by prefixing it with @@ -143,26 +111,23 @@ environment variable `NIX_PATH` will be searched for the given file or directory name. - Antiquotation is supported in any paths except those in angle brackets. - `./${foo}-${bar}.nix` is a more convenient way of writing - `./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At - least one slash must appear *before* any antiquotations for this to be - recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division - operation. `./a.${foo}/b.${bar}` is a path. + When an [interpolated string][string interpolation] evaluates to a path, the path is first copied into the Nix store and the resulting string is the [store path] of the newly created [store object]. - When a path appears in an antiquotation, and is thus coerced into a string, - the path is first copied into the Nix store and the resulting string is - the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in - the current directory to be copied into the Nix store and result in the - string `"/nix/store/-foo.txt"`. + [store path]: ../glossary.md#gloss-store-path + [store object]: ../glossary.md#gloss-store-object - Note that the Nix language assumes that all input files will remain - _unchanged_ during the course of the Nix expression evaluation. - If you for example antiquote a file path during a `nix repl` session, and - then later in the same session, after having changed the file contents, - evaluate the antiquotation with the file path again, then Nix will still - return the first store path. It will _not_ reread the file contents to - produce a different Nix store path. + For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` in the current directory to be copied into the Nix store and result in the string `"/nix/store/-foo.txt"`. + + Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression. + For example, assume you used a file path in an interpolated string during a `nix repl` session. + Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new store path, since Nix might not re-read the file contents. + + Paths themselves, except those in angle brackets (`< >`), support [string interpolation]. + + At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path. + + `a.${foo}/b.${bar}` is a syntactically valid division operation. + `./a.${foo}/b.${bar}` is a path. - Boolean @@ -235,23 +200,33 @@ will evaluate to `"Xyzzy"` because there is no `c` attribute in the set. You can use arbitrary double-quoted strings as attribute names: ```nix -{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}" +{ "$!@#?" = 123; }."$!@#?" ``` -This will evaluate to `123` (Assuming `bar` is antiquotable). In the -case where an attribute name is just a single antiquotation, the quotes -can be dropped: - ```nix -{ foo = 123; }.${bar} or 456 +let bar = "bar"; +{ "foo ${bar}" = 123; }."foo ${bar}" ``` -This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced -to a string and `456` otherwise (again assuming `bar` is antiquotable). +Both will evaluate to `123`. + +Attribute names support [string interpolation]: + +```nix +let bar = "foo"; in +{ foo = 123; }.${bar} +``` + +```nix +let bar = "foo"; in +{ ${bar} = 123; }.foo +``` + +Both will evaluate to `123`. In the special case where an attribute name inside of a set declaration -evaluates to `null` (which is normally an error, as `null` is not -antiquotable), that attribute is simply not added to the set: +evaluates to `null` (which is normally an error, as `null` cannot be coerced to +a string), that attribute is simply not added to the set: ```nix { ${if foo then "bar" else null} = true; } diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 906b048f1..8119b7c31 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -15,19 +15,6 @@ # NIX_PATH=nixpkgs=flake:nixpkgs nix-build '' -A hello ``` -* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables. - For example, - ```shell-session - $ nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev` - ``` - now works just as - ```shell-session - $ nix-build glibc^dev` - ``` - does already. +* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently. + Historical release notes were not changed. -* On Linux, `nix develop` now sets the - [*personality*](https://man7.org/linux/man-pages/man2/personality.2.html) - for the development shell in the same way as the actual build of the - derivation. This makes shells for `i686-linux` derivations work - correctly on `x86_64-linux`. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7efe50324..7cad041af 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1930,8 +1930,8 @@ static RegisterPrimOp primop_toFile({ "; ``` - Note that `${configFile}` is an - [antiquotation](language-values.md), so the result of the + Note that `${configFile}` is a + [string interpolation](language/values.md#type-string), so the result of the expression `configFile` (i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be spliced into the resulting string. diff --git a/tests/lang/eval-okay-ind-string.nix b/tests/lang/eval-okay-ind-string.nix index 1669dc064..95d59b508 100644 --- a/tests/lang/eval-okay-ind-string.nix +++ b/tests/lang/eval-okay-ind-string.nix @@ -110,7 +110,7 @@ let And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t. ''; - # Regression test: antiquotation in '${x}' should work, but didn't. + # Regression test: string interpolation in '${x}' should work, but didn't. s15 = let x = "bla"; in '' foo '${x}' From 105d74eb8177016a1056b6642875c318a148a776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 2 Jan 2023 15:44:04 +0100 Subject: [PATCH 76/97] Revert "Fix why-depends for CA derivations" This reverts commit b13fd4c58e81b2b2b0d72caa5ce80de861622610. --- src/nix/why-depends.cc | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 723017497..6512aee03 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -95,35 +95,19 @@ struct CmdWhyDepends : SourceExprCommand * to build. */ auto dependency = parseInstallable(store, _dependency); - auto derivedDependency = dependency->toDerivedPath(); - auto optDependencyPath = std::visit(overloaded { - [](const DerivedPath::Opaque & nodrv) -> std::optional { - return { nodrv.path }; - }, - [&](const DerivedPath::Built & hasdrv) -> std::optional { - if (hasdrv.outputs.size() != 1) { - throw Error("argument '%s' should evaluate to one store path", dependency->what()); - } - auto outputMap = store->queryPartialDerivationOutputMap(hasdrv.drvPath); - auto maybePath = outputMap.find(*hasdrv.outputs.begin()); - if (maybePath == outputMap.end()) { - throw Error("unexpected end of iterator"); - } - return maybePath->second; - }, - }, derivedDependency.raw()); + auto dependencyPath = Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency); + auto dependencyPathHash = dependencyPath.hashPart(); StorePathSet closure; store->computeFSClosure({packagePath}, closure, false, false); - if (!optDependencyPath.has_value() || !closure.count(*optDependencyPath)) { - printError("'%s' does not depend on '%s'", package->what(), dependency->what()); + if (!closure.count(dependencyPath)) { + printError("'%s' does not depend on '%s'", + store->printStorePath(packagePath), + store->printStorePath(dependencyPath)); return; } - auto dependencyPath = *optDependencyPath; - auto dependencyPathHash = dependencyPath.hashPart(); - stopProgressBar(); // FIXME auto accessor = store->getFSAccessor(); From 6a90ef072c2a5fcb7aada94763c7ccdb5ae2bae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 2 Jan 2023 16:09:03 +0100 Subject: [PATCH 77/97] Increase the test coverage of `why-depends` - Test with `--derivation` - Actually test with ca-derivations (was suuposedly done, but not activated because of a missing line in `local.mk`) --- tests/local.mk | 1 + tests/why-depends.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tests/local.mk b/tests/local.mk index bba6ad9c9..2489baecf 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -92,6 +92,7 @@ nix_tests = \ fmt.sh \ eval-store.sh \ why-depends.sh \ + ca/why-depends.sh \ import-derivation.sh \ ca/import-derivation.sh \ nix_path.sh \ diff --git a/tests/why-depends.sh b/tests/why-depends.sh index c12941e76..a04d529b5 100644 --- a/tests/why-depends.sh +++ b/tests/why-depends.sh @@ -6,6 +6,9 @@ cp ./dependencies.nix ./dependencies.builder0.sh ./config.nix $TEST_HOME cd $TEST_HOME +nix why-depends --derivation --file ./dependencies.nix input2_drv input1_drv +nix why-depends --file ./dependencies.nix input2_drv input1_drv + nix-build ./dependencies.nix -A input0_drv -o dep nix-build ./dependencies.nix -o toplevel From 8cac451fce990151046996a13130bb1b91c6ba19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 2 Jan 2023 17:35:48 +0100 Subject: [PATCH 78/97] Fix why-depends for CA derivations (again) This has the same goal as b13fd4c58e81b2b2b0d72caa5ce80de861622610,but achieves it in a different way in order to not break `nix why-depends --derivation`. --- src/libcmd/installables.cc | 5 +---- src/libstore/realisation.hh | 10 ++++++++++ src/libstore/remote-store.cc | 5 +---- src/nix/why-depends.cc | 18 ++++++++++++------ 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index d2600ca91..b5675d5bd 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -931,10 +931,7 @@ std::vector, BuiltPathWithResult>> Instal DrvOutput outputId { *outputHash, output }; auto realisation = store->queryRealisation(outputId); if (!realisation) - throw Error( - "cannot operate on an output of the " - "unbuilt derivation '%s'", - outputId.to_string()); + throw MissingRealisation(outputId); outputs.insert_or_assign(output, realisation->outPath); } else { // If ca-derivations isn't enabled, assume that diff --git a/src/libstore/realisation.hh b/src/libstore/realisation.hh index 9070a6ee2..911c61909 100644 --- a/src/libstore/realisation.hh +++ b/src/libstore/realisation.hh @@ -93,4 +93,14 @@ struct RealisedPath { GENERATE_CMP(RealisedPath, me->raw); }; +class MissingRealisation : public Error +{ +public: + MissingRealisation(DrvOutput & outputId) + : Error( "cannot operate on an output of the " + "unbuilt derivation '%s'", + outputId.to_string()) + {} +}; + } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 48cf731a8..ccf7d7e8b 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -879,10 +879,7 @@ std::vector RemoteStore::buildPathsWithResults( auto realisation = queryRealisation(outputId); if (!realisation) - throw Error( - "cannot operate on an output of unbuilt " - "content-addressed derivation '%s'", - outputId.to_string()); + throw MissingRealisation(outputId); res.builtOutputs.emplace(realisation->id, *realisation); } else { // If ca-derivations isn't enabled, assume that diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 6512aee03..76125e5e4 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -95,19 +95,25 @@ struct CmdWhyDepends : SourceExprCommand * to build. */ auto dependency = parseInstallable(store, _dependency); - auto dependencyPath = Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency); - auto dependencyPathHash = dependencyPath.hashPart(); + auto optDependencyPath = [&]() -> std::optional { + try { + return {Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency)}; + } catch (MissingRealisation &) { + return std::nullopt; + } + }(); StorePathSet closure; store->computeFSClosure({packagePath}, closure, false, false); - if (!closure.count(dependencyPath)) { - printError("'%s' does not depend on '%s'", - store->printStorePath(packagePath), - store->printStorePath(dependencyPath)); + if (!optDependencyPath.has_value() || !closure.count(*optDependencyPath)) { + printError("'%s' does not depend on '%s'", package->what(), dependency->what()); return; } + auto dependencyPath = *optDependencyPath; + auto dependencyPathHash = dependencyPath.hashPart(); + stopProgressBar(); // FIXME auto accessor = store->getFSAccessor(); From c548e3549850f224a232f8a74b5a9e2276070896 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Jan 2023 20:01:28 +0100 Subject: [PATCH 79/97] Don't use state.positions[noPos] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This caused traces 'at «none»:0: (source not available)'. --- src/libexpr/primops.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 5142d79e8..9356d307e 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -737,7 +737,7 @@ static RegisterPrimOp primop_break({ throw Error(ErrorInfo{ .level = lvlInfo, .msg = hintfmt("quit the debugger"), - .errPos = state.positions[noPos], + .errPos = nullptr, }); } } @@ -1164,7 +1164,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * } } catch (Error & e) { - e.addTrace(state.positions[noPos], + e.addTrace(nullptr, hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName), true); throw; From d33d15a48b55eb81270a39b001c04bc61c42105f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 Jan 2023 20:40:39 +0100 Subject: [PATCH 80/97] Put the --show-trace hint in the logical place --- src/libmain/shared.cc | 2 -- src/libutil/error.cc | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 99e3d6f56..648e21a25 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -403,8 +403,6 @@ int handleExceptions(const std::string & programName, std::function fun) return 1; } catch (BaseError & e) { logError(e.info()); - if (e.hasTrace() && !loggerSettings.showTrace.get()) - printError("(use '--show-trace' to show detailed location information)"); return e.status; } catch (std::bad_alloc & e) { printError(error + "out of memory"); diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 7f7c27267..e4f0d4677 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -303,7 +303,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s size_t count = 0; for (const auto & trace : einfo.traces) { if (!showTrace && count > 3) { - oss << "\n" << ANSI_ITALIC "(stack trace truncated)" ANSI_NORMAL << "\n"; + oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n"; break; } From 224b56f10e1bb754d403c106eb9d1b947fc30414 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 3 Jan 2023 14:51:23 +0100 Subject: [PATCH 81/97] Move creation of the temp roots file into its own function This also moves the file handle into its own Sync object so we're not holding the _state while acquiring the file lock. There was no real deadlock risk here since locking a newly created file cannot block, but it's still a bit nicer. --- src/libstore/gc.cc | 52 +++++++++++++++++++++---------------- src/libstore/local-store.cc | 6 ++--- src/libstore/local-store.hh | 12 ++++++--- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 5d91829f1..f8c29593f 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -77,37 +77,43 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot } -void LocalStore::addTempRoot(const StorePath & path) +void LocalStore::createTempRootsFile() { - auto state(_state.lock()); + auto fdTempRoots(_fdTempRoots.lock()); /* Create the temporary roots file for this process. */ - if (!state->fdTempRoots) { + if (*fdTempRoots) return; - while (1) { - if (pathExists(fnTempRoots)) - /* It *must* be stale, since there can be no two - processes with the same pid. */ - unlink(fnTempRoots.c_str()); + while (1) { + if (pathExists(fnTempRoots)) + /* It *must* be stale, since there can be no two + processes with the same pid. */ + unlink(fnTempRoots.c_str()); - state->fdTempRoots = openLockFile(fnTempRoots, true); + *fdTempRoots = openLockFile(fnTempRoots, true); - debug("acquiring write lock on '%s'", fnTempRoots); - lockFile(state->fdTempRoots.get(), ltWrite, true); + debug("acquiring write lock on '%s'", fnTempRoots); + lockFile(fdTempRoots->get(), ltWrite, true); - /* Check whether the garbage collector didn't get in our - way. */ - struct stat st; - if (fstat(state->fdTempRoots.get(), &st) == -1) - throw SysError("statting '%1%'", fnTempRoots); - if (st.st_size == 0) break; - - /* The garbage collector deleted this file before we could - get a lock. (It won't delete the file after we get a - lock.) Try again. */ - } + /* Check whether the garbage collector didn't get in our + way. */ + struct stat st; + if (fstat(fdTempRoots->get(), &st) == -1) + throw SysError("statting '%1%'", fnTempRoots); + if (st.st_size == 0) break; + /* The garbage collector deleted this file before we could get + a lock. (It won't delete the file after we get a lock.) + Try again. */ } +} + + +void LocalStore::addTempRoot(const StorePath & path) +{ + createTempRootsFile(); + + auto state(_state.lock()); if (!state->fdGCLock) state->fdGCLock = openGCLock(); @@ -162,7 +168,7 @@ void LocalStore::addTempRoot(const StorePath & path) /* Append the store path to the temporary roots file. */ auto s = printStorePath(path) + '\0'; - writeFull(state->fdTempRoots.get(), s); + writeFull(_fdTempRoots.lock()->get(), s); } diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 3bab10af9..be21e3ca0 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -441,9 +441,9 @@ LocalStore::~LocalStore() } try { - auto state(_state.lock()); - if (state->fdTempRoots) { - state->fdTempRoots = -1; + auto fdTempRoots(_fdTempRoots.lock()); + if (*fdTempRoots) { + *fdTempRoots = -1; unlink(fnTempRoots.c_str()); } } catch (...) { diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 4579c2f62..9ea0c0bf7 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -62,9 +62,6 @@ private: /* The global GC lock */ AutoCloseFD fdGCLock; - /* The file to which we write our temporary roots. */ - AutoCloseFD fdTempRoots; - /* Connection to the garbage collector. */ AutoCloseFD fdRootsSocket; @@ -156,6 +153,15 @@ public: void addTempRoot(const StorePath & path) override; +private: + + void createTempRootsFile(); + + /* The file to which we write our temporary roots. */ + Sync _fdTempRoots; + +public: + void addIndirectRoot(const Path & path) override; private: From 28d5b5cd453d89642557455d82b98903da2898b7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 3 Jan 2023 15:15:14 +0100 Subject: [PATCH 82/97] Fix deadlock between auto-GC and addTempRoot() Previously addTempRoot() acquired the LocalStore state lock and waited for the garbage collector to reply. If the garbage collector is in the same process (as it the case with auto-GC), this would deadlock as soon as the garbage collector thread needs the LocalStore state lock. So now addTempRoot() uses separate Syncs for the state that it needs. As long at the auto-GC thread doesn't call addTempRoot() (which it shouldn't), it shouldn't deadlock. Fixes #3224. --- src/libstore/gc.cc | 36 ++++++++++++++++++++++-------------- src/libstore/local-store.hh | 12 ++++++------ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index f8c29593f..996f26a95 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -113,30 +113,37 @@ void LocalStore::addTempRoot(const StorePath & path) { createTempRootsFile(); - auto state(_state.lock()); - - if (!state->fdGCLock) - state->fdGCLock = openGCLock(); + /* Open/create the global GC lock file. */ + { + auto fdGCLock(_fdGCLock.lock()); + if (!*fdGCLock) + *fdGCLock = openGCLock(); + } restart: - FdLock gcLock(state->fdGCLock.get(), ltRead, false, ""); + /* Try to acquire a shared global GC lock (non-blocking). This + only succeeds if the garbage collector is not currently + running. */ + FdLock gcLock(_fdGCLock.lock()->get(), ltRead, false, ""); if (!gcLock.acquired) { /* We couldn't get a shared global GC lock, so the garbage collector is running. So we have to connect to the garbage collector and inform it about our root. */ - if (!state->fdRootsSocket) { + auto fdRootsSocket(_fdRootsSocket.lock()); + + if (!*fdRootsSocket) { auto socketPath = stateDir.get() + gcSocketPath; debug("connecting to '%s'", socketPath); - state->fdRootsSocket = createUnixDomainSocket(); + *fdRootsSocket = createUnixDomainSocket(); try { - nix::connect(state->fdRootsSocket.get(), socketPath); + nix::connect(fdRootsSocket->get(), socketPath); } catch (SysError & e) { /* The garbage collector may have exited, so we need to restart. */ if (e.errNo == ECONNREFUSED) { debug("GC socket connection refused"); - state->fdRootsSocket.close(); + fdRootsSocket->close(); goto restart; } throw; @@ -145,9 +152,9 @@ void LocalStore::addTempRoot(const StorePath & path) try { debug("sending GC root '%s'", printStorePath(path)); - writeFull(state->fdRootsSocket.get(), printStorePath(path) + "\n", false); + writeFull(fdRootsSocket->get(), printStorePath(path) + "\n", false); char c; - readFull(state->fdRootsSocket.get(), &c, 1); + readFull(fdRootsSocket->get(), &c, 1); assert(c == '1'); debug("got ack for GC root '%s'", printStorePath(path)); } catch (SysError & e) { @@ -155,18 +162,19 @@ void LocalStore::addTempRoot(const StorePath & path) restart. */ if (e.errNo == EPIPE || e.errNo == ECONNRESET) { debug("GC socket disconnected"); - state->fdRootsSocket.close(); + fdRootsSocket->close(); goto restart; } throw; } catch (EndOfFile & e) { debug("GC socket disconnected"); - state->fdRootsSocket.close(); + fdRootsSocket->close(); goto restart; } } - /* Append the store path to the temporary roots file. */ + /* Record the store path in the temporary roots file so it will be + seen by a future run of the garbage collector. */ auto s = printStorePath(path) + '\0'; writeFull(_fdTempRoots.lock()->get(), s); } diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 9ea0c0bf7..06d36a7d5 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -59,12 +59,6 @@ private: struct Stmts; std::unique_ptr stmts; - /* The global GC lock */ - AutoCloseFD fdGCLock; - - /* Connection to the garbage collector. */ - AutoCloseFD fdRootsSocket; - /* The last time we checked whether to do an auto-GC, or an auto-GC finished. */ std::chrono::time_point lastGCCheck; @@ -160,6 +154,12 @@ private: /* The file to which we write our temporary roots. */ Sync _fdTempRoots; + /* The global GC lock. */ + Sync _fdGCLock; + + /* Connection to the garbage collector. */ + Sync _fdRootsSocket; + public: void addIndirectRoot(const Path & path) override; From d4d1ca8b1160c8ee045fefafa7ccb00a1a5eeb0b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 3 Jan 2023 08:30:49 -0800 Subject: [PATCH 83/97] nix --version: Print the data directory --- src/libmain/shared.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 648e21a25..d4871a8e2 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -363,6 +363,7 @@ void printVersion(const std::string & programName) << "\n"; std::cout << "Store directory: " << settings.nixStore << "\n"; std::cout << "State directory: " << settings.nixStateDir << "\n"; + std::cout << "Data directory: " << settings.nixDataDir << "\n"; } throw Exit(); } From 609a7dc05974c9f86b2e7304762b9e01c5879380 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Jan 2023 04:36:07 -0800 Subject: [PATCH 84/97] Include macOS sandbox files in the Nix binary This basically reverts 6e5165b77370c76bfa39d4b55e9f83673f3bd466. It fixes errors like sandbox-exec: :292:47: unable to open sandbox-minimal.sb: not found when trying to run a development Nix installed in a user's home directory. Also, we're trying to minimize the number of installed files to make it possible to deploy Nix as a single statically-linked binary. --- src/libstore/build/local-derivation-goal.cc | 14 +++++++++----- src/libstore/local.mk | 4 ---- src/libstore/sandbox-defaults.sb | 4 ++++ src/libstore/sandbox-minimal.sb | 4 ++++ src/libstore/sandbox-network.sb | 4 ++++ 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 9d869d513..488e06d8c 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -2050,10 +2050,14 @@ void LocalDerivationGoal::runChild() sandboxProfile += "(deny default (with no-log))\n"; } - sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; + sandboxProfile += + #include "sandbox-defaults.sb" + ; if (!derivationType.isSandboxed()) - sandboxProfile += "(import \"sandbox-network.sb\")\n"; + sandboxProfile += + #include "sandbox-network.sb" + ; /* Add the output paths we'll use at build-time to the chroot */ sandboxProfile += "(allow file-read* file-write* process-exec\n"; @@ -2096,7 +2100,9 @@ void LocalDerivationGoal::runChild() sandboxProfile += additionalSandboxProfile; } else - sandboxProfile += "(import \"sandbox-minimal.sb\")\n"; + sandboxProfile += + #include "sandbox-minimal.sb" + ; debug("Generated sandbox profile:"); debug(sandboxProfile); @@ -2121,8 +2127,6 @@ void LocalDerivationGoal::runChild() args.push_back(sandboxFile); args.push_back("-D"); args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir); - args.push_back("-D"); - args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/"); if (allowLocalNetworking) { args.push_back("-D"); args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1")); diff --git a/src/libstore/local.mk b/src/libstore/local.mk index 8f28bec6c..e5e24501e 100644 --- a/src/libstore/local.mk +++ b/src/libstore/local.mk @@ -13,10 +13,6 @@ ifdef HOST_LINUX libstore_LDFLAGS += -ldl endif -ifdef HOST_DARWIN -libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb -endif - $(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox))) ifeq ($(ENABLE_S3), 1) diff --git a/src/libstore/sandbox-defaults.sb b/src/libstore/sandbox-defaults.sb index d9d710559..77f013aea 100644 --- a/src/libstore/sandbox-defaults.sb +++ b/src/libstore/sandbox-defaults.sb @@ -1,3 +1,5 @@ +R""( + (define TMPDIR (param "_GLOBAL_TMP_DIR")) (deny default) @@ -104,3 +106,5 @@ (subpath "/System/Library/Apple/usr/libexec/oah") (subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist") (subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist")) + +)"" diff --git a/src/libstore/sandbox-minimal.sb b/src/libstore/sandbox-minimal.sb index 65f5108b3..976a1f636 100644 --- a/src/libstore/sandbox-minimal.sb +++ b/src/libstore/sandbox-minimal.sb @@ -1,5 +1,9 @@ +R""( + (allow default) ; Disallow creating setuid/setgid binaries, since that ; would allow breaking build user isolation. (deny file-write-setugid) + +)"" diff --git a/src/libstore/sandbox-network.sb b/src/libstore/sandbox-network.sb index 19e9eea9a..335edbaed 100644 --- a/src/libstore/sandbox-network.sb +++ b/src/libstore/sandbox-network.sb @@ -1,3 +1,5 @@ +R""( + ; Allow local and remote network traffic. (allow network* (local ip) (remote ip)) @@ -18,3 +20,5 @@ ; Allow access to trustd. (allow mach-lookup (global-name "com.apple.trustd")) (allow mach-lookup (global-name "com.apple.trustd.agent")) + +)"" From 6991e558ddaaf037954741830078f933a36ec2f2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 Jan 2023 04:50:45 -0800 Subject: [PATCH 85/97] Move macOS sandbox files to sr/libstore/build --- src/libstore/{ => build}/sandbox-defaults.sb | 0 src/libstore/{ => build}/sandbox-minimal.sb | 0 src/libstore/{ => build}/sandbox-network.sb | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/libstore/{ => build}/sandbox-defaults.sb (100%) rename src/libstore/{ => build}/sandbox-minimal.sb (100%) rename src/libstore/{ => build}/sandbox-network.sb (100%) diff --git a/src/libstore/sandbox-defaults.sb b/src/libstore/build/sandbox-defaults.sb similarity index 100% rename from src/libstore/sandbox-defaults.sb rename to src/libstore/build/sandbox-defaults.sb diff --git a/src/libstore/sandbox-minimal.sb b/src/libstore/build/sandbox-minimal.sb similarity index 100% rename from src/libstore/sandbox-minimal.sb rename to src/libstore/build/sandbox-minimal.sb diff --git a/src/libstore/sandbox-network.sb b/src/libstore/build/sandbox-network.sb similarity index 100% rename from src/libstore/sandbox-network.sb rename to src/libstore/build/sandbox-network.sb From 4e84b532ed5317ec836c54689c73a1fddab0c892 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 5 Jan 2023 04:58:55 -0800 Subject: [PATCH 86/97] On macOS with auto-uid-allocation and sandboxing, use the correct gid macOS doesn't have user namespacing, so the gid of the builder needs to be nixbld. The logic got "has sandboxing enabled" confused with "has user namespaces". Fixes #7529. --- src/libstore/lock.cc | 12 ++++++++---- src/libstore/lock.hh | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index d02d20b4c..4fe1fcf56 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -123,8 +123,12 @@ struct AutoUserLock : UserLock std::vector getSupplementaryGIDs() override { return {}; } - static std::unique_ptr acquire(uid_t nrIds, bool useChroot) + static std::unique_ptr acquire(uid_t nrIds, bool useUserNamespace) { + #if !defined(__linux__) + useUserNamespace = false; + #endif + settings.requireExperimentalFeature(Xp::AutoAllocateUids); assert(settings.startId > 0); assert(settings.uidCount % maxIdsPerBuild == 0); @@ -157,7 +161,7 @@ struct AutoUserLock : UserLock auto lock = std::make_unique(); lock->fdUserLock = std::move(fd); lock->firstUid = firstUid; - if (useChroot) + if (useUserNamespace) lock->firstGid = firstUid; else { struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); @@ -174,10 +178,10 @@ struct AutoUserLock : UserLock } }; -std::unique_ptr acquireUserLock(uid_t nrIds, bool useChroot) +std::unique_ptr acquireUserLock(uid_t nrIds, bool useUserNamespace) { if (settings.autoAllocateUids) - return AutoUserLock::acquire(nrIds, useChroot); + return AutoUserLock::acquire(nrIds, useUserNamespace); else return SimpleUserLock::acquire(); } diff --git a/src/libstore/lock.hh b/src/libstore/lock.hh index 49ad86de7..7f1934510 100644 --- a/src/libstore/lock.hh +++ b/src/libstore/lock.hh @@ -31,7 +31,7 @@ struct UserLock /* Acquire a user lock for a UID range of size `nrIds`. Note that this may return nullptr if no user is available. */ -std::unique_ptr acquireUserLock(uid_t nrIds, bool useChroot); +std::unique_ptr acquireUserLock(uid_t nrIds, bool useUserNamespace); bool useBuildUsers(); From caebe4112eb491d0168cf423174bccba918330f6 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 22 Dec 2022 21:18:05 +0100 Subject: [PATCH 87/97] reorder columns this is for a simpler transformation into a series of subsections --- doc/manual/src/language/operators.md | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 32398189d..e06e73b2d 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -3,26 +3,26 @@ The table below lists the operators in the Nix language, in order of precedence (from strongest to weakest binding). -| Name | Syntax | Associativity | Description | Precedence | -| ------------------------ | ----------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| Select | *e* `.` *attrpath* \[ `or` *def* \] | none | Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. | 1 | -| Application | *e1* *e2* | left | Call function *e1* with argument *e2*. | 2 | -| Arithmetic Negation | `-` *e* | none | Arithmetic negation. | 3 | -| Has Attribute | *e* `?` *attrpath* | none | Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. | 4 | -| List Concatenation | *e1* `++` *e2* | right | List concatenation. | 5 | -| Multiplication | *e1* `*` *e2*, | left | Arithmetic multiplication. | 6 | -| Division | *e1* `/` *e2* | left | Arithmetic division. | 6 | -| Addition | *e1* `+` *e2* | left | Arithmetic addition. | 7 | -| Subtraction | *e1* `-` *e2* | left | Arithmetic subtraction. | 7 | -| String Concatenation | *string1* `+` *string2* | left | String concatenation. | 7 | -| Not | `!` *e* | none | Boolean negation. | 8 | -| Update | *e1* `//` *e2* | right | Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). | 9 | -| Less Than | *e1* `<` *e2*, | none | Arithmetic/lexicographic comparison. | 10 | -| Less Than or Equal To | *e1* `<=` *e2* | none | Arithmetic/lexicographic comparison. | 10 | -| Greater Than | *e1* `>` *e2* | none | Arithmetic/lexicographic comparison. | 10 | -| Greater Than or Equal To | *e1* `>=` *e2* | none | Arithmetic/lexicographic comparison. | 10 | -| Equality | *e1* `==` *e2* | none | Equality. | 11 | -| Inequality | *e1* `!=` *e2* | none | Inequality. | 11 | -| Logical AND | *e1* `&&` *e2* | left | Logical AND. | 12 | -| Logical OR | *e1* || *e2* | left | Logical OR. | 13 | -| Logical Implication | *e1* `->` *e2* | none | Logical implication (equivalent to !e1 || e2). | 14 | +| Name | Syntax | Description | Associativity | Precedence | +| ------------------------ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ---------- | +| Select | *e* `.` *attrpath* \[ `or` *def* \] | Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. | none | 1 | +| Application | *e1* *e2* | Call function *e1* with argument *e2*. | left | 2 | +| Arithmetic Negation | `-` *e* | Arithmetic negation. | none | 3 | +| Has Attribute | *e* `?` *attrpath* | Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. | none | 4 | +| List Concatenation | *e1* `++` *e2* | List concatenation. | right | 5 | +| Multiplication | *e1* `*` *e2*, | Arithmetic multiplication. | left | 6 | +| Division | *e1* `/` *e2* | Arithmetic division. | left | 6 | +| Addition | *e1* `+` *e2* | Arithmetic addition. | left | 7 | +| Subtraction | *e1* `-` *e2* | Arithmetic subtraction. | left | 7 | +| String Concatenation | *string1* `+` *string2* | String concatenation. | left | 7 | +| Not | `!` *e* | Boolean negation. | none | 8 | +| Update | *e1* `//` *e2* | Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). | right | 9 | +| Less Than | *e1* `<` *e2*, | Arithmetic/lexicographic comparison. | none | 10 | +| Less Than or Equal To | *e1* `<=` *e2* | Arithmetic/lexicographic comparison. | none | 10 | +| Greater Than | *e1* `>` *e2* | Arithmetic/lexicographic comparison. | none | 10 | +| Greater Than or Equal To | *e1* `>=` *e2* | Arithmetic/lexicographic comparison. | none | 10 | +| Equality | *e1* `==` *e2* | Equality. | none | 11 | +| Inequality | *e1* `!=` *e2* | Inequality. | none | 11 | +| Logical AND | *e1* `&&` *e2* | Logical AND. | left | 12 | +| Logical OR | *e1* || *e2* | Logical OR. | left | 13 | +| Logical Implication | *e1* `->` *e2* | Logical implication (equivalent to !e1 || e2). | none | 14 | From e07448ba6bdbbe9fb33fa0b652fef06635f1fc6d Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 22 Dec 2022 21:21:26 +0100 Subject: [PATCH 88/97] convert table to subsections this form is much easier to maintain (also with minimal diffs), and allows for more details on each operator. this change a purely mechanical transformation, without changing any contents. --- doc/manual/src/language/operators.md | 234 ++++++++++++++++++++++++--- 1 file changed, 209 insertions(+), 25 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index e06e73b2d..aeb77b72a 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -1,28 +1,212 @@ # Operators -The table below lists the operators in the Nix language, in -order of precedence (from strongest to weakest binding). +## Select + +> *e* `.` *attrpath* \[ `or` *def* \] + +Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. + +Associativity: none + +Precedence: 1 + +## Application + +> *e1* *e2* + +Call function *e1* with argument *e2*. + +Associativity: left + +Precedence: 2 + +## Arithmetic Negation + +> `-` *e* + +Arithmetic negation. + +Associativity: none + +Precedence: 3 + +## Has Attribute + +> *e* `?` *attrpath* + +Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. + +Associativity: none + +Precedence: 4 + +## List Concatenation + +> *e1* `++` *e2* + +List concatenation. + +Associativity: right + +Precedence: 5 + +## Multiplication + +> *e1* `*` *e2*, + +Arithmetic multiplication. + +Associativity: left + +Precedence: 6 + +## Division + +> *e1* `/` *e2* + +Arithmetic division. + +Associativity: left + +Precedence: 6 + +## Addition + +> *e1* `+` *e2* + +Arithmetic addition. + +Associativity: left + +Precedence: 7 + +## Subtraction + +> *e1* `-` *e2* + +Arithmetic subtraction. + +Associativity: left + +Precedence: 7 + +## String Concatenation + +> *string1* `+` *string2* + +String concatenation. + +Associativity: left + +Precedence: 7 + +## Not + +> `!` *e* + +Boolean negation. + +Associativity: none + +Precedence: 8 + +## Update + +> *e1* `//` *e2* + +Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). + +Associativity: right + +Precedence: 9 + +## Less Than + +> *e1* `<` *e2*, + +Arithmetic/lexicographic comparison. + +Associativity: none + +Precedence: 10 + +## Less Than or Equal To + +> *e1* `<=` *e2* + +Arithmetic/lexicographic comparison. + +Associativity: none + +Precedence: 10 + +## Greater Than + +> *e1* `>` *e2* + +Arithmetic/lexicographic comparison. + +Associativity: none + +Precedence: 10 + +## Greater Than or Equal To + +> *e1* `>=` *e2* + +Arithmetic/lexicographic comparison. + +Associativity: none + +Precedence: 10 + +## Equality + +> *e1* `==` *e2* + +Equality. + +Associativity: none + +Precedence: 11 + +## Inequality + +> *e1* `!=` *e2* + +Inequality. + +Associativity: none + +Precedence: 11 + +## Logical AND + +> *e1* `&&` *e2* + +Logical AND. + +Associativity: left + +Precedence: 12 + +## Logical OR + +> *e1* || *e2* + +Logical OR. + +Associativity: left + +Precedence: 13 + +## Logical Implication + +> *e1* `->` *e2* + +Logical implication (equivalent to !e1 || e2). + +Associativity: none + +Precedence: 14 -| Name | Syntax | Description | Associativity | Precedence | -| ------------------------ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ---------- | -| Select | *e* `.` *attrpath* \[ `or` *def* \] | Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. | none | 1 | -| Application | *e1* *e2* | Call function *e1* with argument *e2*. | left | 2 | -| Arithmetic Negation | `-` *e* | Arithmetic negation. | none | 3 | -| Has Attribute | *e* `?` *attrpath* | Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. | none | 4 | -| List Concatenation | *e1* `++` *e2* | List concatenation. | right | 5 | -| Multiplication | *e1* `*` *e2*, | Arithmetic multiplication. | left | 6 | -| Division | *e1* `/` *e2* | Arithmetic division. | left | 6 | -| Addition | *e1* `+` *e2* | Arithmetic addition. | left | 7 | -| Subtraction | *e1* `-` *e2* | Arithmetic subtraction. | left | 7 | -| String Concatenation | *string1* `+` *string2* | String concatenation. | left | 7 | -| Not | `!` *e* | Boolean negation. | none | 8 | -| Update | *e1* `//` *e2* | Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). | right | 9 | -| Less Than | *e1* `<` *e2*, | Arithmetic/lexicographic comparison. | none | 10 | -| Less Than or Equal To | *e1* `<=` *e2* | Arithmetic/lexicographic comparison. | none | 10 | -| Greater Than | *e1* `>` *e2* | Arithmetic/lexicographic comparison. | none | 10 | -| Greater Than or Equal To | *e1* `>=` *e2* | Arithmetic/lexicographic comparison. | none | 10 | -| Equality | *e1* `==` *e2* | Equality. | none | 11 | -| Inequality | *e1* `!=` *e2* | Inequality. | none | 11 | -| Logical AND | *e1* `&&` *e2* | Logical AND. | left | 12 | -| Logical OR | *e1* || *e2* | Logical OR. | left | 13 | -| Logical Implication | *e1* `->` *e2* | Logical implication (equivalent to !e1 || e2). | none | 14 | From 63b640e0c224fb959396f287b0db0c746a06c747 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 22 Dec 2022 22:10:11 +0100 Subject: [PATCH 89/97] reword descriptions of operators add notes on semantics where appropriate --- doc/manual/src/language/operators.md | 99 +++++++++++++++++----------- doc/manual/src/language/values.md | 2 + 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index aeb77b72a..e3a00d32b 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -1,36 +1,36 @@ # Operators -## Select +## Attribute selection > *e* `.` *attrpath* \[ `or` *def* \] -Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. +Select the attribute denoted by attribute path *attrpath* from attribute set *e*. +An attribute path is a dot-separated list of attribute names. +If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. Associativity: none Precedence: 1 -## Application +## Function application -> *e1* *e2* +> *f* *e* -Call function *e1* with argument *e2*. +Call function *f* with argument *e*. Associativity: left Precedence: 2 -## Arithmetic Negation +## Arithmetic negation > `-` *e* -Arithmetic negation. - Associativity: none Precedence: 3 -## Has Attribute +## Has attribute > *e* `?` *attrpath* @@ -40,11 +40,11 @@ Associativity: none Precedence: 4 -## List Concatenation +## List concatenation > *e1* `++` *e2* -List concatenation. +Concatenate lists *e1* and *e2*. Associativity: right @@ -54,7 +54,7 @@ Precedence: 5 > *e1* `*` *e2*, -Arithmetic multiplication. +Multiply numbers *e1* and *e2*. Associativity: left @@ -64,7 +64,7 @@ Precedence: 6 > *e1* `/` *e2* -Arithmetic division. +Divide numbers *e1* and *e2*. Associativity: left @@ -74,7 +74,7 @@ Precedence: 6 > *e1* `+` *e2* -Arithmetic addition. +Add numbers *e1* and *e2*. Associativity: left @@ -84,77 +84,90 @@ Precedence: 7 > *e1* `-` *e2* -Arithmetic subtraction. +Subtract numbers *e2* from *e1*. Associativity: left Precedence: 7 -## String Concatenation +## String concatenation > *string1* `+` *string2* -String concatenation. +Concatenate *string1* and *string1* and merge their string contexts. Associativity: left Precedence: 7 -## Not +## Logical negation (`NOT`) > `!` *e* -Boolean negation. +Negate the Boolean value *e*. Associativity: none Precedence: 8 -## Update +## Merge attribute sets > *e1* `//` *e2* -Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). +Return a set consisting of all the attributes in *e1* and *e2*. +If an attribute name is present in both, the attribute value from the former is taken. Associativity: right Precedence: 9 -## Less Than +## Less than > *e1* `<` *e2*, -Arithmetic/lexicographic comparison. +- Arithmetic comparison for numbers +- Lexicographic comparison for strings and paths +- Lexicographic comparison for lists: + Elements at the same index in both lists are compared according to their type and skipped if they are equal. Associativity: none Precedence: 10 -## Less Than or Equal To +## Less than or equal to > *e1* `<=` *e2* -Arithmetic/lexicographic comparison. +- Arithmetic comparison for numbers +- Lexicographic comparison for strings and paths +- Lexicographic comparison for lists: + Elements at the same index in both lists are compared according to their type and skipped if they are equal. Associativity: none Precedence: 10 -## Greater Than +## Greater than > *e1* `>` *e2* -Arithmetic/lexicographic comparison. +- Arithmetic comparison for numbers +- Lexicographic comparison for strings and paths +- Lexicographic comparison for lists: + Elements at the same index in both lists are compared according to their type and skipped if they are equal. Associativity: none Precedence: 10 -## Greater Than or Equal To +## Greater than or equal to > *e1* `>=` *e2* -Arithmetic/lexicographic comparison. +- Arithmetic comparison for numbers +- Lexicographic comparison for strings and paths +- Lexicographic comparison for lists: + Elements at the same index in both lists are compared according to their type and skipped if they are equal. Associativity: none @@ -164,7 +177,12 @@ Precedence: 10 > *e1* `==` *e2* -Equality. +Check *e1* and *e2* for equality. + +- Attribute sets and lists are compared recursively, and therefore are fully evaluated. +- Comparison of functions always returns `false`. +- Integers are coerced to floating point numbers if compared to floating point numbers. +- Floating point numbers only differ up to a limited precision. Associativity: none @@ -174,39 +192,40 @@ Precedence: 11 > *e1* `!=` *e2* -Inequality. +Equivalent to `! (`*e1* `==` *e2* `)` Associativity: none Precedence: 11 -## Logical AND +## Logical conjunction (`AND`) > *e1* `&&` *e2* -Logical AND. +Return `true` if and only if both `e1` and `e2` evaluate to `true`, otherwise `false`. Associativity: left Precedence: 12 -## Logical OR +## Logical disjunction (`OR`) -> *e1* || *e2* +> *e1* `||` *e2* -Logical OR. +Return `true` if at least `e1` or `e2` evaluate to `true`, otherwise `false`. Associativity: left Precedence: 13 -## Logical Implication +## Logical implication -> *e1* `->` *e2* +> *e1* `->` *e2* -Logical implication (equivalent to !e1 || e2). +Return `false` if *e1* evaluates to `true` and *e2* evaluates to `false`, otherwise `true`. + +Equivalent to `!`*e1* `||` *e2*. Associativity: none Precedence: 14 - diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md index 08baa8f95..d92f287a3 100644 --- a/doc/manual/src/language/values.md +++ b/doc/manual/src/language/values.md @@ -89,6 +89,8 @@ return integers, whereas any operation involving at least one floating point number will have a floating point number as a result. + Floating point numbers only differ up to a limited precision. + - Path *Paths*, e.g., `/bin/sh` or `./builder.sh`. A path must contain at From 969e5ad5bfc6527b8205182e50ba4ec216f218a0 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 22 Dec 2022 22:11:17 +0100 Subject: [PATCH 90/97] add semantics of overloaded `+` operator --- doc/manual/src/language/operators.md | 58 +++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index e3a00d32b..0c8cc6f57 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -70,16 +70,6 @@ Associativity: left Precedence: 6 -## Addition - -> *e1* `+` *e2* - -Add numbers *e1* and *e2*. - -Associativity: left - -Precedence: 7 - ## Subtraction > *e1* `-` *e2* @@ -90,6 +80,16 @@ Associativity: left Precedence: 7 +## Addition + +> *e1* `+` *e2* + +Add numbers *e1* and *e2*. + +Associativity: left + +Precedence: 7 + ## String concatenation > *string1* `+` *string2* @@ -100,6 +100,44 @@ Associativity: left Precedence: 7 +## Path concatenation + +> *path1* `+` *path2* + +Concatenate two paths. +The result is a path. + +## Path and string concatenation + +> *path* `+` *string* + +Concatenate *path* with *string*. +The result is a path. + +> **Note** +> +> The string must not have a string context that refers to a store path. + +Associativity: left + +Precedence: 7 + +## String and path concatenation + +> *string* `+` *path* + +Concatenate *string* with *path*. +The result is a string. + +> **Important** +> +> The file or directory at *path* must exist and is copied to the store +> The path appears in the result as the corresponding store path. + +Associativity: left + +Precedence: 7 + ## Logical negation (`NOT`) > `!` *e* From 7b2b9e3648920077fb53ced4f0b3bec3e0db5729 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 22 Dec 2022 22:25:37 +0100 Subject: [PATCH 91/97] use more self-explanatory placeholder names --- doc/manual/src/language/operators.md | 121 +++++++++++---------------- 1 file changed, 50 insertions(+), 71 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 0c8cc6f57..459ea9945 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -2,11 +2,11 @@ ## Attribute selection -> *e* `.` *attrpath* \[ `or` *def* \] +> *attrset* `.` *attrpath* \[ `or` *value* \] -Select the attribute denoted by attribute path *attrpath* from attribute set *e*. +Select the attribute denoted by attribute path *attrpath* from attribute set *attrset*. An attribute path is a dot-separated list of attribute names. -If the attribute doesn’t exist, return *def* if provided, otherwise abort evaluation. +If the attribute doesn’t exist, return *value* if provided, otherwise abort evaluation. Associativity: none @@ -14,9 +14,9 @@ Precedence: 1 ## Function application -> *f* *e* +> *f* *a* -Call function *f* with argument *e*. +Call function *f* with argument *a*. Associativity: left @@ -24,7 +24,9 @@ Precedence: 2 ## Arithmetic negation -> `-` *e* +> `-` *n* + +Flip the sign of the number *n*. Associativity: none @@ -32,9 +34,9 @@ Precedence: 3 ## Has attribute -> *e* `?` *attrpath* +> *attrset* `?` *attrpath* -Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. +Test whether attribute set *attrset* contains the attribute denoted by *attrpath*; return `true` or `false`. Associativity: none @@ -42,9 +44,9 @@ Precedence: 4 ## List concatenation -> *e1* `++` *e2* +> *list1* `++` *list2* -Concatenate lists *e1* and *e2*. +Concatenate lists *list1* and *list2*. Associativity: right @@ -52,9 +54,9 @@ Precedence: 5 ## Multiplication -> *e1* `*` *e2*, +> *n1* `*` *n2*, -Multiply numbers *e1* and *e2*. +Multiply numbers *n1* and *n2*. Associativity: left @@ -62,9 +64,9 @@ Precedence: 6 ## Division -> *e1* `/` *e2* +> *n1* `/` *n2* -Divide numbers *e1* and *e2*. +Divide numbers *n1* and *n2*. Associativity: left @@ -72,9 +74,9 @@ Precedence: 6 ## Subtraction -> *e1* `-` *e2* +> *n1* `-` *n2* -Subtract numbers *e2* from *e1*. +Subtract numbers *n2* from *n1*. Associativity: left @@ -82,9 +84,9 @@ Precedence: 7 ## Addition -> *e1* `+` *e2* +> *n1* `+` *n2* -Add numbers *e1* and *e2*. +Add numbers *n1* and *n2*. Associativity: left @@ -140,82 +142,59 @@ Precedence: 7 ## Logical negation (`NOT`) -> `!` *e* +> `!` *b* -Negate the Boolean value *e*. +Negate the Boolean value *b*. Associativity: none Precedence: 8 -## Merge attribute sets +## Update -> *e1* `//` *e2* +> *attrset1* `//` *attrset1* -Return a set consisting of all the attributes in *e1* and *e2*. +Update attribute set *attrset1* with names and values from *attrset2*. + +The returned attribute set will have of all the attributes in *e1* and *e2*. If an attribute name is present in both, the attribute value from the former is taken. Associativity: right Precedence: 9 -## Less than +## Comparison + +- Arithmetic comparison for numbers +- Lexicographic comparison for strings and paths +- Lexicographic comparison for lists: + Elements at the same index in both lists are compared according to their type and skipped if they are equal. + +Associativity: none + +Precedence: 10 + +### Less than > *e1* `<` *e2*, -- Arithmetic comparison for numbers -- Lexicographic comparison for strings and paths -- Lexicographic comparison for lists: - Elements at the same index in both lists are compared according to their type and skipped if they are equal. - -Associativity: none - -Precedence: 10 - -## Less than or equal to +### Less than or equal to > *e1* `<=` *e2* -- Arithmetic comparison for numbers -- Lexicographic comparison for strings and paths -- Lexicographic comparison for lists: - Elements at the same index in both lists are compared according to their type and skipped if they are equal. - -Associativity: none - -Precedence: 10 - -## Greater than +### Greater than > *e1* `>` *e2* -- Arithmetic comparison for numbers -- Lexicographic comparison for strings and paths -- Lexicographic comparison for lists: - Elements at the same index in both lists are compared according to their type and skipped if they are equal. - -Associativity: none - -Precedence: 10 - -## Greater than or equal to +### Greater than or equal to > *e1* `>=` *e2* -- Arithmetic comparison for numbers -- Lexicographic comparison for strings and paths -- Lexicographic comparison for lists: - Elements at the same index in both lists are compared according to their type and skipped if they are equal. - -Associativity: none - -Precedence: 10 - ## Equality > *e1* `==` *e2* -Check *e1* and *e2* for equality. +Check expressions *e1* and *e2* for value equality. - Attribute sets and lists are compared recursively, and therefore are fully evaluated. - Comparison of functions always returns `false`. @@ -238,9 +217,9 @@ Precedence: 11 ## Logical conjunction (`AND`) -> *e1* `&&` *e2* +> *b1* `&&` *b2* -Return `true` if and only if both `e1` and `e2` evaluate to `true`, otherwise `false`. +Return `true` if and only if both `b1` and `b2` evaluate to `true`, otherwise `false`. Associativity: left @@ -248,9 +227,9 @@ Precedence: 12 ## Logical disjunction (`OR`) -> *e1* `||` *e2* +> *b1* `||` *b2* -Return `true` if at least `e1` or `e2` evaluate to `true`, otherwise `false`. +Return `true` if at least one of `b1` or `b2` evaluate to `true`, otherwise `false`. Associativity: left @@ -258,11 +237,11 @@ Precedence: 13 ## Logical implication -> *e1* `->` *e2* +> *b1* `->` *b2* -Return `false` if *e1* evaluates to `true` and *e2* evaluates to `false`, otherwise `true`. +Return `false` if *b1* evaluates to `true` and *b2* evaluates to `false`, otherwise `true`. -Equivalent to `!`*e1* `||` *e2*. +Equivalent to `!`*b1* `||` *b2*. Associativity: none From 7da59e94ae6d5123acbca7bdbbef4d08b02fc709 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 23 Dec 2022 08:58:19 +0100 Subject: [PATCH 92/97] add links to documentation for data types --- doc/manual/src/language/operators.md | 56 ++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 459ea9945..1f11e6ac5 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -4,7 +4,7 @@ > *attrset* `.` *attrpath* \[ `or` *value* \] -Select the attribute denoted by attribute path *attrpath* from attribute set *attrset*. +Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*. An attribute path is a dot-separated list of attribute names. If the attribute doesn’t exist, return *value* if provided, otherwise abort evaluation. @@ -16,7 +16,7 @@ Precedence: 1 > *f* *a* -Call function *f* with argument *a*. +Call [function] *f* with argument *a*. Associativity: left @@ -26,7 +26,7 @@ Precedence: 2 > `-` *n* -Flip the sign of the number *n*. +Flip the sign of the [number] *n*. Associativity: none @@ -36,7 +36,7 @@ Precedence: 3 > *attrset* `?` *attrpath* -Test whether attribute set *attrset* contains the attribute denoted by *attrpath*; return `true` or `false`. +Test whether [attribute set] *attrset* contains the attribute denoted by *attrpath*; return `true` or `false`. Associativity: none @@ -46,7 +46,7 @@ Precedence: 4 > *list1* `++` *list2* -Concatenate lists *list1* and *list2*. +Concatenate [list]s *list1* and *list2*. Associativity: right @@ -56,7 +56,7 @@ Precedence: 5 > *n1* `*` *n2*, -Multiply numbers *n1* and *n2*. +Multiply [number]s *n1* and *n2*. Associativity: left @@ -66,7 +66,7 @@ Precedence: 6 > *n1* `/` *n2* -Divide numbers *n1* and *n2*. +Divide [number]s *n1* and *n2*. Associativity: left @@ -76,7 +76,7 @@ Precedence: 6 > *n1* `-` *n2* -Subtract numbers *n2* from *n1*. +Subtract [number] *n2* from *n1*. Associativity: left @@ -86,7 +86,7 @@ Precedence: 7 > *n1* `+` *n2* -Add numbers *n1* and *n2*. +Add [number]s *n1* and *n2*. Associativity: left @@ -96,7 +96,7 @@ Precedence: 7 > *string1* `+` *string2* -Concatenate *string1* and *string1* and merge their string contexts. +Concatenate two [string]s and merge their string contexts. Associativity: left @@ -106,19 +106,19 @@ Precedence: 7 > *path1* `+` *path2* -Concatenate two paths. +Concatenate two [path]s. The result is a path. ## Path and string concatenation > *path* `+` *string* -Concatenate *path* with *string*. +Concatenate *[path]* with *[string]*. The result is a path. > **Note** > -> The string must not have a string context that refers to a store path. +> The string must not have a string context that refers to a [store path]. Associativity: left @@ -128,13 +128,13 @@ Precedence: 7 > *string* `+` *path* -Concatenate *string* with *path*. +Concatenate *[string]* with *[path]*. The result is a string. > **Important** > -> The file or directory at *path* must exist and is copied to the store -> The path appears in the result as the corresponding store path. +> The file or directory at *path* must exist and is copied to the [store]. +> The path appears in the result as the corresponding [store path]. Associativity: left @@ -144,7 +144,7 @@ Precedence: 7 > `!` *b* -Negate the Boolean value *b*. +Negate the [Boolean] value *b*. Associativity: none @@ -154,7 +154,7 @@ Precedence: 8 > *attrset1* `//` *attrset1* -Update attribute set *attrset1* with names and values from *attrset2*. +Update [attribute set] *attrset1* with names and values from *attrset2*. The returned attribute set will have of all the attributes in *e1* and *e2*. If an attribute name is present in both, the attribute value from the former is taken. @@ -165,9 +165,9 @@ Precedence: 9 ## Comparison -- Arithmetic comparison for numbers -- Lexicographic comparison for strings and paths -- Lexicographic comparison for lists: +- Arithmetic comparison for [number]s +- Lexicographic comparison for [string]s and [path]s +- Lexicographic comparison for [list]s: Elements at the same index in both lists are compared according to their type and skipped if they are equal. Associativity: none @@ -196,8 +196,8 @@ Precedence: 10 Check expressions *e1* and *e2* for value equality. -- Attribute sets and lists are compared recursively, and therefore are fully evaluated. -- Comparison of functions always returns `false`. +- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated. +- Comparison of [function]s always returns `false`. - Integers are coerced to floating point numbers if compared to floating point numbers. - Floating point numbers only differ up to a limited precision. @@ -246,3 +246,13 @@ Equivalent to `!`*b1* `||` *b2*. Associativity: none Precedence: 14 + +[string]: ./values.md#type-string +[path]: ./values.md#type-path +[number]: ./values.md#type-number +[Boolean]: ./values.md#type-boolean +[list]: ./values.md#list +[attribute set]: ./values.md#attribute-set +[function]: ./constructs.md#functions +[store path]: ../glossary.md#gloss-store-path +[store]: ../glossary.md#gloss-store From e57165b85a48daba3649b08cff3eab3519bc8dce Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 5 Jan 2023 15:16:16 +0100 Subject: [PATCH 93/97] bring back table, extract annotations this makes the table less unwieldy, and leaves enough space for extensive explanations. --- doc/manual/src/language/operators.md | 249 +++++++++------------------ doc/manual/src/language/values.md | 7 +- 2 files changed, 82 insertions(+), 174 deletions(-) diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 1f11e6ac5..797f13bd3 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -1,117 +1,96 @@ # Operators +| Name | Syntax | Associativity | Precedence | +|----------------------------------------|--------------------------------------------|---------------|------------| +| [Attribute selection] | *attrset* `.` *attrpath* \[ `or` *expr* \] | none | 1 | +| Function application | *func* *expr* | left | 2 | +| [Arithmetic negation][arithmetic] | `-` *number* | none | 3 | +| [Has attribute] | *attrset* `?` *attrpath* | none | 4 | +| List concatenation | *list* `++` *list* | right | 5 | +| [Multiplication][arithmetic] | *number* `*` *number* | left | 6 | +| [Division][arithmetic] | *number* `/` *number* | left | 6 | +| [Subtraction][arithmetic] | *number* `-` *number* | left | 7 | +| [Addition][arithmetic] | *number* `+` *number* | left | 7 | +| [String concatenation] | *string* `+` *string* | left | 7 | +| [Path concatenation] | *path* `+` *path* | left | 7 | +| [Path and string concatenation] | *path* `+` *string* | left | 7 | +| [String and path concatenation] | *string* `+` *path* | left | 7 | +| Logical negation (`NOT`) | `!` *bool* | none | 8 | +| [Update] | *attrset* `//` *attrset* | right | 9 | +| [Less than][Comparison] | *expr* `<` *expr* | none | 10 | +| [Less than or equal to][Comparison] | *expr* `<=` *expr* | none | 10 | +| [Greater than][Comparison] | *expr* `>` *expr* | none | 10 | +| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 | +| [Equality] | *expr* `==` *expr* | none | 11 | +| Inequality | *expr* `!=` *expr* | none | 11 | +| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 | +| Logical disjunction (`OR`) | *bool* `||` *bool* | left | 13 | +| [Logical implication] | *bool* `->` *bool* | none | 14 | + +[string]: ./values.md#type-string +[path]: ./values.md#type-path +[number]: ./values.md#type-number +[list]: ./values.md#list +[attribute set]: ./values.md#attribute-set + ## Attribute selection -> *attrset* `.` *attrpath* \[ `or` *value* \] - Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*. -An attribute path is a dot-separated list of attribute names. If the attribute doesn’t exist, return *value* if provided, otherwise abort evaluation. -Associativity: none + -Precedence: 1 +An attribute path is a dot-separated list of attribute names. +An attribute name can be an identifier or a string. -## Function application +> *attrpath* = *name* [ `.` *name* ]... +> *name* = *identifier* | *string* +> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*` -> *f* *a* - -Call [function] *f* with argument *a*. - -Associativity: left - -Precedence: 2 - -## Arithmetic negation - -> `-` *n* - -Flip the sign of the [number] *n*. - -Associativity: none - -Precedence: 3 +[Attribute selection]: #attribute-selection ## Has attribute > *attrset* `?` *attrpath* -Test whether [attribute set] *attrset* contains the attribute denoted by *attrpath*; return `true` or `false`. +Test whether [attribute set] *attrset* contains the attribute denoted by *attrpath*. +The result is a [Boolean] value. -Associativity: none +[Boolean]: ./values.md#type-boolean -Precedence: 4 +[Has attribute]: #has-attribute -## List concatenation +## Arithmetic -> *list1* `++` *list2* +Numbers are type-compatible: +Pure integer operations will always return integers, whereas any operation involving at least one floating point number return a floating point number. -Concatenate [list]s *list1* and *list2*. +See also [Comparison] and [Equality]. -Associativity: right +The `+` operator is overloaded to also work on strings and paths. -Precedence: 5 - -## Multiplication - -> *n1* `*` *n2*, - -Multiply [number]s *n1* and *n2*. - -Associativity: left - -Precedence: 6 - -## Division - -> *n1* `/` *n2* - -Divide [number]s *n1* and *n2*. - -Associativity: left - -Precedence: 6 - -## Subtraction - -> *n1* `-` *n2* - -Subtract [number] *n2* from *n1*. - -Associativity: left - -Precedence: 7 - -## Addition - -> *n1* `+` *n2* - -Add [number]s *n1* and *n2*. - -Associativity: left - -Precedence: 7 +[arithmetic]: #arithmetic ## String concatenation -> *string1* `+` *string2* +> *string* `+` *string* Concatenate two [string]s and merge their string contexts. -Associativity: left - -Precedence: 7 +[String concatenation]: #string-concatenation ## Path concatenation -> *path1* `+` *path2* +> *path* `+` *path* Concatenate two [path]s. The result is a path. +[Path concatenation]: #path-concatenation + ## Path and string concatenation -> *path* `+` *string* +> *path* + *string* Concatenate *[path]* with *[string]*. The result is a path. @@ -120,13 +99,11 @@ The result is a path. > > The string must not have a string context that refers to a [store path]. -Associativity: left - -Precedence: 7 +[Path and string concatenation]: #path-and-string-concatenation ## String and path concatenation -> *string* `+` *path* +> *string* + *path* Concatenate *[string]* with *[path]*. The result is a string. @@ -136,123 +113,55 @@ The result is a string. > The file or directory at *path* must exist and is copied to the [store]. > The path appears in the result as the corresponding [store path]. -Associativity: left +[store path]: ../glossary.md#gloss-store-path +[store]: ../glossary.md#gloss-store -Precedence: 7 - -## Logical negation (`NOT`) - -> `!` *b* - -Negate the [Boolean] value *b*. - -Associativity: none - -Precedence: 8 +[Path and string concatenation]: #path-and-string-concatenation ## Update -> *attrset1* `//` *attrset1* +> *attrset1* + *attrset2* Update [attribute set] *attrset1* with names and values from *attrset2*. The returned attribute set will have of all the attributes in *e1* and *e2*. If an attribute name is present in both, the attribute value from the former is taken. -Associativity: right - -Precedence: 9 +[Update]: #update ## Comparison -- Arithmetic comparison for [number]s -- Lexicographic comparison for [string]s and [path]s -- Lexicographic comparison for [list]s: - Elements at the same index in both lists are compared according to their type and skipped if they are equal. +Comparison is -Associativity: none +- [arithmetic] for [number]s +- lexicographic for [string]s and [path]s +- item-wise lexicographic for [list]s: + elements at the same index in both lists are compared according to their type and skipped if they are equal. -Precedence: 10 +All comparison operators are implemented in terms of `<`, and the following equivalencies hold: -### Less than +| comparison | implementation | +|--------------|-----------------------| +| *a* `<=` *b* | `! (` *b* `<` *a* `)` | +| *a* `>` *b* | *b* `<` *a* | +| *a* `>=` *b* | `! (` *a* `<` *b* `)` | -> *e1* `<` *e2*, - -### Less than or equal to - -> *e1* `<=` *e2* - -### Greater than - -> *e1* `>` *e2* - -### Greater than or equal to - -> *e1* `>=` *e2* +[Comparison]: #comparison-operators ## Equality -> *e1* `==` *e2* - -Check expressions *e1* and *e2* for value equality. - - [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated. - Comparison of [function]s always returns `false`. -- Integers are coerced to floating point numbers if compared to floating point numbers. +- Numbers are type-compatible, see [arithmetic] operators. - Floating point numbers only differ up to a limited precision. -Associativity: none +[function]: ./constructs.md#functions -Precedence: 11 - -## Inequality - -> *e1* `!=` *e2* - -Equivalent to `! (`*e1* `==` *e2* `)` - -Associativity: none - -Precedence: 11 - -## Logical conjunction (`AND`) - -> *b1* `&&` *b2* - -Return `true` if and only if both `b1` and `b2` evaluate to `true`, otherwise `false`. - -Associativity: left - -Precedence: 12 - -## Logical disjunction (`OR`) - -> *b1* `||` *b2* - -Return `true` if at least one of `b1` or `b2` evaluate to `true`, otherwise `false`. - -Associativity: left - -Precedence: 13 +[Equality]: #equality ## Logical implication -> *b1* `->` *b2* - -Return `false` if *b1* evaluates to `true` and *b2* evaluates to `false`, otherwise `true`. - Equivalent to `!`*b1* `||` *b2*. -Associativity: none +[Logical implication]: #logical-implication -Precedence: 14 - -[string]: ./values.md#type-string -[path]: ./values.md#type-path -[number]: ./values.md#type-number -[Boolean]: ./values.md#type-boolean -[list]: ./values.md#list -[attribute set]: ./values.md#attribute-set -[function]: ./constructs.md#functions -[store path]: ../glossary.md#gloss-store-path -[store]: ../glossary.md#gloss-store diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md index d92f287a3..3973518ca 100644 --- a/doc/manual/src/language/values.md +++ b/doc/manual/src/language/values.md @@ -85,11 +85,10 @@ Numbers, which can be *integers* (like `123`) or *floating point* (like `123.43` or `.27e13`). - Numbers are type-compatible: pure integer operations will always - return integers, whereas any operation involving at least one - floating point number will have a floating point number as a result. + See [arithmetic] and [comparison] operators for semantics. - Floating point numbers only differ up to a limited precision. + [arithmetic]: ./operators.md#arithmetic + [comparison]: ./operators.md#comparison - Path From f1ee4ece806d109df8f6994d9e70eb7f05505709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Thu, 5 Jan 2023 18:23:30 +0100 Subject: [PATCH 94/97] Don't check NixOS modules NixOS modules can be paths. Rather than dig further down into the layer violation, don't check anything specific to NixOS modules. --- src/nix/flake.cc | 17 ----------------- tests/flakes/check.sh | 16 ++-------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 06fd87ef2..9b4cdf35a 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -381,23 +381,6 @@ struct CmdFlakeCheck : FlakeCommand auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); - if (v.isLambda()) { - if (!v.lambda.fun->hasFormals() || !v.lambda.fun->formals->ellipsis) - throw Error("module must match an open attribute set ('{ config, ... }')"); - } else if (v.type() == nAttrs) { - for (auto & attr : *v.attrs) - try { - state->forceValue(*attr.value, attr.pos); - } catch (Error & e) { - e.addTrace( - state->positions[attr.pos], - hintfmt("while evaluating the option '%s'", state->symbols[attr.name])); - throw; - } - } else - throw Error("module must be a function or an attribute set"); - // FIXME: if we have a 'nixpkgs' input, use it to - // check the module. } catch (Error & e) { e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath)); reportError(e); diff --git a/tests/flakes/check.sh b/tests/flakes/check.sh index f572aa75c..278ac7fa4 100644 --- a/tests/flakes/check.sh +++ b/tests/flakes/check.sh @@ -41,9 +41,9 @@ nix flake check $flakeDir cat > $flakeDir/flake.nix < $flakeDir/flake.nix < $flakeDir/flake.nix < Date: Fri, 6 Jan 2023 23:04:43 -0600 Subject: [PATCH 95/97] Fix typo in example for builtin function map --- src/libexpr/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9356d307e..9ef91cbc5 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2807,7 +2807,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" ]`. From c83a8174fdfd7e7f7c04584a02a0160403fb047d Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Sun, 8 Jan 2023 14:38:34 +0100 Subject: [PATCH 96/97] tests: fix for nixpkgs 22.11 runCommand now uses stdenvNoCC by default, so that needs to be included instead of the regular stdenv. --- tests/containers.nix | 16 ++++++++-------- tests/setuid.nix | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/containers.nix b/tests/containers.nix index f31f22cf6..a4856b2df 100644 --- a/tests/containers.nix +++ b/tests/containers.nix @@ -16,7 +16,7 @@ makeTest ({ { virtualisation.writableStore = true; virtualisation.diskSize = 2048; virtualisation.additionalPaths = - [ pkgs.stdenv + [ pkgs.stdenvNoCC (import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel ]; virtualisation.memorySize = 4096; @@ -38,30 +38,30 @@ makeTest ({ # Test that 'id' gives the expected result in various configurations. # Existing UIDs, sandbox. - host.succeed("nix build --no-auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-1") + host.succeed("nix build -v --no-auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-1") host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]") # Existing UIDs, no sandbox. - host.succeed("nix build --no-auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-2") + host.succeed("nix build -v --no-auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-2") host.succeed("[[ $(cat ./result) = 'uid=30001(nixbld1) gid=30000(nixbld) groups=30000(nixbld)' ]]") # Auto-allocated UIDs, sandbox. - host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-3") + host.succeed("nix build -v --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-3") host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]") # Auto-allocated UIDs, no sandbox. - host.succeed("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-4") + host.succeed("nix build -v --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-4") host.succeed("[[ $(cat ./result) = 'uid=872415232 gid=30000(nixbld) groups=30000(nixbld)' ]]") # Auto-allocated UIDs, UID range, sandbox. - host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-5 --arg uidRange true") + host.succeed("nix build -v --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-5 --arg uidRange true") host.succeed("[[ $(cat ./result) = 'uid=0(root) gid=0(root) groups=0(root)' ]]") # Auto-allocated UIDs, UID range, no sandbox. - host.fail("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-6 --arg uidRange true") + host.fail("nix build -v --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-6 --arg uidRange true") # Run systemd-nspawn in a Nix build. - host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}") + host.succeed("nix build -v --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}") host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]") ''; diff --git a/tests/setuid.nix b/tests/setuid.nix index 82efd6d54..6784615e4 100644 --- a/tests/setuid.nix +++ b/tests/setuid.nix @@ -15,7 +15,7 @@ makeTest { { virtualisation.writableStore = true; nix.settings.substituters = lib.mkForce [ ]; nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ]; - virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ]; + virtualisation.additionalPaths = [ pkgs.stdenvNoCC pkgs.pkgsi686Linux.stdenvNoCC ]; }; testScript = { nodes }: '' From 89ef26664d3339a89afa8dfa762326cf196d1622 Mon Sep 17 00:00:00 2001 From: Jeremy Fleischman Date: Mon, 9 Jan 2023 00:49:46 -0800 Subject: [PATCH 97/97] Add a pointer from "realising" to `nix log`. (#4876) --- doc/manual/src/command-ref/nix-store.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index acf29e4aa..6d0e02ca5 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -155,6 +155,12 @@ To test whether a previously-built derivation is deterministic: $ nix-build '' -A hello --check -K ``` +Use [`--read-log`](#operation---read-log) to show the stderr and stdout of a build: + +```console +$ nix-store --read-log $(nix-instantiate ./test.nix) +``` + # Operation `--serve` ## Synopsis