diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 27b333807..050b49833 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -836,6 +836,37 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & evaluator. So here are some helper functions for throwing exceptions. */ +template +void EvalState::throwFrameErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr) +{ + hintformat f(format); + if (!s1.empty()) { f = f % s1; } + if (!s2.empty()) { f = f % s2; } + if (sym1) { f = f % symbols[*sym1]; } + if (sym2) { f = f % symbols[*sym2]; } + if (val1) { f = f % showType(*val1); } + if (val2) { f = f % showType(*val2); } + if (pos1) { f = f % positions[pos1]; } + if (!s3.empty()) { f = f % s3; } + + auto e = ErrorType(ErrorInfo { + .msg = f, + .errPos = positions[pos], + .suggestions = suggestions ? *suggestions : Suggestions(), + }); + e.addTrace(positions[tracePos], traceStr, true); + debugThrow(e, env, expr); +} + template void EvalState::throwErrorWithTrace( PosIdx pos, const char* format, @@ -901,9 +932,9 @@ void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) e.addTrace(std::nullopt, s, s2); } -void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const +void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const { - e.addTrace(positions[pos], s, s2); + e.addTrace(positions[pos], hintfmt(s, s2), frame); } static std::unique_ptr makeDebugTraceStacker( @@ -1164,7 +1195,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwError(pos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1178,7 +1209,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - throwError(pos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + throwError(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1502,7 +1533,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throwErrorWithTrace(lambda.pos, + throwFrameErrorWithTrace(lambda.pos, "function '%1%' called without required argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); @@ -1525,7 +1556,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - throwErrorWithTrace(lambda.pos, + throwFrameErrorWithTrace(lambda.pos, "function '%1%' called with unexpected argument '%2%'", (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index fd5ac3e77..3d72d3fe1 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -312,6 +312,19 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); + template + [[gnu::noinline, gnu::noreturn]] + void throwFrameErrorWithTrace( + PosIdx pos, const char* format, + const std::string_view s1, const std::string_view s2, + const Symbol * sym1, const Symbol * sym2, + Value * val1, Value * val2, + PosIdx pos1, + const std::string_view s3, + const Suggestions * suggestions, + PosIdx tracePos, const std::string_view traceStr, + Env * env, Expr * expr); + template [[gnu::noinline, gnu::noreturn]] void throwErrorWithTrace( @@ -340,7 +353,7 @@ public: [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; [[gnu::noinline]] - void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; + void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const; public: /* Return true iff the value `v' denotes a derivation (i.e. a diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9be4bbf6a..625b0aa4a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1171,9 +1171,9 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * } } catch (Error & e) { - e.addTrace(state.positions[posDrvName], - "while evaluating the attribute '%1%' of the derivation '%2%'", - key, drvName); + e.addTrace(state.positions[noPos], + hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName), + true); throw; } } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 5f86e1e76..cf4f4a56f 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -9,9 +9,9 @@ namespace nix { const std::string nativeSystem = SYSTEM; -void BaseError::addTrace(std::optional e, hintformat hint) +void BaseError::addTrace(std::optional e, hintformat hint, bool frame) { - err.traces.push_front(Trace { .pos = e, .hint = hint }); + err.traces.push_front(Trace { .pos = e, .hint = hint, .frame = frame }); } // c++ std::exception descendants must have a 'const char* what()' function. @@ -382,6 +382,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s * */ + bool frameOnly = false; if (!einfo.traces.empty()) { unsigned int count = 0; for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { @@ -391,7 +392,11 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s } if (iter->hint.str().empty()) continue; + if (frameOnly && !iter->frame) continue; + count++; + frameOnly = iter->frame; + oss << "\n" << "… " << iter->hint.str() << "\n"; if (iter->pos.has_value() && (*iter->pos)) { diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 50335676e..bf99581e2 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -110,6 +110,7 @@ void printAtPos(const ErrPos & pos, std::ostream & out); struct Trace { std::optional pos; hintformat hint; + bool frame; }; struct ErrorInfo { @@ -188,7 +189,7 @@ public: addTrace(e, hintfmt(std::string(fs), args...)); } - void addTrace(std::optional e, hintformat hint); + void addTrace(std::optional e, hintformat hint, bool frame = false); bool hasTrace() const { return !err.traces.empty(); } };