From a65e9e58282f3809abcbc4c264db7256ef8874e2 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Tue, 3 Dec 2024 20:38:41 +0100 Subject: [PATCH] libexpr: extract eval error creation into new type this will let us pass the capability to create debuggable eval errors without having to pass an entire EvalState. we could pass symbols and debug states around just as easily, but if we add new capabilities to our debugger we might have to change many more places than with this. Change-Id: I2f8893012e5d98a986ef1fc888234c2dd8d5e096 --- lix/libcmd/repl.cc | 4 +- lix/libexpr/attr-path.cc | 4 +- lix/libexpr/eval-cache.cc | 28 ++++---- lix/libexpr/eval-error.cc | 20 +++--- lix/libexpr/eval-error.hh | 22 ++++--- lix/libexpr/eval-inline.hh | 4 +- lix/libexpr/eval.cc | 71 +++++++++++---------- lix/libexpr/eval.hh | 19 ++++-- lix/libexpr/flake/flake.cc | 14 ++-- lix/libexpr/get-drvs.cc | 4 +- lix/libexpr/nixexpr.cc | 2 +- lix/libexpr/primops.cc | 92 +++++++++++++-------------- lix/libexpr/primops/context.cc | 12 ++-- lix/libexpr/primops/fetchMercurial.cc | 4 +- lix/libexpr/primops/fetchTree.cc | 20 +++--- lix/libexpr/primops/fromTOML.cc | 2 +- lix/libexpr/value-to-json.cc | 4 +- lix/libutil/error.cc | 4 +- lix/nix/eval.cc | 2 +- lix/nix/flake.cc | 4 +- tests/unit/libexpr/error_traces.cc | 10 +-- 21 files changed, 178 insertions(+), 168 deletions(-) diff --git a/lix/libcmd/repl.cc b/lix/libcmd/repl.cc index 1152cc1a8..b0663aa7b 100644 --- a/lix/libcmd/repl.cc +++ b/lix/libcmd/repl.cc @@ -988,7 +988,7 @@ Value * NixRepl::replOverlays() auto replInit = evalFile(sourcePath); if (!replInit->isLambda()) { - state.error( + state.errors.make( "Expected `repl-overlays` to be a lambda but found %1%: %2%", showType(*replInit), ValuePrinter(state, *replInit, errorPrintOptions) @@ -999,7 +999,7 @@ Value * NixRepl::replOverlays() if (replInit->lambda.fun->hasFormals() && !replInit->lambda.fun->formals->ellipsis) { - state.error( + state.errors.make( "Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument", "repl-overlays", "..." diff --git a/lix/libexpr/attr-path.cc b/lix/libexpr/attr-path.cc index 23de3923b..a70d5c6cf 100644 --- a/lix/libexpr/attr-path.cc +++ b/lix/libexpr/attr-path.cc @@ -65,7 +65,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::strin if (!attrIndex) { if (v->type() != nAttrs) - state.error( + state.errors.make( "the expression selected by the selection path '%1%' should be a set but is %2%", attrPath, showType(*v)).debugThrow(); @@ -88,7 +88,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::strin else { if (!v->isList()) - state.error( + state.errors.make( "the expression selected by the selection path '%1%' should be a list but is %2%", attrPath, showType(*v)).debugThrow(); diff --git a/lix/libexpr/eval-cache.cc b/lix/libexpr/eval-cache.cc index 1c6dfd18c..b041b8375 100644 --- a/lix/libexpr/eval-cache.cc +++ b/lix/libexpr/eval-cache.cc @@ -506,7 +506,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name) // evaluate to see whether 'name' exists } else return nullptr; - //error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + //errors.make("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } @@ -514,7 +514,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name) if (v.type() != nAttrs) return nullptr; - //error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + //errors.make("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); auto attr = v.attrs->get(name); @@ -580,14 +580,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.errors.make("'%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(), v.type()).debugThrow(); + root->state.errors.make("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow(); } return v.type() == nString ? v.string.s : v.path().to_string(); @@ -623,7 +623,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); + root->state.errors.make("'%s' is not a string", getAttrPathStr()).debugThrow(); } } @@ -636,7 +636,7 @@ string_t AttrCursor::getStringWithContext() } else if (v.type() == nPath) { return {v.path().to_string(), {}}; } else { - root->state.error("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow(); + root->state.errors.make("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow(); } } @@ -650,14 +650,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.errors.make("'%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.errors.make("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); return v.boolean; } @@ -672,14 +672,14 @@ NixInt AttrCursor::getInt() debug("using cached integer attribute '%s'", getAttrPathStr()); return i->x; } else - root->state.error("'%s' is not an integer", getAttrPathStr()).debugThrow(); + root->state.errors.make("'%s' is not an integer", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nInt) - root->state.error("'%s' is not an integer", getAttrPathStr()).debugThrow(); + root->state.errors.make("'%s' is not an integer", getAttrPathStr()).debugThrow(); return v.integer; } @@ -694,7 +694,7 @@ std::vector AttrCursor::getListOfStrings() debug("using cached list of strings attribute '%s'", getAttrPathStr()); return *l; } else - root->state.error("'%s' is not a list of strings", getAttrPathStr()).debugThrow(); + root->state.errors.make("'%s' is not a list of strings", getAttrPathStr()).debugThrow(); } } @@ -704,7 +704,7 @@ std::vector AttrCursor::getListOfStrings() root->state.forceValue(v, noPos); if (v.type() != nList) - root->state.error("'%s' is not a list", getAttrPathStr()).debugThrow(); + root->state.errors.make("'%s' is not a list", getAttrPathStr()).debugThrow(); std::vector res; @@ -727,14 +727,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.errors.make("'%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.errors.make("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/lix/libexpr/eval-error.cc b/lix/libexpr/eval-error.cc index 0da05eac1..cf2cdc424 100644 --- a/lix/libexpr/eval-error.cc +++ b/lix/libexpr/eval-error.cc @@ -14,7 +14,7 @@ EvalErrorBuilder EvalErrorBuilder::withExitStatus(unsigned int exitStatus) template EvalErrorBuilder EvalErrorBuilder::atPos(PosIdx pos) && { - error->err.pos = state.positions[pos]; + error->err.pos = positions[pos]; return std::move(*this); } @@ -28,7 +28,7 @@ template EvalErrorBuilder EvalErrorBuilder::withTrace(PosIdx pos, const std::string_view text) && { error->err.traces.push_front( - Trace{.pos = state.positions[pos], .hint = HintFmt(std::string(text))}); + Trace{.pos = positions[pos], .hint = HintFmt(std::string(text))}); return std::move(*this); } @@ -42,9 +42,9 @@ EvalErrorBuilder EvalErrorBuilder::withSuggestions(Suggestions & s) && template EvalErrorBuilder EvalErrorBuilder::withFrame(const Env & env, const Expr & expr) && { - if (state.debug) { - error->frame = state.debug->addTrace(DebugTrace{ - .pos = state.positions[expr.getPos()], + if (debug) { + error->frame = debug->addTrace(DebugTrace{ + .pos = positions[expr.getPos()], .expr = expr, .env = env, .hint = HintFmt("Fake frame for debugging purposes"), @@ -57,7 +57,7 @@ EvalErrorBuilder EvalErrorBuilder::withFrame(const Env & env, const Expr & template EvalErrorBuilder EvalErrorBuilder::addTrace(PosIdx pos, HintFmt hint) && { - error->addTrace(state.positions[pos], hint); + error->addTrace(positions[pos], hint); return std::move(*this); } @@ -67,18 +67,18 @@ EvalErrorBuilder EvalErrorBuilder::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) && { - addTrace(state.positions[pos], HintFmt(std::string(formatString), formatArgs...)); + addTrace(positions[pos], HintFmt(std::string(formatString), formatArgs...)); return std::move(*this); } template void EvalErrorBuilder::debugThrow() && { - if (state.debug) { - if (auto last = state.debug->traces().next()) { + if (debug) { + if (auto last = debug->traces().next()) { const Env * env = &(*last)->env; const Expr * expr = &(*last)->expr; - state.debug->onEvalError(error.get(), *env, *expr); + debug->onEvalError(error.get(), *env, *expr); } } diff --git a/lix/libexpr/eval-error.hh b/lix/libexpr/eval-error.hh index 77513943c..2ae775753 100644 --- a/lix/libexpr/eval-error.hh +++ b/lix/libexpr/eval-error.hh @@ -5,9 +5,11 @@ #include "lix/libutil/error.hh" #include "lix/libutil/types.hh" #include "lix/libexpr/pos-idx.hh" +#include "lix/libexpr/pos-table.hh" namespace nix { +struct DebugState; struct DebugTrace; struct Env; struct Expr; @@ -55,18 +57,18 @@ public: template class [[nodiscard]] EvalErrorBuilder final { - friend class EvalState; - - EvalState & state; - - template - explicit EvalErrorBuilder(EvalState & state, const Args &... args) - : state(state), error(make_box_ptr(args...)) - { - } + const PosTable & positions; + DebugState * debug; + box_ptr error; public: - box_ptr error; + template + explicit EvalErrorBuilder(const PosTable & positions, DebugState * debug, const Args &... args) + : positions(positions) + , debug{debug} + , error(make_box_ptr(args...)) + { + } [[gnu::noinline]] EvalErrorBuilder withExitStatus(unsigned int exitStatus) &&; diff --git a/lix/libexpr/eval-inline.hh b/lix/libexpr/eval-inline.hh index 38b5c84d4..882242731 100644 --- a/lix/libexpr/eval-inline.hh +++ b/lix/libexpr/eval-inline.hh @@ -91,7 +91,7 @@ inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view { forceValue(v, pos); if (v.type() != nAttrs) { - error( + errors.make( "expected a set but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -105,7 +105,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e { forceValue(v, pos); if (!v.isList()) { - error( + errors.make( "expected a list but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 44d741ce7..991ebb192 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -299,6 +299,7 @@ EvalState::EvalState( ) : nullptr } + , errors{positions, debug.get()} { countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; @@ -750,7 +751,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!fromWith->parentWith) - error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); + errors.make("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); for (size_t l = fromWith->prevWith; l; --l, env = env->up) ; fromWith = fromWith->parentWith; } @@ -981,7 +982,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( + errors.make( "expected a Boolean but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -999,7 +1000,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( + errors.make( "expected a set but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -1131,7 +1132,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.errors.make("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 */ @@ -1306,7 +1307,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } // Otherwise, we must type error. - state.error( + state.errors.make( "expected a set but found %s: %s", showType(*vCurrent), ValuePrinter(state, *vCurrent, errorPrintOptions) @@ -1333,7 +1334,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) allAttrNames.insert(state.symbols[attr.name]); } auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); - state.error("attribute '%s' missing", state.symbols[name]) + state.errors.make("attribute '%s' missing", state.symbols[name]) .atPos(pos) .withSuggestions(suggestions) .withFrame(env, *this) @@ -1469,7 +1470,7 @@ FormalsMatch matchupFormals(EvalState & state, Env & env, Displacement & displ, void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) { if (callDepth > evalSettings.maxCallDepth) - error("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); + errors.make("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); CallDepth _level(callDepth); auto trace = evalSettings.traceFunctionCalls @@ -1531,7 +1532,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & ); for (auto const & missingArg : formalsMatch.missing) { auto const missing = symbols[missingArg]; - error("function '%s' called without required argument '%s'", lambda.getName(symbols), missing) + errors.make("function '%s' called without required argument '%s'", lambda.getName(symbols), missing) .atPos(lambda.pos) .withTrace(pos, "from call site") .withFrame(*fun.lambda.env, lambda) @@ -1544,7 +1545,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & formalNames.insert(symbols[formal.name]); } auto sug = Suggestions::bestMatches(formalNames, unex); - error("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex) + errors.make("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex) .atPos(lambda.pos) .withTrace(pos, "from call site") .withSuggestions(sug) @@ -1685,7 +1686,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - error( + errors.make( "attempt to call something which is not a function but %1%: %2%", showType(vCur), ValuePrinter(*this, vCur, errorPrintOptions)) @@ -1772,7 +1773,7 @@ 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%') + errors.make(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 @@ -1808,7 +1809,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.errors.make("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); } body->eval(state, env, v); } @@ -1983,7 +1984,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) if (auto checked = newN.valueChecked(); checked.has_value()) { n = NixInt(*checked); } else { - state.error("integer overflow in adding %1% + %2%", n, vTmp.integer).atPos(i_pos).debugThrow(); + state.errors.make("integer overflow in adding %1% + %2%", n, vTmp.integer).atPos(i_pos).debugThrow(); } } else if (vTmp.type() == nFloat) { // Upgrade the type from int to float; @@ -1991,14 +1992,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n.value; nf += vTmp.fpoint; } else - state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); + state.errors.make("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.value; } 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.errors.make("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 @@ -2020,7 +2021,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.errors.make("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); v.mkPath(CanonPath(canonPath(str()))); } else v.mkStringMove(c_str(), context); @@ -2035,7 +2036,7 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v) void ExprBlackHole::eval(EvalState & state, Env & env, Value & v) { - state.error("infinite recursion encountered") + state.errors.make("infinite recursion encountered") .atPos(v.determinePos(noPos)) .debugThrow(); } @@ -2099,7 +2100,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - error( + errors.make( "expected an integer but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2121,7 +2122,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err if (v.type() == nInt) return v.integer.value; else if (v.type() != nFloat) - error( + errors.make( "expected a float but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2139,7 +2140,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - error( + errors.make( "expected a Boolean but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2165,7 +2166,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - error( + errors.make( "expected a function but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2182,7 +2183,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - error( + errors.make( "expected a string but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2215,7 +2216,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(); + errors.make("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; } @@ -2280,7 +2281,7 @@ BackedStringView EvalState::coerceToString( return std::move(*maybeString); auto i = v.attrs->find(s.outPath); if (i == v.attrs->end()) { - error( + errors.make( "cannot coerce %1% to a string: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) @@ -2330,7 +2331,7 @@ BackedStringView EvalState::coerceToString( } } - error("cannot coerce %1% to a string: %2%", + errors.make("cannot coerce %1% to a string: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions) ) @@ -2342,7 +2343,7 @@ BackedStringView EvalState::coerceToString( StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path) { if (nix::isDerivation(path.path.abs())) - error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); + errors.make("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); auto i = srcToStore.find(path); @@ -2367,7 +2368,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext { auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (path == "" || path[0] != '/') - error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); + errors.make("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); return CanonPath(path); } @@ -2377,7 +2378,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); + errors.make("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } @@ -2387,7 +2388,7 @@ std::pair EvalState::coerceToSingleDerivedP auto s = forceString(v, context, pos, errorCtx); auto csize = context.size(); if (csize != 1) - error( + errors.make( "string '%s' has %d entries in its context. It should only have exactly one entry", s, csize) .withTrace(pos, errorCtx).debugThrow(); @@ -2396,7 +2397,7 @@ std::pair EvalState::coerceToSingleDerivedP return std::move(o); }, [&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath { - error( + errors.make( "string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time", s).withTrace(pos, errorCtx).debugThrow(); }, @@ -2421,13 +2422,13 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value & error message. */ std::visit(overloaded { [&](const SingleDerivedPath::Opaque & o) { - error( + errors.make( "path string '%s' has context with the different path '%s'", s, sExpected) .withTrace(pos, errorCtx).debugThrow(); }, [&](const SingleDerivedPath::Built & b) { - error( + errors.make( "string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'", s, b.output, b.drvPath->to_string(*store), sExpected) .withTrace(pos, errorCtx).debugThrow(); @@ -2512,7 +2513,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v case nThunk: // Must not be left by forceValue default: - error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); + errors.make("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); } } @@ -2755,7 +2756,7 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_ if (path.starts_with("nix/")) return CanonPath(concatStrings(corepkgsPrefix, path.substr(4))); - error( + errors.make( 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)", @@ -2817,7 +2818,7 @@ std::optional EvalState::resolveSearchPathPath(const SearchPath::Pa std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const { - state.error( + state.errors.make( "cannot coerce %1% to a string: %2%", showType(), *this ).atPos(pos).debugThrow(); } diff --git a/lix/libexpr/eval.hh b/lix/libexpr/eval.hh index e35ec35b1..298f3ddba 100644 --- a/lix/libexpr/eval.hh +++ b/lix/libexpr/eval.hh @@ -348,6 +348,18 @@ struct EvalRuntimeCaches std::map> fileEval; }; +struct EvalErrorContext +{ + const PosTable & positions; + DebugState * debug; + + template + [[gnu::noinline]] + EvalErrorBuilder make(const Args & ... args) { + return EvalErrorBuilder(positions, debug, args...); + } +}; + class EvalState { @@ -389,12 +401,7 @@ public: const ref buildStore; std::unique_ptr debug; - - template - [[gnu::noinline]] - EvalErrorBuilder error(const Args & ... args) { - return EvalErrorBuilder(*this, args...); - } + EvalErrorContext errors; private: diff --git a/lix/libexpr/flake/flake.cc b/lix/libexpr/flake/flake.cc index 202a05ace..6854cf738 100644 --- a/lix/libexpr/flake/flake.cc +++ b/lix/libexpr/flake/flake.cc @@ -142,14 +142,14 @@ static FlakeInput parseFlakeInput(EvalState & state, auto intValue = attr.value->integer.value; if (intValue < 0) { - state.error("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow(); + state.errors.make("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow(); } uint64_t asUnsigned = intValue; attrs.emplace(state.symbols[attr.name], asUnsigned); break; } default: - state.error("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", + state.errors.make("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", state.symbols[attr.name], showType(*attr.value)).debugThrow(); } #pragma GCC diagnostic pop @@ -247,7 +247,7 @@ static Flake getFlake( // Enforce that 'flake.nix' is a direct attrset, not a computation. if (!(dynamic_cast(&flakeExpr))) { - state.error("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow(); + state.errors.make("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow(); } Value vInfo; @@ -307,14 +307,14 @@ static Flake getFlake( std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) - state.error("list element in flake configuration setting '%s' is %s while a string is expected", + state.errors.make("list element in flake configuration setting '%s' is %s while a string is expected", state.symbols[setting.name], showType(*setting.value)).debugThrow(); ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, "")); } flake.config.settings.emplace(state.symbols[setting.name], ss); } else - state.error("flake configuration setting '%s' is %s", + state.errors.make("flake configuration setting '%s' is %s", state.symbols[setting.name], showType(*setting.value)).debugThrow(); } } @@ -858,7 +858,7 @@ void prim_flakeRefToString( auto intValue = attr.value->integer.value; if (intValue < 0) { - state.error("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); + state.errors.make("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); } uint64_t asUnsigned = intValue; @@ -870,7 +870,7 @@ void prim_flakeRefToString( attrs.emplace(state.symbols[attr.name], std::string(attr.value->str())); } else { - state.error( + state.errors.make( "flake reference attribute sets may only contain integers, Booleans, " "and strings, but attribute '%s' is %s", state.symbols[attr.name], diff --git a/lix/libexpr/get-drvs.cc b/lix/libexpr/get-drvs.cc index 91ae4cfc6..59c30985c 100644 --- a/lix/libexpr/get-drvs.cc +++ b/lix/libexpr/get-drvs.cc @@ -50,7 +50,7 @@ std::string DrvInfo::queryName() { if (name == "" && attrs) { auto i = attrs->find(state->s.name); - if (i == attrs->end()) state->error("derivation name missing").debugThrow(); + if (i == attrs->end()) state->errors.make("derivation name missing").debugThrow(); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation"); } return name; @@ -446,7 +446,7 @@ static void getDerivations(EvalState & state, Value & vIn, return; } else if (v.type() != nAttrs) { - state.error( + state.errors.make( "expression was expected to be a derivation or collection of derivations, but instead was %s", showType(v.type(), true) ).debugThrow(); diff --git a/lix/libexpr/nixexpr.cc b/lix/libexpr/nixexpr.cc index 4b051ff5e..3c017e77d 100644 --- a/lix/libexpr/nixexpr.cc +++ b/lix/libexpr/nixexpr.cc @@ -364,7 +364,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr & enclosing `with'. If there is no `with', then we can issue an "undefined variable" error now. */ if (withLevel == -1) - es.error( + es.errors.make( "undefined variable '%1%'", es.symbols[name] ).atPos(pos).debugThrow(); diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index aba5d43ec..bfe511020 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -47,7 +47,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context) for (auto & c : context) { auto ensureValid = [&](const StorePath & p) { if (!store->isValidPath(p)) - error(store->printStorePath(p)).debugThrow(); + errors.make(store->printStorePath(p)).debugThrow(); }; std::visit(overloaded { [&](const NixStringContextElem::Built & b) { @@ -74,7 +74,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context) if (drvs.empty()) return {}; if (!evalSettings.enableImportFromDerivation) - error( + errors.make( "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", drvs.begin()->to_string(*store) ).debugThrow(); @@ -285,16 +285,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!handle) - state.error("could not open '%1%': %2%", path, dlerror()).debugThrow(); + state.errors.make("could not open '%1%': %2%", path, dlerror()).debugThrow(); dlerror(); ValueInitializer func = reinterpret_cast(dlsym(handle, sym.c_str())); if(!func) { char *message = dlerror(); if (message) - state.error("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow(); + state.errors.make("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow(); else - state.error("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow(); + state.errors.make("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow(); } (func)(state, v); @@ -310,7 +310,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) auto elems = args[0]->listElems(); auto count = args[0]->listSize(); if (count == 0) - state.error("at least one argument to 'exec' required").atPos(pos).debugThrow(); + state.errors.make("at least one argument to 'exec' required").atPos(pos).debugThrow(); NixStringContext context; auto program = state.coerceToString(pos, *elems[0], context, "while evaluating the first element of the argument passed to builtins.exec", @@ -452,7 +452,7 @@ struct CompareValues if (v1->type() == nInt && v2->type() == nFloat) return v1->integer.value < v2->fpoint; if (v1->type() != v2->type()) - state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); + state.errors.make("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); // Allow selecting a subset of enum values #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" @@ -477,7 +477,7 @@ struct CompareValues } } default: - state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); + state.errors.make("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); #pragma GCC diagnostic pop } } catch (Error & e) { @@ -503,7 +503,7 @@ static Bindings::iterator getAttr( { Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { - state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); + state.errors.make("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); } return value; } @@ -591,7 +591,7 @@ static void prim_abort(EvalState & state, const PosIdx pos, Value * * args, Valu NixStringContext context; auto s = state.coerceToString(pos, *args[0], context, "while evaluating the error message passed to builtins.abort").toOwned(); - state.error("evaluation aborted with the following error message: '%1%'", s).debugThrow(); + state.errors.make("evaluation aborted with the following error message: '%1%'", s).debugThrow(); } static void prim_throw(EvalState & state, const PosIdx pos, Value * * args, Value & v) @@ -599,7 +599,7 @@ static void prim_throw(EvalState & state, const PosIdx pos, Value * * args, Valu NixStringContext context; auto s = state.coerceToString(pos, *args[0], context, "while evaluating the error message passed to builtin.throw").toOwned(); - state.error(s).debugThrow(); + state.errors.make(s).debugThrow(); } static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) @@ -827,7 +827,7 @@ drvName, Bindings * attrs, Value & v) experimentalFeatureSettings.require(Xp::DynamicDerivations); ingestionMethod = TextIngestionMethod {}; } else - state.error( + state.errors.make( "invalid value '%s' for 'outputHashMode' attribute", s ).atPos(v).debugThrow(); }; @@ -836,7 +836,7 @@ drvName, Bindings * attrs, Value & v) outputs.clear(); for (auto & j : ss) { if (outputs.find(j) != outputs.end()) - state.error("duplicate derivation output '%1%'", j) + state.errors.make("duplicate derivation output '%1%'", j) .atPos(v) .debugThrow(); /* !!! Check whether j is a valid attribute @@ -845,13 +845,13 @@ drvName, Bindings * attrs, Value & v) then we'd have an attribute ‘drvPath’ in the resulting set. */ if (j == "drv") - state.error("invalid derivation output name 'drv'") + state.errors.make("invalid derivation output name 'drv'") .atPos(v) .debugThrow(); outputs.insert(j); } if (outputs.empty()) - state.error("derivation cannot have an empty set of outputs") + state.errors.make("derivation cannot have an empty set of outputs") .atPos(v) .debugThrow(); }; @@ -989,12 +989,12 @@ drvName, Bindings * attrs, Value & v) /* Do we have all required attributes? */ if (drv.builder == "") - state.error("required attribute 'builder' missing") + state.errors.make("required attribute 'builder' missing") .atPos(v) .debugThrow(); if (drv.platform == "") - state.error("required attribute 'system' missing") + state.errors.make("required attribute 'system' missing") .atPos(v) .debugThrow(); @@ -1004,7 +1004,7 @@ drvName, Bindings * attrs, Value & v) outputs.size() == 1 && *(outputs.begin()) == "out")) { - state.error( + state.errors.make( "derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension ).atPos(v).debugThrow(); @@ -1016,7 +1016,7 @@ drvName, Bindings * attrs, Value & v) Ignore `__contentAddressed` because fixed output derivations are already content addressed. */ if (outputs.size() != 1 || *(outputs.begin()) != "out") - state.error( + state.errors.make( "multiple outputs are not supported in fixed-output derivations" ).atPos(v).debugThrow(); @@ -1037,7 +1037,7 @@ drvName, Bindings * attrs, Value & v) else if (contentAddressed || isImpure) { if (contentAddressed && isImpure) - state.error("derivation cannot be both content-addressed and impure") + state.errors.make("derivation cannot be both content-addressed and impure") .atPos(v).debugThrow(); auto ht = parseHashTypeOpt(outputHashAlgo).value_or(HashType::SHA256); @@ -1079,7 +1079,7 @@ drvName, Bindings * attrs, Value & v) for (auto & i : outputs) { auto h = get(hashModulo.hashes, i); if (!h) - state.error( + state.errors.make( "derivation produced no hash for output '%s'", i ).atPos(v).debugThrow(); @@ -1167,7 +1167,7 @@ static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Val static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { if (evalSettings.pureEval) - state.error( + state.errors.make( "'%s' is not allowed in pure evaluation mode", "builtins.storePath" ).atPos(pos).debugThrow(); @@ -1180,7 +1180,7 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, if (!state.store->isStorePath(path.abs())) path = CanonPath(canonPath(path.abs(), true)); if (!state.store->isInStore(path.abs())) - state.error("path '%1%' is not in the Nix store", path) + state.errors.make("path '%1%' is not in the Nix store", path) .atPos(pos).debugThrow(); auto path2 = state.store->toStorePath(path.abs()).first; if (!settings.readOnlyMode) @@ -1257,7 +1257,7 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V auto path = realisePath(state, pos, *args[0]); auto s = path.readFile(); if (s.find((char) 0) != std::string::npos) - state.error( + state.errors.make( "the contents of the file '%1%' cannot be represented as a Nix string", path ).atPos(pos).debugThrow(); @@ -1308,7 +1308,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V auto rewrites = state.realiseContext(context); path = rewriteStrings(path, rewrites); } catch (InvalidPathError & e) { - state.error( + state.errors.make( "cannot find '%1%', since path '%2%' is not valid", path, e.path @@ -1332,7 +1332,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile"); std::optional ht = parseHashType(type); if (!ht) - state.error("unknown hash type '%1%'", type).atPos(pos).debugThrow(); + state.errors.make("unknown hash type '%1%'", type).atPos(pos).debugThrow(); auto path = realisePath(state, pos, *args[1]); @@ -1461,7 +1461,7 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val if (auto p = std::get_if(&c.raw)) refs.insert(p->path); else - state.error( + state.errors.make( "files created by %1% may not reference derivations, but %2% references %3%", "builtins.toFile", name, @@ -1548,7 +1548,7 @@ static void addPath( auto dstPath = fetchToStore( *state.store, CanonPath(path), name, method, &filter, state.repair); if (expectedHash && expectedStorePath != dstPath) - state.error( + state.errors.make( "store path mismatch in (possibly filtered) path added from '%s'", path ).atPos(pos).debugThrow(); @@ -1595,13 +1595,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value else if (n == "sha256") expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashType::SHA256); else - state.error( + state.errors.make( "unsupported argument '%1%' to 'addPath'", state.symbols[attr.name] ).atPos(attr.pos).debugThrow(); } if (!path) - state.error( + state.errors.make( "missing required 'path' attribute in the first argument to builtins.path" ).atPos(pos).debugThrow(); if (name.empty()) @@ -1908,7 +1908,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg return; } if (!args[0]->isLambda()) - state.error("'functionArgs' requires a function").atPos(pos).debugThrow(); + state.errors.make("'functionArgs' requires a function").atPos(pos).debugThrow(); if (!args[0]->lambda.fun->hasFormals()) { v.mkAttrs(&Bindings::EMPTY); @@ -2005,7 +2005,7 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val { state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt"); if (n < 0 || (unsigned int) n >= list.listSize()) - state.error( + state.errors.make( "list index %1% is out of bounds", n ).atPos(pos).debugThrow(); @@ -2033,7 +2033,7 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value { state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail"); if (args[0]->listSize() == 0) - state.error("'tail' called on an empty list").atPos(pos).debugThrow(); + state.errors.make("'tail' called on an empty list").atPos(pos).debugThrow(); v = state.mem.newList(args[0]->listSize() - 1); for (unsigned int n = 0; n < v.listSize(); ++n) @@ -2180,7 +2180,7 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va auto len_ = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList").value; if (len_ < 0) - state.error("cannot create list of size %1%", len_).atPos(pos).debugThrow(); + state.errors.make("cannot create list of size %1%", len_).atPos(pos).debugThrow(); size_t len = len_; @@ -2353,7 +2353,7 @@ static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value if (auto result = result_.valueChecked(); result.has_value()) { v.mkInt(*result); } else { - state.error("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow(); + state.errors.make("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow(); } } } @@ -2374,7 +2374,7 @@ static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value if (auto result = result_.valueChecked(); result.has_value()) { v.mkInt(*result); } else { - state.error("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow(); + state.errors.make("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow(); } } } @@ -2395,7 +2395,7 @@ static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value if (auto result = result_.valueChecked(); result.has_value()) { v.mkInt(*result); } else { - state.error("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow(); + state.errors.make("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow(); } } } @@ -2407,7 +2407,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division"); if (f2 == 0) - state.error("division by zero").atPos(pos).debugThrow(); + state.errors.make("division by zero").atPos(pos).debugThrow(); 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); @@ -2419,7 +2419,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value if (auto result = result_.valueChecked(); result.has_value()) { v.mkInt(*result); } else { - state.error("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow(); + state.errors.make("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow(); } } } @@ -2483,7 +2483,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, NixInt::Inner start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring").value; if (start < 0) - state.error("negative start position in 'substring'").atPos(pos).debugThrow(); + state.errors.make("negative start position in 'substring'").atPos(pos).debugThrow(); NixInt::Inner len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring").value; @@ -2523,7 +2523,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args, auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString"); std::optional ht = parseHashType(type); if (!ht) - state.error("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow(); + state.errors.make("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow(); NixStringContext context; // discarded auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString"); @@ -2585,11 +2585,11 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v) } catch (std::regex_error & e) { if (e.code() == std::regex_constants::error_space) { // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - state.error("memory limit exceeded by regular expression '%s'", re) + state.errors.make("memory limit exceeded by regular expression '%s'", re) .atPos(pos) .debugThrow(); } else - state.error("invalid regular expression '%s'", re) + state.errors.make("invalid regular expression '%s'", re) .atPos(pos) .debugThrow(); } @@ -2651,11 +2651,11 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v) } catch (std::regex_error & e) { if (e.code() == std::regex_constants::error_space) { // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ - state.error("memory limit exceeded by regular expression '%s'", re) + state.errors.make("memory limit exceeded by regular expression '%s'", re) .atPos(pos) .debugThrow(); } else - state.error("invalid regular expression '%s'", re) + state.errors.make("invalid regular expression '%s'", re) .atPos(pos) .debugThrow(); } @@ -2685,7 +2685,7 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a 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()) - state.error( + state.errors.make( "'from' and 'to' arguments passed to builtins.replaceStrings have different lengths" ).atPos(pos).debugThrow(); diff --git a/lix/libexpr/primops/context.cc b/lix/libexpr/primops/context.cc index 9e8fe79e4..a0a59db7a 100644 --- a/lix/libexpr/primops/context.cc +++ b/lix/libexpr/primops/context.cc @@ -56,7 +56,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * auto contextSize = context.size(); if (contextSize != 1) { - state.error( + state.errors.make( "context of string '%s' must have exactly one element, but has %d", *s, contextSize @@ -66,7 +66,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * (NixStringContextElem { std::visit(overloaded { [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { if (!c.path.isDerivation()) { - state.error( + state.errors.make( "path '%s' is not a derivation", state.store->printStorePath(c.path) ).atPos(pos).debugThrow(); @@ -76,7 +76,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * }; }, [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { - state.error( + state.errors.make( "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output ).atPos(pos).debugThrow(); @@ -176,7 +176,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar for (auto & i : *args[1]->attrs) { const auto & name = state.symbols[i.name]; if (!state.store->isStorePath(name)) - state.error( + state.errors.make( "context key '%s' is not a store path", name ).atPos(i.pos).debugThrow(); @@ -196,7 +196,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar if (iter != i.value->attrs->end()) { if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) { if (!isDerivation(name)) { - state.error( + state.errors.make( "tried to add all-outputs context of %s, which is not a derivation, to a string", name ).atPos(i.pos).debugThrow(); @@ -211,7 +211,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar if (iter != i.value->attrs->end()) { state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context"); if (iter->value->listSize() && !isDerivation(name)) { - state.error( + state.errors.make( "tried to add derivation output context of %s, which is not a derivation, to a string", name ).atPos(i.pos).debugThrow(); diff --git a/lix/libexpr/primops/fetchMercurial.cc b/lix/libexpr/primops/fetchMercurial.cc index 31c49750b..518d16307 100644 --- a/lix/libexpr/primops/fetchMercurial.cc +++ b/lix/libexpr/primops/fetchMercurial.cc @@ -38,11 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a else if (n == "name") name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial"); else - state.error("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow(); + state.errors.make("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow(); } if (url.empty()) - state.error("'url' argument required").atPos(pos).debugThrow(); + state.errors.make("'url' argument required").atPos(pos).debugThrow(); } else url = state.coerceToString(pos, *args[0], context, diff --git a/lix/libexpr/primops/fetchTree.cc b/lix/libexpr/primops/fetchTree.cc index cb91930cf..bbef00f72 100644 --- a/lix/libexpr/primops/fetchTree.cc +++ b/lix/libexpr/primops/fetchTree.cc @@ -124,12 +124,12 @@ static void fetchTree( if (auto aType = args[0]->attrs->get(state.s.type)) { if (type) - state.error( + state.errors.make( "unexpected attribute 'type'" ).atPos(pos).debugThrow(); type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree"); } else if (!type) - state.error( + state.errors.make( "attribute 'type' is missing in call to 'fetchTree'" ).atPos(pos).debugThrow(); @@ -153,19 +153,19 @@ static void fetchTree( auto intValue = attr.value->integer.value; if (intValue < 0) { - state.error("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); + state.errors.make("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); } unsigned long asUnsigned = intValue; attrs.emplace(state.symbols[attr.name], asUnsigned); } else - state.error("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", + state.errors.make("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.symbols[attr.name], showType(*attr.value)).debugThrow(); } if (!params.allowNameArgument) if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) - state.error( + state.errors.make( "attribute 'name' isn’t supported in call to 'fetchTree'" ).atPos(pos).debugThrow(); @@ -189,7 +189,7 @@ static void fetchTree( input = lookupInRegistries(state.store, input).first; if (evalSettings.pureEval && !input.isLocked()) { - state.error("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow(); + state.errors.make("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow(); } auto [tree, input2] = input.fetch(state.store); @@ -231,12 +231,12 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v else if (n == "name") name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch"); else - state.error("unsupported argument '%s' to '%s'", n, who) + state.errors.make("unsupported argument '%s' to '%s'", n, who) .atPos(pos).debugThrow(); } if (!url) - state.error( + state.errors.make( "'url' argument required").atPos(pos).debugThrow(); } else url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); @@ -250,7 +250,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v name = baseNameOf(*url); if (evalSettings.pureEval && !expectedHash) - state.error("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow(); + state.errors.make("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow(); // early exit if pinned and already in the store if (expectedHash && expectedHash->type == HashType::SHA256) { @@ -280,7 +280,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v ? state.store->queryPathInfo(storePath)->narHash : hashFile(HashType::SHA256, state.store->toRealPath(storePath)); if (hash != *expectedHash) { - state.error( + state.errors.make( "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", *url, expectedHash->to_string(Base::Base32, true), diff --git a/lix/libexpr/primops/fromTOML.cc b/lix/libexpr/primops/fromTOML.cc index 83f9052c4..2a66fc95b 100644 --- a/lix/libexpr/primops/fromTOML.cc +++ b/lix/libexpr/primops/fromTOML.cc @@ -83,7 +83,7 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & try { visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */)); } catch (std::exception & e) { // TODO: toml::syntax_error - state.error("while parsing TOML: %s", e.what()).atPos(pos).debugThrow(); + state.errors.make("while parsing TOML: %s", e.what()).atPos(pos).debugThrow(); } } diff --git a/lix/libexpr/value-to-json.cc b/lix/libexpr/value-to-json.cc index b6b7caa03..670de061c 100644 --- a/lix/libexpr/value-to-json.cc +++ b/lix/libexpr/value-to-json.cc @@ -99,7 +99,7 @@ json printValueAsJSON(EvalState & state, bool strict, case nThunk: case nFunction: - state.error( + state.errors.make( "cannot convert %1% to JSON", showType(v) ) @@ -118,7 +118,7 @@ void printValueAsJSON(EvalState & state, bool strict, json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, NixStringContext & context, bool copyToStore) const { - state.error("cannot convert %1% to JSON", showType()) + state.errors.make("cannot convert %1% to JSON", showType()) .debugThrow(); } diff --git a/lix/libutil/error.cc b/lix/libutil/error.cc index cd5382d66..db05d6319 100644 --- a/lix/libutil/error.cc +++ b/lix/libutil/error.cc @@ -330,7 +330,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * try { * e->eval(*this, env, v); * if (v.type() != nAttrs) - * error("expected a set but found %1%", v); + * errors.make("expected a set but found %1%", v); * } catch (Error & e) { * e.addTrace(pos, errorCtx); * throw; @@ -344,7 +344,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * e->eval(*this, env, v); * try { * if (v.type() != nAttrs) - * error("expected a set but found %1%", v); + * errors.make("expected a set but found %1%", v); * } catch (Error & e) { * e.addTrace(pos, errorCtx); * throw; diff --git a/lix/nix/eval.cc b/lix/nix/eval.cc index 906e3e57f..c6f1f2128 100644 --- a/lix/nix/eval.cc +++ b/lix/nix/eval.cc @@ -106,7 +106,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption } } else - state->error("value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow(); + state->errors.make("value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow(); }; recurse(*v, pos, *writeTo); diff --git a/lix/nix/flake.cc b/lix/nix/flake.cc index 18383247f..e8a239c39 100644 --- a/lix/nix/flake.cc +++ b/lix/nix/flake.cc @@ -868,7 +868,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand auto templateDir = templateDirAttr->getString(); if (!store->isInStore(templateDir)) - evalState->error( + evalState->errors.make( "'%s' was not found in the Nix store\n" "If you've set '%s' to a string, try using a path instead.", templateDir, templateDirAttr->getAttrPathStr()).debugThrow(); @@ -1374,7 +1374,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON { auto aType = visitor.maybeGetAttr("type"); if (!aType || aType->getString() != "app") - state->error("not an app definition").debugThrow(); + state->errors.make("not an app definition").debugThrow(); if (json) { j.emplace("type", "app"); } else { diff --git a/tests/unit/libexpr/error_traces.cc b/tests/unit/libexpr/error_traces.cc index 7017172b4..80f2578f5 100644 --- a/tests/unit/libexpr/error_traces.cc +++ b/tests/unit/libexpr/error_traces.cc @@ -12,19 +12,19 @@ namespace nix { TEST_F(ErrorTraceTest, TraceBuilder) { ASSERT_THROW( - state.error("puppy").debugThrow(), + state.errors.make("puppy").debugThrow(), EvalError ); ASSERT_THROW( - state.error("puppy").withTrace(noPos, "doggy").debugThrow(), + state.errors.make("puppy").withTrace(noPos, "doggy").debugThrow(), EvalError ); ASSERT_THROW( try { try { - state.error("puppy").withTrace(noPos, "doggy").debugThrow(); + state.errors.make("puppy").withTrace(noPos, "doggy").debugThrow(); } catch (Error & e) { e.addTrace(state.positions[noPos], "beans"); throw; @@ -47,10 +47,10 @@ namespace nix { TEST_F(ErrorTraceTest, NestedThrows) { try { - state.error("puppy").withTrace(noPos, "doggy").debugThrow(); + state.errors.make("puppy").withTrace(noPos, "doggy").debugThrow(); } catch (BaseError & e) { try { - state.error("beans").debugThrow(); + state.errors.make("beans").debugThrow(); } catch (Error & e2) { e.addTrace(state.positions[noPos], "beans2"); //e2.addTrace(state.positions[noPos], "Something", "");