From 8c3afd2d685bdd616dcdd856803e571578685426 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 22 Oct 2022 23:37:54 +0200 Subject: [PATCH] Introduce an Error builder to tackle complexity --- mk/lib.mk | 2 + src/libexpr/eval-cache.cc | 16 +-- src/libexpr/eval-inline.hh | 6 +- src/libexpr/eval.cc | 202 ++++++++++++++++++------------------- src/libexpr/eval.hh | 73 +++++++++----- 5 files changed, 160 insertions(+), 139 deletions(-) diff --git a/mk/lib.mk b/mk/lib.mk index 92f0983d5..49f505ff8 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -87,6 +87,8 @@ endif # Pass -g if we want debug info. BUILD_DEBUG ?= 1 +GLOBAL_CXXFLAGS += -gdwarf-4 + ifeq ($(BUILD_DEBUG), 1) GLOBAL_CFLAGS += -g GLOBAL_CXXFLAGS += -g diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 4eb3fb53e..df5348148 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -571,14 +571,14 @@ std::string AttrCursor::getString() debug("using cached string attribute '%s'", getAttrPathStr()); return s->first; } else - root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nString && v.type() != nPath) - root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); return v.type() == nString ? v.string.s : v.path; } @@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext() return *s; } } else - root->state.throwError(noPos, "'%s' is not a string", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow(); } } @@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext() else if (v.type() == nPath) return {v.path, {}}; else - root->state.throwError(noPos, "'%s' is not a string but %s", getAttrPathStr(), "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); } bool AttrCursor::getBool() @@ -626,14 +626,14 @@ bool AttrCursor::getBool() debug("using cached Boolean attribute '%s'", getAttrPathStr()); return *b; } else - root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nBool) - root->state.throwError(noPos, "'%s' is not a Boolean", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); return v.boolean; } @@ -703,14 +703,14 @@ std::vector AttrCursor::getAttrs() debug("using cached attrset attribute '%s'", getAttrPathStr()); return *attrs; } else - root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); } } auto & v = forceValue(); if (v.type() != nAttrs) - root->state.throwError(noPos, "'%s' is not an attribute set", getAttrPathStr(), "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); std::vector attrs; for (auto & attr : *getValue().attrs) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 43a4e8fa6..279f5b8be 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -103,7 +103,7 @@ void EvalState::forceValue(Value & v, Callable getPos) else if (v.isApp()) callFunction(*v.app.left, *v.app.right, v, noPos); else if (v.isBlackhole()) - throwError(getPos(), "infinite recursion encountered", "", "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("infinite recursion encountered").atPos(getPos()).debugThrow(); } @@ -121,7 +121,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e forceValue(v, noPos); if (v.type() != nAttrs) { PosIdx pos = getPos(); - this->throwErrorWithTrace(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + this->error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } @@ -131,7 +131,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e { forceValue(v, noPos); if (!v.isList()) { - this->throwErrorWithTrace(noPos, "value is %1% while a list was expected", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + this->error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0a9ac68be..bd102b186 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -418,6 +418,59 @@ static Strings parseNixPath(const std::string & s) return res; } +template +ErrorBuilder::ErrorBuilder(EvalState & s): + state(s), + info(ErrorInfo { .msg = hintformat(""), .errPos = std::nullopt, }) +{} + +template +ErrorBuilder & ErrorBuilder::atPos(PosIdx pos) +{ + info.errPos = state.positions[pos]; + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text) +{ + info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false }); + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text) +{ + info.traces.push_back(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true }); + return *this; +} + +template +ErrorBuilder & ErrorBuilder::suggestions(Suggestions & s) { + info.suggestions = s; + return *this; +} + +template +ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr) { + // NOTE: This is abusing side-effects. + // TODO: check compatibility with nested debugger calls. + state.debugTraces.push_front(DebugTrace { + .pos = std::nullopt, + .expr = expr, + .env = env, + .hint = hintformat("Fake frame for debugg{ing,er} purposes"), + .isError = true + }); + return *this; +} + +template +void ErrorBuilder::debugThrow() { + // NOTE: We always use the -LastTrace version as we push the new trace in withFrame() + state.debugThrowLastTrace(ErrorType(info)); +} + EvalState::EvalState( const Strings & _searchPath, @@ -831,71 +884,6 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & } } -/* Every "format" object (even temporary) takes up a few hundred bytes - of stack space, which is a real killer in the recursive - evaluator. So here are some helper functions for throwing - exceptions. */ - -template -void EvalState::throwErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr) -{ - hintformat f(format); - if (!s1.empty()) { f = f % s1; } - if (!s2.empty()) { f = f % s2; } - if (sym1) { f = f % symbols[*sym1]; } - if (sym2) { f = f % symbols[*sym2]; } - if (val1) { f = f % showType(*val1); } - if (val2) { f = f % showType(*val2); } - if (pos1) { f = f % positions[pos1]; } - if (!s3.empty()) { f = f % s3; } - - auto e = ErrorType(ErrorInfo { - .msg = f, - .errPos = positions[pos], - .suggestions = suggestions ? *suggestions : Suggestions(), - }); - e.addTrace(positions[tracePos], traceStr); - debugThrow(e, env, expr); -} - -template -void EvalState::throwError( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - Env * env, Expr * expr) -{ - hintformat f(format); - if (!s1.empty()) { f = f % s1; } - if (!s2.empty()) { f = f % s2; } - if (sym1) { f = f % symbols[*sym1]; } - if (sym2) { f = f % symbols[*sym2]; } - if (val1) { f = f % showType(*val1); } - if (val2) { f = f % showType(*val2); } - if (pos1) { f = f % positions[pos1]; } - if (!s3.empty()) { f = f % s3; } - - auto e = ErrorType(ErrorInfo { - .msg = f, - .errPos = positions[pos], - .suggestions = suggestions ? *suggestions : Suggestions(), - }); - debugThrow(e, env, expr); -} - void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { e.addTrace(std::nullopt, s, s2); @@ -990,7 +978,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - throwError(var.pos, "undefined variable '%1%'", "", "", &var.name, 0, 0, 0, noPos, "", 0, env, const_cast(&var)); + error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1140,7 +1128,7 @@ void EvalState::cacheFile( // computation. if (mustBeTrivial && !(dynamic_cast(e))) - throwError(noPos, "file '%s' must be an attribute set", path, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("file '%s' must be an attribute set", path).debugThrow(); eval(e, v); } catch (Error & e) { addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); @@ -1164,7 +1152,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri Value v; e->eval(*this, env, v); if (v.type() != nBool) - throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -1178,7 +1166,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po try { e->eval(*this, env, v); if (v.type() != nAttrs) - throwError(noPos, "value is %1% while a set was expected", "", "", 0, 0, &v, 0, noPos, "", 0, &env, e); + error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -1287,7 +1275,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - state.throwError(i.pos, "dynamic attribute '%1%' already defined at %2%", "", "", &nameSym, 0, 0, 0, j->pos, "", 0, &env, this); + state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1390,9 +1378,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) for (auto & attr : *vAttrs->attrs) allAttrNames.insert(state.symbols[attr.name]); auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); - state.throwError(pos, "attribute '%1%' missing", - "", "", &name, 0, 0, 0, noPos, "", &suggestions, - &env, this); + state.error("attribute '%1%' missing", state.symbols[name]) + .atPos(pos).suggestions(suggestions).withFrame(env, *this).debugThrow(); } } vAttrs = j->value; @@ -1502,10 +1489,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto j = args[0]->attrs->get(i.name); if (!j) { if (!i.def) { - throwErrorWithTrace(lambda.pos, - "function '%1%' called without required argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", - &i.name, 0, 0, 0, noPos, "", 0, pos, "from call site", fun.lambda.env, &lambda); + error("function '%1%' called without required argument '%2%'", + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + symbols[i.name]) + .atPos(lambda.pos) + .withTrace(pos, "from call site") + .withFrame(*fun.lambda.env, lambda) + .debugThrow(); } env2.values[displ++] = i.def->maybeThunk(*this, env2); } else { @@ -1525,10 +1515,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & for (auto & formal : lambda.formals->formals) formalNames.insert(symbols[formal.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); - throwErrorWithTrace(lambda.pos, - "function '%1%' called with unexpected argument '%2%'", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), "", - &i.name, 0, 0, 0, noPos, "", &suggestions, pos, "from call site", fun.lambda.env, &lambda); + error("function '%1%' called with unexpected argument '%2%'", + (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + symbols[i.name]) + .atPos(lambda.pos) + .withTrace(pos, "from call site") + .suggestions(suggestions) + .withFrame(*fun.lambda.env, lambda) + .debugThrow(); } abort(); // can't happen } @@ -1553,8 +1547,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (loggerSettings.showTrace.get()) { addErrorTrace(e, lambda.pos, - "while evaluating the '%s' function", - (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), + "while evaluating %s", + lambda.name + ? concatStrings("'", symbols[lambda.name], "'") + : "anonymous lambda", true); if (pos) addErrorTrace(e, pos, "from call site%s", "", true); } @@ -1657,7 +1653,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } else - throwError(pos, "attempt to call something which is not a function but %1%", "", "", 0, 0, &vCur, 0, noPos, "", 0, 0, 0); + error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow(); } vRes = vCur; @@ -1721,12 +1717,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) if (j != args.end()) { attrs.insert(*j); } else if (!i.def) { - throwError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') + error(R"(cannot evaluate a function that has an argument without a value ('%1%') Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", "", "", &i.name, 0, 0, 0, noPos, "", 0, - fun.lambda.env, fun.lambda.fun); +https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name]) + .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow(); } } } @@ -1759,7 +1755,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { std::ostringstream out; cond->show(state.symbols, out); - state.throwError(pos, "assertion '%1%' failed", out.str(), "", 0, 0, 0, 0, noPos, "", 0, &env, this); + state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); } body->eval(state, env, v); } @@ -1936,14 +1932,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - state.throwError(i_pos, "cannot add %1% to an integer", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); + state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - state.throwError(i_pos, "cannot add %1% to a float", "", "", 0, 0, &vTmp, 0, noPos, "", 0, &env, this); + state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -1963,7 +1959,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - state.throwError(pos, "a string that refers to a store path cannot be appended to a path", "", "", 0, 0, 0, 0, noPos, "", 0, &env, this); + state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); @@ -2018,7 +2014,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt try { forceValue(v, pos); if (v.type() != nInt) - throwError(noPos, "value is %1% while an integer was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while an integer was expected", showType(v)).debugThrow(); return v.integer; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2034,7 +2030,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err if (v.type() == nInt) return v.integer; else if (v.type() != nFloat) - throwError(noPos, "value is %1% while a float was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a float was expected", showType(v)).debugThrow(); return v.fpoint; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2048,7 +2044,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx try { forceValue(v, pos); if (v.type() != nBool) - throwError(noPos, "value is %1% while a Boolean was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a Boolean was expected", showType(v)).debugThrow(); return v.boolean; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2068,7 +2064,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro try { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) - throwError(noPos, "value is %1% while a function was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a function was expected", showType(v)).debugThrow(); } catch (Error & e) { e.addTrace(positions[pos], errorCtx); throw; @@ -2081,7 +2077,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string try { forceValue(v, pos); if (v.type() != nString) - throwError(noPos, "value is %1% while a string was expected", "", "", 0, 0, &v, 0, noPos, "", 0, 0, 0); + error("value is %1% while a string was expected", showType(v)).debugThrow(); return v.string.s; } catch (Error & e) { e.addTrace(positions[pos], errorCtx); @@ -2142,7 +2138,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s { auto s = forceString(v, pos, errorCtx); if (v.string.context) { - throwErrorWithTrace(noPos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0], 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow(); } return s; } @@ -2198,7 +2194,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet return std::move(*maybeString); auto i = v.attrs->find(sOutPath); if (i == v.attrs->end()) - throwErrorWithTrace(noPos, "cannot coerce a set to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx); } @@ -2233,14 +2229,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet } } - throwErrorWithTrace(noPos, "cannot coerce %1% to a string", "", "", 0, 0, &v, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow(); } std::string EvalState::copyPathToStore(PathSet & context, const Path & path) { if (nix::isDerivation(path)) - throwError(noPos, "file names are not allowed to end in '%1%'", drvExtension, "", 0, 0, 0, 0, noPos, "", 0, 0, 0); + error("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); Path dstPath; auto i = srcToStore.find(path); @@ -2265,7 +2261,7 @@ Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std { auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (path == "" || path[0] != '/') - throwErrorWithTrace(noPos, "string '%1%' doesn't represent an absolute path", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); return path; } @@ -2275,7 +2271,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & co auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - throwErrorWithTrace(noPos, "path '%1%' is not in the Nix store", path, "", 0, 0, 0, 0, noPos, "", 0, pos, errorCtx, 0, 0); + error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } @@ -2352,7 +2348,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v return v1.fpoint == v2.fpoint; default: - throwErrorWithTrace(noPos, "cannot compare %1% with %2%", "", "", 0, 0, &v1, &v2, noPos, "", 0, pos, errorCtx, 0, 0); + error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); } } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 234b5c06b..ff407d090 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -87,6 +87,47 @@ struct DebugTrace { void debugError(Error * e, Env & env, Expr & expr); +template +class ErrorBuilder +{ + + EvalState & state; + ErrorInfo info; + + public: + [[gnu::noinline]] + ErrorBuilder(EvalState & s); + + [[gnu::noinline]] + ErrorBuilder & atPos(PosIdx pos); + + template + [[gnu::noinline]] + ErrorBuilder & msg(const std::string & fs, const Args & ... args) + { + hintformat f(fs); + formatHelper(f, args...); + info.msg = f; + return *this; + } + + [[gnu::noinline]] + ErrorBuilder & withTrace(PosIdx pos, const std::string_view text); + + [[gnu::noinline]] + ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text); + + [[gnu::noinline]] + ErrorBuilder & suggestions(Suggestions & s); + + [[gnu::noinline]] + ErrorBuilder & withFrame(const Env & e, const Expr & ex); + + [[gnu::noinline, gnu::noreturn]] + void debugThrow(); +}; + + class EvalState : public std::enable_shared_from_this { public: @@ -167,6 +208,13 @@ public: throw std::move(error); } + template + ErrorBuilder & error(const std::string & fs, const Args & ... args) { + ErrorBuilder * errorBuilder = new ErrorBuilder(*this); + errorBuilder->msg(fs, args ...); + return *errorBuilder; + } + private: SrcToStore srcToStore; @@ -312,31 +360,6 @@ public: std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); - template - [[gnu::noinline, gnu::noreturn]] - void throwErrorWithTrace( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - PosIdx tracePos, const std::string_view traceStr, - Env * env, Expr * expr); - - template - [[gnu::noinline, gnu::noreturn]] - void throwError( - PosIdx pos, const char* format, - const std::string_view s1, const std::string_view s2, - const Symbol * sym1, const Symbol * sym2, - Value * val1, Value * val2, - PosIdx pos1, - const std::string_view s3, - const Suggestions * suggestions, - Env * env, Expr * expr); - [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; [[gnu::noinline]]