From c70d6084b1e6ee7135b99a2f37771a457a3ed8a2 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Tue, 23 Jan 2024 16:36:19 -0800 Subject: [PATCH] builder --- src/libexpr/attr-path.cc | 8 +-- src/libexpr/eval-cache.cc | 28 ++++---- src/libexpr/eval-error.cc | 69 ++++++++++++++------ src/libexpr/eval-error.hh | 68 ++++++++++++++------ src/libexpr/eval-inline.hh | 4 +- src/libexpr/eval.cc | 76 +++++++++++----------- src/libexpr/eval.hh | 12 ---- src/libexpr/flake/flake.cc | 14 ++-- src/libexpr/get-drvs.cc | 4 +- src/libexpr/nixexpr.cc | 2 +- src/libexpr/parser.y | 2 +- src/libexpr/primops.cc | 92 +++++++++++++-------------- src/libexpr/primops/context.cc | 24 +++---- src/libexpr/primops/fetchClosure.cc | 22 +++---- src/libexpr/primops/fetchMercurial.cc | 4 +- src/libexpr/primops/fetchTree.cc | 25 ++++---- src/libexpr/primops/fromTOML.cc | 2 +- src/libexpr/value-to-json.cc | 4 +- src/libmain/shared.cc | 2 +- src/libstore/build/entry-points.cc | 4 +- src/libstore/daemon.cc | 2 +- src/libutil/error.cc | 6 +- src/libutil/error.hh | 24 +++++-- src/libutil/logging.cc | 2 +- src/nix-store/nix-store.cc | 4 +- src/nix/eval.cc | 2 +- src/nix/flake.cc | 6 +- tests/unit/libexpr/error_traces.cc | 10 +-- tests/unit/libutil/logging.cc | 4 +- 29 files changed, 291 insertions(+), 235 deletions(-) diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 9b332ac1fdab..332f171a2290 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -65,11 +65,11 @@ std::pair findAlongAttrPath(EvalState & state, const std::strin if (!attrIndex) { if (v->type() != nAttrs) - throw TypeError( + EvalErrorBuilder( state, "the expression selected by the selection path '%1%' should be a set but is %2%", attrPath, - showType(*v)); + showType(*v)).debugThrow(); if (attr.empty()) throw Error("empty attribute name in selection path '%1%'", attrPath); @@ -89,11 +89,11 @@ std::pair findAlongAttrPath(EvalState & state, const std::strin else { if (!v->isList()) - throw TypeError( + EvalErrorBuilder( state, "the expression selected by the selection path '%1%' should be a list but is %2%", attrPath, - showType(*v)); + showType(*v)).debugThrow(); if (*attrIndex >= v->listSize()) throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index c7d44fc2b43c..1f1396916524 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -500,7 +500,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro // evaluate to see whether 'name' exists } else return nullptr; - //throw TypeError("'%s' is not an attribute set", getAttrPathStr()); + //EvalErrorBuilder("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } @@ -508,7 +508,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro if (v.type() != nAttrs) return nullptr; - //throw TypeError("'%s' is not an attribute set", getAttrPathStr()); + //EvalErrorBuilder("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); auto attr = v.attrs->get(name); @@ -574,14 +574,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - TypeError(root->state, "'%s' is not a string", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a string", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - TypeError(root->state, "'%s' is not a string but %s", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a string but %s", getAttrPathStr()).debugThrow(); return v.type() == nString ? v.c_str() : v.path().to_string(); } @@ -616,7 +616,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - TypeError(root->state, "'%s' is not a string", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a string", getAttrPathStr()).debugThrow(); } } @@ -630,7 +630,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path().to_string(), {}}; else - TypeError(root->state, "'%s' is not a string but %s", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a string but %s", getAttrPathStr()).debugThrow(); } bool AttrCursor::getBool() @@ -643,14 +643,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - TypeError(root->state, "'%s' is not a Boolean", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a Boolean", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nBool) - TypeError(root->state, "'%s' is not a Boolean", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not a Boolean", getAttrPathStr()).debugThrow(); return v.boolean; } @@ -665,14 +665,14 @@ NixInt AttrCursor::getInt() debug("using cached integer attribute '%s'", getAttrPathStr()); return i->x; } else - throw TypeError(root->state, "'%s' is not an integer", getAttrPathStr()); + EvalErrorBuilder(root->state, "'%s' is not an integer", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nInt) - throw TypeError(root->state, "'%s' is not an integer", getAttrPathStr()); + EvalErrorBuilder(root->state, "'%s' is not an integer", getAttrPathStr()).debugThrow(); return v.integer; } @@ -687,7 +687,7 @@ std::vector AttrCursor::getListOfStrings() debug("using cached list of strings attribute '%s'", getAttrPathStr()); return *l; } else - throw TypeError(root->state, "'%s' is not a list of strings", getAttrPathStr()); + EvalErrorBuilder(root->state, "'%s' is not a list of strings", getAttrPathStr()).debugThrow(); } } @@ -697,7 +697,7 @@ std::vector AttrCursor::getListOfStrings() root->state.forceValue(v, noPos); if (v.type() != nList) - throw TypeError(root->state, "'%s' is not a list", getAttrPathStr()); + EvalErrorBuilder(root->state, "'%s' is not a list", getAttrPathStr()).debugThrow(); std::vector res; @@ -720,14 +720,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - TypeError(root->state, "'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nAttrs) - TypeError(root->state, "'%s' is not an attribute set", getAttrPathStr()).debugThrow(); + EvalErrorBuilder(root->state, "'%s' is not an attribute set", getAttrPathStr()).debugThrow(); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/src/libexpr/eval-error.cc b/src/libexpr/eval-error.cc index 108fd8294497..83c85956fef4 100644 --- a/src/libexpr/eval-error.cc +++ b/src/libexpr/eval-error.cc @@ -3,37 +3,51 @@ namespace nix { -EvalError & EvalError::atPos(PosIdx pos) +template +EvalErrorBuilder & EvalErrorBuilder::withExitStatus(unsigned int exitStatus) { - err.errPos = state.positions[pos]; + error.withExitStatus(exitStatus); return *this; } -EvalError & EvalError::withTrace(PosIdx pos, const std::string_view text) +template +EvalErrorBuilder & EvalErrorBuilder::atPos(PosIdx pos) { - err.traces.push_front(Trace{.pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false}); + error.err.pos = error.state.positions[pos]; return *this; } -EvalError & EvalError::withFrameTrace(PosIdx pos, const std::string_view text) +template +EvalErrorBuilder & EvalErrorBuilder::withTrace(PosIdx pos, const std::string_view text) { - err.traces.push_front(Trace{.pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true}); + error.err.traces.push_front( + Trace{.pos = error.state.positions[pos], .hint = hintformat(std::string(text)), .frame = false}); return *this; } -EvalError & EvalError::withSuggestions(Suggestions & s) +template +EvalErrorBuilder & EvalErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text) { - err.suggestions = s; + error.err.traces.push_front( + Trace{.pos = error.state.positions[pos], .hint = hintformat(std::string(text)), .frame = true}); return *this; } -EvalError & EvalError::withFrame(const Env & env, const Expr & expr) +template +EvalErrorBuilder & EvalErrorBuilder::withSuggestions(Suggestions & s) +{ + error.err.suggestions = s; + return *this; +} + +template +EvalErrorBuilder & EvalErrorBuilder::withFrame(const Env & env, const Expr & expr) { // NOTE: This is abusing side-effects. // TODO: check compatibility with nested debugger calls. // TODO: What side-effects?? - state.debugTraces.push_front(DebugTrace{ - .pos = state.positions[expr.getPos()], + error.state.debugTraces.push_front(DebugTrace{ + .pos = error.state.positions[expr.getPos()], .expr = expr, .env = env, .hint = hintformat("Fake frame for debugging purposes"), @@ -41,30 +55,45 @@ EvalError & EvalError::withFrame(const Env & env, const Expr & expr) return *this; } -EvalError & EvalError::addTrace(PosIdx pos, hintformat hint, bool frame) +template +EvalErrorBuilder & EvalErrorBuilder::addTrace(PosIdx pos, hintformat hint, bool frame) { - BaseError::addTrace(state.positions[pos], hint, frame); + error.addTrace(error.state.positions[pos], hint, frame); return *this; } +template template -EvalError & EvalError::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) +EvalErrorBuilder & +EvalErrorBuilder::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) { - addTrace(state.positions[pos], hintfmt(std::string(formatString), formatArgs...)); + addTrace(error.state.positions[pos], hintfmt(std::string(formatString), formatArgs...)); return *this; } -void EvalError::debugThrow() +template +void EvalErrorBuilder::debugThrow() { - if (state.debugRepl && !state.debugTraces.empty()) { - const DebugTrace & last = state.debugTraces.front(); + if (error.state.debugRepl && !error.state.debugTraces.empty()) { + const DebugTrace & last = error.state.debugTraces.front(); const Env * env = &last.env; const Expr * expr = &last.expr; - state.runDebugRepl(this, *env, *expr); + error.state.runDebugRepl(&error, *env, *expr); } - throw std::move(*this); + throw error; } +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; +template class EvalErrorBuilder; + } diff --git a/src/libexpr/eval-error.hh b/src/libexpr/eval-error.hh index f55300827b39..b7d2b15fd590 100644 --- a/src/libexpr/eval-error.hh +++ b/src/libexpr/eval-error.hh @@ -9,9 +9,13 @@ struct Env; struct Expr; class EvalState; +template +class EvalErrorBuilder; class EvalError : public Error { + template + friend class EvalErrorBuilder; public: EvalState & state; @@ -27,23 +31,6 @@ public: , state(state) { } - - [[nodiscard, gnu::noinline]] EvalError & atPos(PosIdx pos); - - [[nodiscard, gnu::noinline]] EvalError & withTrace(PosIdx pos, const std::string_view text); - - [[nodiscard, gnu::noinline]] EvalError & withFrameTrace(PosIdx pos, const std::string_view text); - - [[nodiscard, gnu::noinline]] EvalError & withSuggestions(Suggestions & s); - - [[nodiscard, gnu::noinline]] EvalError & withFrame(const Env & e, const Expr & ex); - - [[nodiscard, gnu::noinline]] EvalError & addTrace(PosIdx pos, hintformat hint, bool frame = false); - - template - EvalError & addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs); - - [[gnu::noinline, gnu::noreturn]] void debugThrow(); }; MakeError(ParseError, Error); @@ -53,12 +40,53 @@ MakeError(Abort, EvalError); MakeError(TypeError, EvalError); MakeError(UndefinedVarError, EvalError); MakeError(MissingArgumentError, EvalError); +MakeError(CachedEvalError, EvalError); +MakeError(InfiniteRecursionError, EvalError); + +struct InvalidPathError : public EvalError +{ +public: + Path path; + InvalidPathError(EvalState & state, const Path & path) + : EvalError(state, "path '%s' is not valid", path) + { + } +#ifdef EXCEPTION_NEEDS_THROW_SPEC + ~InvalidPathError() throw(){}; +#endif +}; -class InfiniteRecursionError : public EvalError +template +class EvalErrorBuilder { - friend class EvalState; public: - using EvalError::EvalError; + T error; + + template + explicit EvalErrorBuilder(EvalState & state, const Args &... args) + : error(T(state, args...)) + { + } + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & withExitStatus(unsigned int exitStatus); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & atPos(PosIdx pos); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & withTrace(PosIdx pos, const std::string_view text); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & withSuggestions(Suggestions & s); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & withFrame(const Env & e, const Expr & ex); + + [[nodiscard, gnu::noinline]] EvalErrorBuilder & addTrace(PosIdx pos, hintformat hint, bool frame = false); + + template + [[nodiscard, gnu::noinline]] EvalErrorBuilder & + addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs); + + [[gnu::noinline, gnu::noreturn]] virtual void debugThrow(); }; } diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index f7210e712d7a..e6328e4fa9d0 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -116,7 +116,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e PosIdx pos = getPos(); forceValue(v, pos); if (v.type() != nAttrs) { - TypeError( + EvalErrorBuilder( *this, "expected a set but found %1%: %2%", showType(v), @@ -131,7 +131,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e { forceValue(v, pos); if (!v.isList()) { - TypeError( + EvalErrorBuilder( *this, "expected a list but found %1%: %2%", showType(v), diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index acf12de4c5bd..756ea498e01a 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -758,7 +758,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & ? std::make_unique( *this, DebugTrace { - .pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()], + .pos = error->info().pos ? error->info().pos : positions[expr.getPos()], .expr = expr, .env = env, .hint = error->info().msg, @@ -877,7 +877,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!fromWith->parentWith) - UndefinedVarError(*this, "undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); + EvalErrorBuilder(*this, "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; } @@ -1083,7 +1083,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) // computation. if (mustBeTrivial && !(dynamic_cast(e))) - EvalError(*this, "file '%s' must be an attribute set", path).debugThrow(); + EvalErrorBuilder(*this, "file '%s' must be an attribute set", path).debugThrow(); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); @@ -1114,7 +1114,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) - TypeError( + EvalErrorBuilder( *this, "expected a Boolean but found %1%: %2%", showType(v), @@ -1133,7 +1133,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - TypeError( + EvalErrorBuilder( *this, "expected a set but found %1%: %2%", showType(v), @@ -1247,7 +1247,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) auto nameSym = state.symbols.create(nameVal.string_view()); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - EvalError(state, "dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); + EvalErrorBuilder(state, "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 */ @@ -1359,7 +1359,7 @@ 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]); - EvalError(state, "attribute '%1%' missing", state.symbols[name]) + EvalErrorBuilder(state, "attribute '%1%' missing", state.symbols[name]) .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow(); } } @@ -1433,7 +1433,7 @@ class CallDepth { void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) { if (callDepth > evalSettings.maxCallDepth) - EvalError(*this, "stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); + EvalErrorBuilder(*this, "stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); CallDepth _level(callDepth); auto trace = evalSettings.traceFunctionCalls @@ -1491,7 +1491,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) { - TypeError(*this, "function '%1%' called without required argument '%2%'", + EvalErrorBuilder(*this, "function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), symbols[i.name]) .atPos(lambda.pos) @@ -1517,7 +1517,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]); - TypeError(*this, "function '%1%' called with unexpected argument '%2%'", + EvalErrorBuilder(*this, "function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), symbols[i.name]) .atPos(lambda.pos) @@ -1656,7 +1656,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - TypeError(*this, "attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); + EvalErrorBuilder(*this, "attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); } vRes = vCur; @@ -1726,7 +1726,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { attrs.insert(*j); } else if (!i.def) { - MissingArgumentError(*this, R"(cannot evaluate a function that has an argument without a value ('%1%') + EvalErrorBuilder(*this, 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 @@ -1762,7 +1762,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); - AssertionError(state, "assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); + EvalErrorBuilder(state, "assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); } body->eval(state, env, v); } @@ -1940,14 +1940,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - EvalError(state, "cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); + EvalErrorBuilder(state, "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 - EvalError(state, "cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); + EvalErrorBuilder(state, "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 @@ -1969,7 +1969,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - EvalError(state, "a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); + EvalErrorBuilder(state, "a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); v.mkPath(state.rootPath(CanonPath(canonPath(str())))); } else v.mkStringMove(c_str(), context); @@ -1984,7 +1984,7 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v) void ExprBlackHole::eval(EvalState & state, Env & env, Value & v) { - InfiniteRecursionError(state, "infinite recursion encountered") + EvalErrorBuilder(state, "infinite recursion encountered") .atPos(v.determinePos(noPos)) .debugThrow(); } @@ -2000,7 +2000,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos) try { std::rethrow_exception(e); } catch (InfiniteRecursionError & e) { - e.err.errPos = positions[pos]; + e.atPos(positions[pos]); } catch (...) { } } @@ -2048,7 +2048,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - TypeError( + EvalErrorBuilder( *this, "expected an integer but found %1%: %2%", showType(v), @@ -2061,7 +2061,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt } if (v.type() != nInt) - TypeError(*this, "value is %1% while an integer was expected", showType(v)).atPos(pos).debugThrow(); + EvalErrorBuilder(*this, "value is %1% while an integer was expected", showType(v)).atPos(pos).debugThrow(); return v.integer; } @@ -2073,7 +2073,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) - TypeError( + EvalErrorBuilder( *this, "expected a float but found %1%: %2%", showType(v), @@ -2092,7 +2092,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - TypeError( + EvalErrorBuilder( *this, "expected a Boolean but found %1%: %2%", showType(v), @@ -2105,7 +2105,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx } if (v.type() != nBool) - TypeError(*this, "value is %1% while a Boolean was expected", showType(v)).debugThrow(); + EvalErrorBuilder(*this, "value is %1% while a Boolean was expected", showType(v)).debugThrow(); return v.boolean; } @@ -2121,7 +2121,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - TypeError( + EvalErrorBuilder( *this, "expected a function but found %1%: %2%", showType(v), @@ -2133,7 +2133,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro } if (v.type() != nFunction && !isFunctor(v)) - TypeError(*this, "value is %1% while a function was expected", showType(v)).debugThrow(); + EvalErrorBuilder(*this, "value is %1% while a function was expected", showType(v)).debugThrow(); } @@ -2142,7 +2142,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - TypeError( + EvalErrorBuilder( *this, "expected a string but found %1%: %2%", showType(v), @@ -2176,7 +2176,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s { auto s = forceString(v, pos, errorCtx); if (v.context()) { - EvalError(*this, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow(); + EvalErrorBuilder(*this, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow(); } return s; } @@ -2241,7 +2241,7 @@ BackedStringView EvalState::coerceToString( return std::move(*maybeString); auto i = v.attrs->find(sOutPath); if (i == v.attrs->end()) { - TypeError(*this, "cannot coerce %1% to a string", showType(v)) + EvalErrorBuilder(*this, "cannot coerce %1% to a string", showType(v)) .withTrace(pos, errorCtx) .debugThrow(); } @@ -2287,7 +2287,7 @@ BackedStringView EvalState::coerceToString( } } - TypeError(*this, "cannot coerce %1% to a string", showType(v)) + EvalErrorBuilder(*this, "cannot coerce %1% to a string", showType(v)) .withTrace(pos, errorCtx) .debugThrow(); } @@ -2296,7 +2296,7 @@ BackedStringView EvalState::coerceToString( StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path) { if (nix::isDerivation(path.path.abs())) - EvalError(*this, "file names are not allowed to end in '%1%'", drvExtension).debugThrow(); + EvalErrorBuilder(*this, "file names are not allowed to end in '%1%'", drvExtension).debugThrow(); auto i = srcToStore.find(path); @@ -2345,7 +2345,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext relative to the root filesystem. */ auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (path == "" || path[0] != '/') - EvalError(*this, "string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); + EvalErrorBuilder(*this, "string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); return rootPath(CanonPath(path)); } @@ -2355,7 +2355,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; - EvalError(*this, "path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); + EvalErrorBuilder(*this, "path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } @@ -2365,7 +2365,7 @@ std::pair EvalState::coerceToSingleDerivedP auto s = forceString(v, context, pos, errorCtx); auto csize = context.size(); if (csize != 1) - EvalError( + EvalErrorBuilder( *this, "string '%s' has %d entries in its context. It should only have exactly one entry", s, csize) @@ -2375,7 +2375,7 @@ std::pair EvalState::coerceToSingleDerivedP return std::move(o); }, [&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath { - EvalError( + EvalErrorBuilder( *this, "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(); @@ -2401,14 +2401,14 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value & error message. */ std::visit(overloaded { [&](const SingleDerivedPath::Opaque & o) { - EvalError( + EvalErrorBuilder( *this, "path string '%s' has context with the different path '%s'", s, sExpected) .withTrace(pos, errorCtx).debugThrow(); }, [&](const SingleDerivedPath::Built & b) { - EvalError( + EvalErrorBuilder( *this, "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) @@ -2497,7 +2497,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v case nThunk: // Must not be left by forceValue default: - EvalError(*this, "cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); + EvalErrorBuilder(*this, "cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); } } @@ -2650,7 +2650,7 @@ void EvalState::printStatistics() std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const { - TypeError( + EvalErrorBuilder( state, "cannot coerce %1% to a string", showType() ).atPos(pos).debugThrow(); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 8740f7f1c554..3f32ecfb41df 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -769,18 +769,6 @@ SourcePath resolveExprPath(SourcePath path); */ bool isAllowedURI(std::string_view uri, const Strings & allowedPaths); -struct InvalidPathError : public EvalError -{ -public: - Path path; - InvalidPathError(EvalState & state, const Path & path) - : EvalError(state, "path '%s' is not valid", path) - { } -#ifdef EXCEPTION_NEEDS_THROW_SPEC - ~InvalidPathError() throw () { }; -#endif -}; - } #include "eval-inline.hh" diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 3a0e5caf8163..fb34ab035e1e 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -147,8 +147,8 @@ static FlakeInput parseFlakeInput(EvalState & state, NixStringContext emptyContext = {}; attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump()); } else - throw TypeError(state, "flake input attribute '%s' is %s while a string, Boolean, or integer is expected", - state.symbols[attr.name], showType(*attr.value)); + EvalErrorBuilder(state, "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 } @@ -295,15 +295,15 @@ static Flake getFlake( std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) - throw TypeError(state, "list element in flake configuration setting '%s' is %s while a string is expected", - state.symbols[setting.name], showType(*setting.value)); + EvalErrorBuilder(state, "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 - throw TypeError(state, "flake configuration setting '%s' is %s", - state.symbols[setting.name], showType(*setting.value)); + EvalErrorBuilder(state, "flake configuration setting '%s' is %s", + state.symbols[setting.name], showType(*setting.value)).debugThrow(); } } @@ -865,7 +865,7 @@ static void prim_flakeRefToString( attrs.emplace(state.symbols[attr.name], std::string(attr.value->string_view())); } else { - EvalError( + EvalErrorBuilder( state, "flake reference attribute sets may only contain integers, Booleans, " "and strings, but attribute '%s' is %s", diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 8ed410a75d15..df072eea6d20 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -49,7 +49,7 @@ std::string PackageInfo::queryName() const { if (name == "" && attrs) { auto i = attrs->find(state->sName); - if (i == attrs->end()) TypeError(*state, "derivation name missing").debugThrow(); + if (i == attrs->end()) EvalErrorBuilder(*state, "derivation name missing").debugThrow(); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation"); } return name; @@ -397,7 +397,7 @@ static void getDerivations(EvalState & state, Value & vIn, } else - TypeError(state, "expression does not evaluate to a derivation (or a set or list of those)").debugThrow(); + EvalErrorBuilder(state, "expression does not evaluate to a derivation (or a set or list of those)").debugThrow(); } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 25afc6e76afd..1808d27eeedf 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -294,7 +294,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) - UndefinedVarError( + EvalErrorBuilder( es, "undefined variable '%1%'", es.symbols[name] diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 40e5716e8392..24e715acb580 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -799,7 +799,7 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_ if (hasPrefix(path, "nix/")) return {corepkgsFS, CanonPath(path.substr(3))}; - ThrownError( + EvalErrorBuilder( *this, evalSettings.pureEval ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index d0dfb1d60a9c..d4f563e1b806 100644 --- a/src/libexpr/primops.cc +++ b/src/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)) - InvalidPathError(*this, store->printStorePath(p)).debugThrow(); + EvalErrorBuilder(*this, 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) - EvalError( + EvalErrorBuilder( *this, "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", drvs.begin()->to_string(*store) @@ -338,16 +338,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) - EvalError(state, "could not open '%1%': %2%", path, dlerror()).debugThrow(); + EvalErrorBuilder(state, "could not open '%1%': %2%", path, dlerror()).debugThrow(); dlerror(); ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str()); if(!func) { char *message = dlerror(); if (message) - EvalError(state, "could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow(); + EvalErrorBuilder(state, "could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow(); else - EvalError(state, "symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow(); + EvalErrorBuilder(state, "symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow(); } (func)(state, v); @@ -363,7 +363,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) - EvalError(state, "at least one argument to 'exec' required").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "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", @@ -378,7 +378,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) try { auto _ = state.realiseContext(context); // FIXME: Handle CA derivations } catch (InvalidPathError & e) { - EvalError(state, "cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow(); } auto output = runProgram(program, true, commandArgs); @@ -580,7 +580,7 @@ struct CompareValues if (v1->type() == nInt && v2->type() == nFloat) return v1->integer < v2->fpoint; if (v1->type() != v2->type()) - EvalError(state, "cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); + EvalErrorBuilder(state, "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" @@ -608,7 +608,7 @@ struct CompareValues } } default: - EvalError(state, "cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); + EvalErrorBuilder(state, "cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); #pragma GCC diagnostic pop } } catch (Error & e) { @@ -635,7 +635,7 @@ static Bindings::iterator getAttr( { Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { - TypeError(state, "attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); + EvalErrorBuilder(state, "attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); } return value; } @@ -756,7 +756,7 @@ static RegisterPrimOp primop_break({ auto error = Error(ErrorInfo { .level = lvlInfo, .msg = hintfmt("breakpoint reached"), - .errPos = state.positions[pos], + .pos = state.positions[pos], }); auto & dt = state.debugTraces.front(); @@ -767,7 +767,7 @@ static RegisterPrimOp primop_break({ throw Error(ErrorInfo{ .level = lvlInfo, .msg = hintfmt("quit the debugger"), - .errPos = nullptr, + .pos = nullptr, }); } } @@ -788,7 +788,7 @@ static RegisterPrimOp primop_abort({ NixStringContext context; auto s = state.coerceToString(pos, *args[0], context, "while evaluating the error message passed to builtins.abort").toOwned(); - Abort(state, "evaluation aborted with the following error message: '%1%'", s).debugThrow(); + EvalErrorBuilder(state, "evaluation aborted with the following error message: '%1%'", s).debugThrow(); } }); @@ -807,7 +807,7 @@ static RegisterPrimOp primop_throw({ NixStringContext context; auto s = state.coerceToString(pos, *args[0], context, "while evaluating the error message passed to builtin.throw").toOwned(); - ThrownError(state, "%s", s).debugThrow(); + EvalErrorBuilder(state, s).debugThrow(); } }); @@ -1126,7 +1126,7 @@ drvName, Bindings * attrs, Value & v) experimentalFeatureSettings.require(Xp::DynamicDerivations); ingestionMethod = TextIngestionMethod {}; } else - EvalError( + EvalErrorBuilder( state, "invalid value '%s' for 'outputHashMode' attribute", s ).debugThrow(); @@ -1136,7 +1136,7 @@ drvName, Bindings * attrs, Value & v) outputs.clear(); for (auto & j : ss) { if (outputs.find(j) != outputs.end()) - EvalError(state, "duplicate derivation output '%1%'", j) + EvalErrorBuilder(state, "duplicate derivation output '%1%'", j) .atPos(v.determinePos(noPos)) .debugThrow(); /* !!! Check whether j is a valid attribute @@ -1145,13 +1145,13 @@ drvName, Bindings * attrs, Value & v) then we'd have an attribute ‘drvPath’ in the resulting set. */ if (j == "drv") - EvalError(state, "invalid derivation output name 'drv'") + EvalErrorBuilder(state, "invalid derivation output name 'drv'") .atPos(v.determinePos(noPos)) .debugThrow(); outputs.insert(j); } if (outputs.empty()) - EvalError(state, "derivation cannot have an empty set of outputs") + EvalErrorBuilder(state, "derivation cannot have an empty set of outputs") .atPos(v.determinePos(noPos)) .debugThrow(); }; @@ -1276,12 +1276,12 @@ drvName, Bindings * attrs, Value & v) /* Do we have all required attributes? */ if (drv.builder == "") - EvalError(state, "required attribute 'builder' missing") + EvalErrorBuilder(state, "required attribute 'builder' missing") .atPos(v.determinePos(noPos)) .debugThrow(); if (drv.platform == "") - EvalError(state, "required attribute 'system' missing") + EvalErrorBuilder(state, "required attribute 'system' missing") .atPos(v.determinePos(noPos)) .debugThrow(); @@ -1291,7 +1291,7 @@ drvName, Bindings * attrs, Value & v) outputs.size() == 1 && *(outputs.begin()) == "out")) { - EvalError( + EvalErrorBuilder( state, "derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension @@ -1304,7 +1304,7 @@ drvName, Bindings * attrs, Value & v) Ignore `__contentAddressed` because fixed output derivations are already content addressed. */ if (outputs.size() != 1 || *(outputs.begin()) != "out") - EvalError( + EvalErrorBuilder( state, "multiple outputs are not supported in fixed-output derivations" ).atPos(v.determinePos(noPos)).debugThrow(); @@ -1326,7 +1326,7 @@ drvName, Bindings * attrs, Value & v) else if (contentAddressed || isImpure) { if (contentAddressed && isImpure) - EvalError(state, "derivation cannot be both content-addressed and impure") + EvalErrorBuilder(state, "derivation cannot be both content-addressed and impure") .atPos(v.determinePos(noPos)).debugThrow(); auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256); @@ -1368,7 +1368,7 @@ drvName, Bindings * attrs, Value & v) for (auto & i : outputs) { auto h = get(hashModulo.hashes, i); if (!h) - AssertionError( + EvalErrorBuilder( state, "derivation produced no hash for output '%s'", i @@ -1478,7 +1478,7 @@ static RegisterPrimOp primop_toPath({ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { if (evalSettings.pureEval) - EvalError( + EvalErrorBuilder( state, "'%s' is not allowed in pure evaluation mode", "builtins.storePath" @@ -1492,7 +1492,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())) - EvalError(state, "path '%1%' is not in the Nix store", path) + EvalErrorBuilder(state, "path '%1%' is not in the Nix store", path) .atPos(pos).debugThrow(); auto path2 = state.store->toStorePath(path.abs()).first; if (!settings.readOnlyMode) @@ -1608,7 +1608,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) - EvalError( + EvalErrorBuilder( state, "the contents of the file '%1%' cannot be represented as a Nix string", path @@ -1669,7 +1669,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) { - EvalError( + EvalErrorBuilder( state, "cannot find '%1%', since path '%2%' is not valid", path, @@ -1743,7 +1743,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile"); std::optional ha = parseHashAlgo(algo); if (!ha) - EvalError(state, "unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow(); auto path = realisePath(state, pos, *args[1]); @@ -2063,7 +2063,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 - EvalError( + EvalErrorBuilder( state, "in 'toFile': the file named '%1%' must not contain a reference to a derivation but contains (%2%)", name, @@ -2237,7 +2237,7 @@ static void addPath( if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) { auto dstPath = fetchToStore(*state.store, path, name, method, filter.get(), state.repair); if (expectedHash && expectedStorePath != dstPath) - EvalError( + EvalErrorBuilder( state, "store path mismatch in (possibly filtered) path added from '%s'", path @@ -2341,14 +2341,14 @@ 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"), HashAlgorithm::SHA256); else - EvalError( + EvalErrorBuilder( state, "unsupported argument '%1%' to 'addPath'", state.symbols[attr.name] ).atPos(attr.pos).debugThrow(); } if (!path) - EvalError( + EvalErrorBuilder( state, "missing required 'path' attribute in the first argument to builtins.path" ).atPos(pos).debugThrow(); @@ -2769,7 +2769,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg return; } if (!args[0]->isLambda()) - TypeError(state, "'functionArgs' requires a function").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "'functionArgs' requires a function").atPos(pos).debugThrow(); if (!args[0]->lambda.fun->hasFormals()) { v.mkAttrs(&state.emptyBindings); @@ -2939,7 +2939,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()) - EvalError( + EvalErrorBuilder( state, "list index %1% is out of bounds", n @@ -2988,7 +2988,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) - EvalError(state, "'tail' called on an empty list").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "'tail' called on an empty list").atPos(pos).debugThrow(); state.mkList(v, args[0]->listSize() - 1); for (unsigned int n = 0; n < v.listSize(); ++n) @@ -3245,7 +3245,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"); if (len < 0) - EvalError(state, "cannot create list of size %1%", len).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "cannot create list of size %1%", len).atPos(pos).debugThrow(); // More strict than striclty (!) necessary, but acceptable // as evaluating map without accessing any values makes little sense. @@ -3562,7 +3562,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) - EvalError(state, "division by zero").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "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); @@ -3571,7 +3571,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value 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) - EvalError(state, "overflow in integer division").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "overflow in integer division").atPos(pos).debugThrow(); v.mkInt(i1 / i2); } @@ -3702,7 +3702,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring"); if (start < 0) - EvalError(state, "negative start position in 'substring'").atPos(pos).debugThrow(); + EvalErrorBuilder(state, "negative start position in 'substring'").atPos(pos).debugThrow(); int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring"); @@ -3767,7 +3767,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args, auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString"); std::optional ha = parseHashAlgo(algo); if (!ha) - EvalError(state, "unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow(); NixStringContext context; // discarded auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString"); @@ -3933,11 +3933,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++ - EvalError(state, "memory limit exceeded by regular expression '%s'", re) + EvalErrorBuilder(state, "memory limit exceeded by regular expression '%s'", re) .atPos(pos) .debugThrow(); } else - EvalError(state, "invalid regular expression '%s'", re) + EvalErrorBuilder(state, "invalid regular expression '%s'", re) .atPos(pos) .debugThrow(); } @@ -4035,11 +4035,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++ - EvalError(state, "memory limit exceeded by regular expression '%s'", re) + EvalErrorBuilder(state, "memory limit exceeded by regular expression '%s'", re) .atPos(pos) .debugThrow(); } else - EvalError(state, "invalid regular expression '%s'", re) + EvalErrorBuilder(state, "invalid regular expression '%s'", re) .atPos(pos) .debugThrow(); } @@ -4117,7 +4117,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()) - EvalError( + EvalErrorBuilder( state, "'from' and 'to' arguments passed to builtins.replaceStrings have different lengths" ).atPos(pos).debugThrow(); diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 7ca0c835ad2b..ff99c030e207 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -98,33 +98,33 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V auto contextSize = context.size(); if (contextSize != 1) { - throw EvalError( + EvalErrorBuilder( state, "context of string '%s' must have exactly one element, but has %d", *s, contextSize - ).atPos(pos); + ).atPos(pos).debugThrow(); } NixStringContext context2 { (NixStringContextElem { std::visit(overloaded { [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { if (!c.path.isDerivation()) { - throw EvalError( + EvalErrorBuilder( state, "path '%s' is not a derivation", state.store->printStorePath(c.path) - ).atPos(pos); + ).atPos(pos).debugThrow(); } return NixStringContextElem::DrvDeep { .drvPath = c.path, }; }, [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { - throw EvalError( + EvalErrorBuilder( state, "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output - ).atPos(pos); + ).atPos(pos).debugThrow(); }, [&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep { /* Reuse original item because we want this to be idempotent. */ @@ -264,11 +264,11 @@ 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)) - throw EvalError( + EvalErrorBuilder( state, "context key '%s' is not a store path", name - ).atPos(i.pos); + ).atPos(i.pos).debugThrow(); auto namePath = state.store->parseStorePath(name); if (!settings.readOnlyMode) state.store->ensurePath(namePath); @@ -285,11 +285,11 @@ 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)) { - throw EvalError( + EvalErrorBuilder( state, "tried to add all-outputs context of %s, which is not a derivation, to a string", name - ); + ).debugThrow(); } context.emplace(NixStringContextElem::DrvDeep { .drvPath = namePath, @@ -301,11 +301,11 @@ 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)) { - throw EvalError( + EvalErrorBuilder( state, "tried to add derivation output context of %s, which is not a derivation, to a string", name - ).atPos(i.pos); + ).atPos(i.pos).debugThrow(); } for (auto elem : iter->value->listItems()) { auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context"); diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 27147a5d13c8..5806b3ff90c5 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -27,7 +27,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor state.store->printStorePath(fromPath), state.store->printStorePath(rewrittenPath), state.store->printStorePath(*toPathMaybe)), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); if (!toPathMaybe) throw Error({ @@ -36,7 +36,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor "Use this value for the 'toPath' attribute passed to 'fetchClosure'", state.store->printStorePath(fromPath), state.store->printStorePath(rewrittenPath)), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } @@ -54,7 +54,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor "The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n" "Set 'toPath' to an empty string to make Nix report the correct content-addressed path.", state.store->printStorePath(toPath)), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } @@ -80,7 +80,7 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos "to the 'fetchClosure' arguments.\n\n" "Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.", state.store->printStorePath(fromPath)), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } @@ -103,7 +103,7 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId "The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n" "Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed", state.store->printStorePath(fromPath)), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } @@ -154,14 +154,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg else throw Error({ .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } if (!fromPath) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); bool inputAddressed = inputAddressedMaybe.value_or(false); @@ -172,14 +172,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg .msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them", "inputAddressed", "toPath"), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); } if (!fromStoreUrl) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); auto parsedURL = parseURL(*fromStoreUrl); @@ -189,13 +189,13 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) throw Error({ .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); if (!parsedURL.query.empty()) throw Error({ .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), - .errPos = state.positions[pos] + .pos = state.positions[pos] }); auto fromStore = openStore(parsedURL.to_string()); diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index c4a4e0e1f83a..0bfc06ff86d8 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/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 - throw EvalError(state, "unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos); + EvalErrorBuilder(state, "unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow(); } if (url.empty()) - throw EvalError(state, "'url' argument required").atPos(pos); + EvalErrorBuilder(state, "'url' argument required").atPos(pos).debugThrow(); } else url = state.coerceToString(pos, *args[0], context, diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index d3ccab16c29c..76e310777ff3 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -100,13 +100,13 @@ static void fetchTree( if (auto aType = args[0]->attrs->get(state.sType)) { if (type) - EvalError( + EvalErrorBuilder( state, "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) - EvalError( + EvalErrorBuilder( state, "attribute 'type' is missing in call to 'fetchTree'" ).atPos(pos).debugThrow(); @@ -132,7 +132,7 @@ static void fetchTree( attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump()); } else - TypeError(state, "fetchTree argument '%s' is %s while a string, Boolean or integer is expected", + EvalErrorBuilder(state, "fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.symbols[attr.name], showType(*attr.value)).debugThrow(); } @@ -142,7 +142,7 @@ static void fetchTree( if (!params.allowNameArgument) if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) - EvalError( + EvalErrorBuilder( state, "attribute 'name' isn’t supported in call to 'fetchTree'" ).atPos(pos).debugThrow(); @@ -163,7 +163,7 @@ static void fetchTree( input = fetchers::Input::fromAttrs(std::move(attrs)); } else { if (!experimentalFeatureSettings.isEnabled(Xp::Flakes)) - EvalError( + EvalErrorBuilder( state, "passing a string argument to 'fetchTree' requires the 'flakes' experimental feature" ).atPos(pos).debugThrow(); @@ -179,7 +179,7 @@ static void fetchTree( if (params.isFetchGit) fetcher = "fetchGit"; - EvalError( + EvalErrorBuilder( state, "in pure evaluation mode, %s requires a locked input", fetcher @@ -437,13 +437,13 @@ 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 - EvalError( + EvalErrorBuilder( state, "unsupported argument '%s' to '%s'", n, who) .atPos(pos).debugThrow(); } if (!url) - EvalError(state, + EvalErrorBuilder(state, "'url' argument required").atPos(pos).debugThrow(); } else url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); @@ -457,7 +457,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v name = baseNameOf(*url); if (evalSettings.pureEval && !expectedHash) - EvalError(state, "in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "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->algo == HashAlgorithm::SHA256) { @@ -487,15 +487,14 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v ? state.store->queryPathInfo(storePath)->narHash : hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath)); if (hash != *expectedHash) { - auto err = EvalError( + EvalErrorBuilder( state, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", *url, expectedHash->to_string(HashFormat::Nix32, true), hash.to_string(HashFormat::Nix32, true) - ); - err.withExitStatus(102); - err.debugThrow(); + ).withExitStatus(102) + .debugThrow(); } } diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index 5e7987b85525..4d9c97a0345d 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -83,7 +83,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V try { visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */)); } catch (std::exception & e) { // TODO: toml::syntax_error - EvalError(state, "while parsing TOML: %s", e.what()).atPos(pos).debugThrow(); + EvalErrorBuilder(state, "while parsing TOML: %s", e.what()).atPos(pos).debugThrow(); } } diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 4ac23be84c1c..b0150c8a8b17 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -99,7 +99,7 @@ json printValueAsJSON(EvalState & state, bool strict, case nThunk: case nFunction: - TypeError( + EvalErrorBuilder( state, "cannot convert %1% to JSON", showType(v) @@ -119,7 +119,7 @@ void printValueAsJSON(EvalState & state, bool strict, json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, NixStringContext & context, bool copyToStore) const { - TypeError(state, "cannot convert %1% to JSON", showType()) + EvalErrorBuilder(state, "cannot convert %1% to JSON", showType()) .debugThrow(); } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 862ef355b958..7b9b3c5b587a 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -340,7 +340,7 @@ int handleExceptions(const std::string & programName, std::function fun) return 1; } catch (BaseError & e) { logError(e.info()); - return e.status; + return e.info().status; } catch (std::bad_alloc & e) { printError(error + "out of memory"); return 1; diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc index 7f0a05d5d93b..d4bead28edf1 100644 --- a/src/libstore/build/entry-points.cc +++ b/src/libstore/build/entry-points.cc @@ -33,7 +33,7 @@ void Store::buildPaths(const std::vector & reqs, BuildMode buildMod } if (failed.size() == 1 && ex) { - ex->status = worker.failingExitStatus(); + ex->withExitStatus(worker.failingExitStatus()); throw std::move(*ex); } else if (!failed.empty()) { if (ex) logError(ex->info()); @@ -104,7 +104,7 @@ void Store::ensurePath(const StorePath & path) if (goal->exitCode != Goal::ecSuccess) { if (goal->ex) { - goal->ex->status = worker.failingExitStatus(); + goal->ex->withExitStatus(worker.failingExitStatus()); throw std::move(*goal->ex); } else throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path)); diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 923ea6447d24..1ca7c7b4fc5b 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -119,7 +119,7 @@ struct TunnelLogger : public Logger if (GET_PROTOCOL_MINOR(clientVersion) >= 26) { to << STDERR_ERROR << *ex; } else { - to << STDERR_ERROR << ex->what() << ex->status; + to << STDERR_ERROR << ex->what() << ex->info().status; } } } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 1f0cb08c9495..27e458d126c7 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -335,7 +335,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * try { * e->eval(*this, env, v); * if (v.type() != nAttrs) - * throwTypeError("expected a set but found %1%", v); + * throwEvalErrorBuilder("expected a set but found %1%", v); * } catch (Error & e) { * e.addTrace(pos, errorCtx); * throw; @@ -349,7 +349,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * e->eval(*this, env, v); * try { * if (v.type() != nAttrs) - * throwTypeError("expected a set but found %1%", v); + * throwEvalErrorBuilder("expected a set but found %1%", v); * } catch (Error & e) { * e.addTrace(pos, errorCtx); * throw; @@ -411,7 +411,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s oss << einfo.msg << "\n"; - printPosMaybe(oss, "", einfo.errPos); + printPosMaybe(oss, "", einfo.pos); auto suggestions = einfo.suggestions.trim(); if (!suggestions.suggestions.empty()) { diff --git a/src/libutil/error.hh b/src/libutil/error.hh index ebe1d9cf4254..9f9302020ebf 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -84,9 +84,14 @@ inline bool operator>=(const Trace& lhs, const Trace& rhs); struct ErrorInfo { Verbosity level; hintformat msg; - std::shared_ptr errPos; + std::shared_ptr pos; std::list traces; + /** + * Exit status. + */ + unsigned int status = 1; + Suggestions suggestions; static std::optional programName; @@ -103,18 +108,21 @@ class BaseError : public std::exception protected: mutable ErrorInfo err; + /** + * Cached formatted contents of `err.msg`. + */ mutable std::optional what_; + /** + * Format `err.msg` and set `what_` to the resulting value. + */ const std::string & calcWhat() const; 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...) } - , status(status) + : err { .level = lvlError, .msg = hintfmt(args...), .status = status } { } template @@ -151,7 +159,11 @@ public: void withExitStatus(unsigned int status) { - this->status = status; + err.status = status; + } + + void atPos(std::shared_ptr pos) { + err.pos = pos; } void pushTrace(Trace trace) diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index d68ddacc0c17..89fbd194a289 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -199,7 +199,7 @@ struct JSONLogger : Logger { json["level"] = ei.level; json["msg"] = oss.str(); json["raw_msg"] = ei.msg.str(); - to_json(json, ei.errPos); + to_json(json, ei.pos); if (loggerSettings.showTrace.get() && !ei.traces.empty()) { nlohmann::json traces = nlohmann::json::array(); diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 40378e123883..017818ed509f 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -950,8 +950,8 @@ static void opServe(Strings opFlags, Strings opArgs) store->buildPaths(toDerivedPaths(paths)); out << 0; } catch (Error & e) { - assert(e.status); - out << e.status << e.msg(); + assert(e.info().status); + out << e.info().status << e.msg(); } break; } diff --git a/src/nix/eval.cc b/src/nix/eval.cc index ca1223610425..3e718ac15ec6 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -104,7 +104,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption } } else - throw TypeError(*state, "value at '%s' is not a string or an attribute set", state->positions[pos]); + EvalErrorBuilder(*state, "value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow(); }; recurse(*v, pos, *writeTo); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 217c3b360b19..4414d653261d 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -831,11 +831,11 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand auto templateDir = templateDirAttr->getString(); if (!store->isInStore(templateDir)) - throw TypeError( + EvalErrorBuilder( *evalState, "'%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()); + templateDir, templateDirAttr->getAttrPathStr()).debugThrow(); std::vector changedFiles; std::vector conflictedFiles; @@ -1305,7 +1305,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON { auto aType = visitor.maybeGetAttr("type"); if (!aType || aType->getString() != "app") - throw EvalError(*state, "not an app definition"); + EvalErrorBuilder(*state, "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 b3802a96c318..14ccd524075e 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( - EvalError(state, "puppy").debugThrow(), + EvalErrorBuilder(state, "puppy").debugThrow(), EvalError ); ASSERT_THROW( - EvalError(state, "puppy").withTrace(noPos, "doggy").debugThrow(), + EvalErrorBuilder(state, "puppy").withTrace(noPos, "doggy").debugThrow(), EvalError ); ASSERT_THROW( try { try { - EvalError(state, "puppy").withTrace(noPos, "doggy").debugThrow(); + EvalErrorBuilder(state, "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 { - EvalError(state, "puppy").withTrace(noPos, "doggy").debugThrow(); + EvalErrorBuilder(state, "puppy").withTrace(noPos, "doggy").debugThrow(); } catch (BaseError & e) { try { - EvalError(state, "beans").debugThrow(); + EvalErrorBuilder(state, "beans").debugThrow(); } catch (Error & e2) { e.addTrace(state.positions[noPos], "beans2", ""); //e2.addTrace(state.positions[noPos], "Something", ""); diff --git a/tests/unit/libutil/logging.cc b/tests/unit/libutil/logging.cc index 8950a26d4e9a..6f9b4686369b 100644 --- a/tests/unit/libutil/logging.cc +++ b/tests/unit/libutil/logging.cc @@ -262,7 +262,7 @@ namespace nix { auto oneliner_file = testTable.create(one_liner); auto invalidfilename = testTable.create("invalid filename"); - auto e = AssertionError(ErrorInfo { + auto e = EvalErrorBuilder(ErrorInfo { .name = "wat", .msg = hintfmt("it has been %1% days since our last error", "zero"), .errPos = Pos(foString, problem_file, 2, 13), @@ -288,7 +288,7 @@ namespace nix { auto oneliner_file = testTable.create(one_liner); auto invalidfilename = testTable.create("invalid filename"); - auto e = AssertionError(ErrorInfo { + auto e = EvalErrorBuilder(ErrorInfo { .name = "wat", .msg = hintfmt("it has been %1% days since our last error", "zero"), .errPos = Pos(foString, problem_file, 2, 13),