libexpr: extract eval error creation into new type

this will let us pass the capability to create debuggable eval errors
without having to pass an entire EvalState. we could pass symbols and
debug states around just as easily, but if we add new capabilities to
our debugger we might have to change many more places than with this.

Change-Id: I2f8893012e5d98a986ef1fc888234c2dd8d5e096
This commit is contained in:
eldritch horrors 2024-12-03 20:38:41 +01:00
parent a4fa93d469
commit a65e9e5828
21 changed files with 178 additions and 168 deletions

View file

@ -988,7 +988,7 @@ Value * NixRepl::replOverlays()
auto replInit = evalFile(sourcePath); auto replInit = evalFile(sourcePath);
if (!replInit->isLambda()) { if (!replInit->isLambda()) {
state.error<TypeError>( state.errors.make<TypeError>(
"Expected `repl-overlays` to be a lambda but found %1%: %2%", "Expected `repl-overlays` to be a lambda but found %1%: %2%",
showType(*replInit), showType(*replInit),
ValuePrinter(state, *replInit, errorPrintOptions) ValuePrinter(state, *replInit, errorPrintOptions)
@ -999,7 +999,7 @@ Value * NixRepl::replOverlays()
if (replInit->lambda.fun->hasFormals() if (replInit->lambda.fun->hasFormals()
&& !replInit->lambda.fun->formals->ellipsis) { && !replInit->lambda.fun->formals->ellipsis) {
state.error<TypeError>( state.errors.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument", "Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument",
"repl-overlays", "repl-overlays",
"..." "..."

View file

@ -65,7 +65,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) { if (!attrIndex) {
if (v->type() != nAttrs) if (v->type() != nAttrs)
state.error<TypeError>( state.errors.make<TypeError>(
"the expression selected by the selection path '%1%' should be a set but is %2%", "the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath, attrPath,
showType(*v)).debugThrow(); showType(*v)).debugThrow();
@ -88,7 +88,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
else { else {
if (!v->isList()) if (!v->isList())
state.error<TypeError>( state.errors.make<TypeError>(
"the expression selected by the selection path '%1%' should be a list but is %2%", "the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath, attrPath,
showType(*v)).debugThrow(); showType(*v)).debugThrow();

View file

@ -506,7 +506,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
// evaluate to see whether 'name' exists // evaluate to see whether 'name' exists
} else } else
return nullptr; return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); //errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
} }
} }
@ -514,7 +514,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
if (v.type() != nAttrs) if (v.type() != nAttrs)
return nullptr; return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); //errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(name); auto attr = v.attrs->get(name);
@ -580,14 +580,14 @@ std::string AttrCursor::getString()
debug("using cached string attribute '%s'", getAttrPathStr()); debug("using cached string attribute '%s'", getAttrPathStr());
return s->first; return s->first;
} else } else
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nString && v.type() != nPath) { if (v.type() != nString && v.type() != nPath) {
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow();
} }
return v.type() == nString ? v.string.s : v.path().to_string(); return v.type() == nString ? v.string.s : v.path().to_string();
@ -623,7 +623,7 @@ string_t AttrCursor::getStringWithContext()
return *s; return *s;
} }
} else } else
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
} }
} }
@ -636,7 +636,7 @@ string_t AttrCursor::getStringWithContext()
} else if (v.type() == nPath) { } else if (v.type() == nPath) {
return {v.path().to_string(), {}}; return {v.path().to_string(), {}};
} else { } else {
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a string but %s", getAttrPathStr(), v.type()).debugThrow();
} }
} }
@ -650,14 +650,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr()); debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b; return *b;
} else } else
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nBool) if (v.type() != nBool)
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
return v.boolean; return v.boolean;
} }
@ -672,14 +672,14 @@ NixInt AttrCursor::getInt()
debug("using cached integer attribute '%s'", getAttrPathStr()); debug("using cached integer attribute '%s'", getAttrPathStr());
return i->x; return i->x;
} else } else
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nInt) if (v.type() != nInt)
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
return v.integer; return v.integer;
} }
@ -694,7 +694,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
debug("using cached list of strings attribute '%s'", getAttrPathStr()); debug("using cached list of strings attribute '%s'", getAttrPathStr());
return *l; return *l;
} else } else
root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
} }
} }
@ -704,7 +704,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
root->state.forceValue(v, noPos); root->state.forceValue(v, noPos);
if (v.type() != nList) if (v.type() != nList)
root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
std::vector<std::string> res; std::vector<std::string> res;
@ -727,14 +727,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
debug("using cached attrset attribute '%s'", getAttrPathStr()); debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs; return *attrs;
} else } else
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nAttrs) if (v.type() != nAttrs)
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); root->state.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs) for (auto & attr : *getValue().attrs)

View file

@ -14,7 +14,7 @@ EvalErrorBuilder<T> EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
template<class T> template<class T>
EvalErrorBuilder<T> EvalErrorBuilder<T>::atPos(PosIdx pos) && EvalErrorBuilder<T> EvalErrorBuilder<T>::atPos(PosIdx pos) &&
{ {
error->err.pos = state.positions[pos]; error->err.pos = positions[pos];
return std::move(*this); return std::move(*this);
} }
@ -28,7 +28,7 @@ template<class T>
EvalErrorBuilder<T> EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text) && EvalErrorBuilder<T> EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text) &&
{ {
error->err.traces.push_front( error->err.traces.push_front(
Trace{.pos = state.positions[pos], .hint = HintFmt(std::string(text))}); Trace{.pos = positions[pos], .hint = HintFmt(std::string(text))});
return std::move(*this); return std::move(*this);
} }
@ -42,9 +42,9 @@ EvalErrorBuilder<T> EvalErrorBuilder<T>::withSuggestions(Suggestions & s) &&
template<class T> template<class T>
EvalErrorBuilder<T> EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr) && EvalErrorBuilder<T> EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr) &&
{ {
if (state.debug) { if (debug) {
error->frame = state.debug->addTrace(DebugTrace{ error->frame = debug->addTrace(DebugTrace{
.pos = state.positions[expr.getPos()], .pos = positions[expr.getPos()],
.expr = expr, .expr = expr,
.env = env, .env = env,
.hint = HintFmt("Fake frame for debugging purposes"), .hint = HintFmt("Fake frame for debugging purposes"),
@ -57,7 +57,7 @@ EvalErrorBuilder<T> EvalErrorBuilder<T>::withFrame(const Env & env, const Expr &
template<class T> template<class T>
EvalErrorBuilder<T> EvalErrorBuilder<T>::addTrace(PosIdx pos, HintFmt hint) && EvalErrorBuilder<T> EvalErrorBuilder<T>::addTrace(PosIdx pos, HintFmt hint) &&
{ {
error->addTrace(state.positions[pos], hint); error->addTrace(positions[pos], hint);
return std::move(*this); return std::move(*this);
} }
@ -67,18 +67,18 @@ EvalErrorBuilder<T>
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) && EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs) &&
{ {
addTrace(state.positions[pos], HintFmt(std::string(formatString), formatArgs...)); addTrace(positions[pos], HintFmt(std::string(formatString), formatArgs...));
return std::move(*this); return std::move(*this);
} }
template<class T> template<class T>
void EvalErrorBuilder<T>::debugThrow() && void EvalErrorBuilder<T>::debugThrow() &&
{ {
if (state.debug) { if (debug) {
if (auto last = state.debug->traces().next()) { if (auto last = debug->traces().next()) {
const Env * env = &(*last)->env; const Env * env = &(*last)->env;
const Expr * expr = &(*last)->expr; const Expr * expr = &(*last)->expr;
state.debug->onEvalError(error.get(), *env, *expr); debug->onEvalError(error.get(), *env, *expr);
} }
} }

View file

@ -5,9 +5,11 @@
#include "lix/libutil/error.hh" #include "lix/libutil/error.hh"
#include "lix/libutil/types.hh" #include "lix/libutil/types.hh"
#include "lix/libexpr/pos-idx.hh" #include "lix/libexpr/pos-idx.hh"
#include "lix/libexpr/pos-table.hh"
namespace nix { namespace nix {
struct DebugState;
struct DebugTrace; struct DebugTrace;
struct Env; struct Env;
struct Expr; struct Expr;
@ -55,18 +57,18 @@ public:
template<class T> template<class T>
class [[nodiscard]] EvalErrorBuilder final class [[nodiscard]] EvalErrorBuilder final
{ {
friend class EvalState; const PosTable & positions;
DebugState * debug;
EvalState & state; box_ptr<T> error;
template<typename... Args>
explicit EvalErrorBuilder(EvalState & state, const Args &... args)
: state(state), error(make_box_ptr<T>(args...))
{
}
public: public:
box_ptr<T> error; template<typename... Args>
explicit EvalErrorBuilder(const PosTable & positions, DebugState * debug, const Args &... args)
: positions(positions)
, debug{debug}
, error(make_box_ptr<T>(args...))
{
}
[[gnu::noinline]] EvalErrorBuilder<T> withExitStatus(unsigned int exitStatus) &&; [[gnu::noinline]] EvalErrorBuilder<T> withExitStatus(unsigned int exitStatus) &&;

View file

@ -91,7 +91,7 @@ inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nAttrs) { if (v.type() != nAttrs) {
error<TypeError>( errors.make<TypeError>(
"expected a set but found %1%: %2%", "expected a set but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -105,7 +105,7 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
{ {
forceValue(v, pos); forceValue(v, pos);
if (!v.isList()) { if (!v.isList()) {
error<TypeError>( errors.make<TypeError>(
"expected a list but found %1%: %2%", "expected a list but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)

View file

@ -299,6 +299,7 @@ EvalState::EvalState(
) )
: nullptr : nullptr
} }
, errors{positions, debug.get()}
{ {
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@ -750,7 +751,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value; return j->value;
} }
if (!fromWith->parentWith) if (!fromWith->parentWith)
error<UndefinedVarError>("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow(); errors.make<UndefinedVarError>("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow();
for (size_t l = fromWith->prevWith; l; --l, env = env->up) ; for (size_t l = fromWith->prevWith; l; --l, env = env->up) ;
fromWith = fromWith->parentWith; fromWith = fromWith->parentWith;
} }
@ -981,7 +982,7 @@ inline bool EvalState::evalBool(Env & env, Expr & e, const PosIdx pos, std::stri
Value v; Value v;
e.eval(*this, env, v); e.eval(*this, env, v);
if (v.type() != nBool) if (v.type() != nBool)
error<TypeError>( errors.make<TypeError>(
"expected a Boolean but found %1%: %2%", "expected a Boolean but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -999,7 +1000,7 @@ inline void EvalState::evalAttrs(Env & env, Expr & e, Value & v, const PosIdx po
try { try {
e.eval(*this, env, v); e.eval(*this, env, v);
if (v.type() != nAttrs) if (v.type() != nAttrs)
error<TypeError>( errors.make<TypeError>(
"expected a set but found %1%: %2%", "expected a set but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -1131,7 +1132,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
auto nameSym = state.symbols.create(nameVal.string.s); auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym); Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end()) if (j != v.attrs->end())
state.error<EvalError>("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow(); state.errors.make<EvalError>("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); i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */ /* Keep sorted order so find can catch duplicates */
@ -1306,7 +1307,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
} }
// Otherwise, we must type error. // Otherwise, we must type error.
state.error<TypeError>( state.errors.make<TypeError>(
"expected a set but found %s: %s", "expected a set but found %s: %s",
showType(*vCurrent), showType(*vCurrent),
ValuePrinter(state, *vCurrent, errorPrintOptions) ValuePrinter(state, *vCurrent, errorPrintOptions)
@ -1333,7 +1334,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
allAttrNames.insert(state.symbols[attr.name]); allAttrNames.insert(state.symbols[attr.name]);
} }
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]); auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error<EvalError>("attribute '%s' missing", state.symbols[name]) state.errors.make<EvalError>("attribute '%s' missing", state.symbols[name])
.atPos(pos) .atPos(pos)
.withSuggestions(suggestions) .withSuggestions(suggestions)
.withFrame(env, *this) .withFrame(env, *this)
@ -1469,7 +1470,7 @@ FormalsMatch matchupFormals(EvalState & state, Env & env, Displacement & displ,
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{ {
if (callDepth > evalSettings.maxCallDepth) if (callDepth > evalSettings.maxCallDepth)
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); errors.make<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
CallDepth _level(callDepth); CallDepth _level(callDepth);
auto trace = evalSettings.traceFunctionCalls auto trace = evalSettings.traceFunctionCalls
@ -1531,7 +1532,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
); );
for (auto const & missingArg : formalsMatch.missing) { for (auto const & missingArg : formalsMatch.missing) {
auto const missing = symbols[missingArg]; auto const missing = symbols[missingArg];
error<TypeError>("function '%s' called without required argument '%s'", lambda.getName(symbols), missing) errors.make<TypeError>("function '%s' called without required argument '%s'", lambda.getName(symbols), missing)
.atPos(lambda.pos) .atPos(lambda.pos)
.withTrace(pos, "from call site") .withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda) .withFrame(*fun.lambda.env, lambda)
@ -1544,7 +1545,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
formalNames.insert(symbols[formal.name]); formalNames.insert(symbols[formal.name]);
} }
auto sug = Suggestions::bestMatches(formalNames, unex); auto sug = Suggestions::bestMatches(formalNames, unex);
error<TypeError>("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex) errors.make<TypeError>("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex)
.atPos(lambda.pos) .atPos(lambda.pos)
.withTrace(pos, "from call site") .withTrace(pos, "from call site")
.withSuggestions(sug) .withSuggestions(sug)
@ -1685,7 +1686,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
} }
else else
error<TypeError>( errors.make<TypeError>(
"attempt to call something which is not a function but %1%: %2%", "attempt to call something which is not a function but %1%: %2%",
showType(vCur), showType(vCur),
ValuePrinter(*this, vCur, errorPrintOptions)) ValuePrinter(*this, vCur, errorPrintOptions))
@ -1772,7 +1773,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) { if (j != args.end()) {
attrs.insert(*j); attrs.insert(*j);
} else if (!i.def) { } else if (!i.def) {
error<MissingArgumentError>(R"(cannot evaluate a function that has an argument without a value ('%1%') errors.make<MissingArgumentError>(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 Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See values, or passed explicitly with '--arg' or '--argstr'. See
@ -1808,7 +1809,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
if (!state.evalBool(env, *cond, pos, "in the condition of the assert statement")) { if (!state.evalBool(env, *cond, pos, "in the condition of the assert statement")) {
std::ostringstream out; std::ostringstream out;
cond->show(state.symbols, out); cond->show(state.symbols, out);
state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow(); state.errors.make<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1983,7 +1984,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
if (auto checked = newN.valueChecked(); checked.has_value()) { if (auto checked = newN.valueChecked(); checked.has_value()) {
n = NixInt(*checked); n = NixInt(*checked);
} else { } else {
state.error<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer).atPos(i_pos).debugThrow(); state.errors.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer).atPos(i_pos).debugThrow();
} }
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
// Upgrade the type from int to float; // Upgrade the type from int to float;
@ -1991,14 +1992,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n.value; nf = n.value;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); state.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else if (firstType == nFloat) { } else if (firstType == nFloat) {
if (vTmp.type() == nInt) { if (vTmp.type() == nInt) {
nf += vTmp.integer.value; nf += vTmp.integer.value;
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow(); state.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else { } else {
if (s.empty()) s.reserve(es.size()); if (s.empty()) s.reserve(es.size());
/* skip canonization of first path, which would only be not /* skip canonization of first path, which would only be not
@ -2020,7 +2021,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf); v.mkFloat(nf);
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow(); state.errors.make<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
v.mkPath(CanonPath(canonPath(str()))); v.mkPath(CanonPath(canonPath(str())));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -2035,7 +2036,7 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v) void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
{ {
state.error<InfiniteRecursionError>("infinite recursion encountered") state.errors.make<InfiniteRecursionError>("infinite recursion encountered")
.atPos(v.determinePos(noPos)) .atPos(v.determinePos(noPos))
.debugThrow(); .debugThrow();
} }
@ -2099,7 +2100,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
try { try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nInt) if (v.type() != nInt)
error<TypeError>( errors.make<TypeError>(
"expected an integer but found %1%: %2%", "expected an integer but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2121,7 +2122,7 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
if (v.type() == nInt) if (v.type() == nInt)
return v.integer.value; return v.integer.value;
else if (v.type() != nFloat) else if (v.type() != nFloat)
error<TypeError>( errors.make<TypeError>(
"expected a float but found %1%: %2%", "expected a float but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2139,7 +2140,7 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
try { try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nBool) if (v.type() != nBool)
error<TypeError>( errors.make<TypeError>(
"expected a Boolean but found %1%: %2%", "expected a Boolean but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2165,7 +2166,7 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro
try { try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v)) if (v.type() != nFunction && !isFunctor(v))
error<TypeError>( errors.make<TypeError>(
"expected a function but found %1%: %2%", "expected a function but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2182,7 +2183,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
try { try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nString) if (v.type() != nString)
error<TypeError>( errors.make<TypeError>(
"expected a string but found %1%: %2%", "expected a string but found %1%: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2215,7 +2216,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
{ {
auto s = forceString(v, pos, errorCtx); auto s = forceString(v, pos, errorCtx);
if (v.string.context) { if (v.string.context) {
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow(); errors.make<EvalError>("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; return s;
} }
@ -2280,7 +2281,7 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString); return std::move(*maybeString);
auto i = v.attrs->find(s.outPath); auto i = v.attrs->find(s.outPath);
if (i == v.attrs->end()) { if (i == v.attrs->end()) {
error<TypeError>( errors.make<TypeError>(
"cannot coerce %1% to a string: %2%", "cannot coerce %1% to a string: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
@ -2330,7 +2331,7 @@ BackedStringView EvalState::coerceToString(
} }
} }
error<TypeError>("cannot coerce %1% to a string: %2%", errors.make<TypeError>("cannot coerce %1% to a string: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions) ValuePrinter(*this, v, errorPrintOptions)
) )
@ -2342,7 +2343,7 @@ BackedStringView EvalState::coerceToString(
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path) StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
{ {
if (nix::isDerivation(path.path.abs())) if (nix::isDerivation(path.path.abs()))
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); errors.make<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
@ -2367,7 +2368,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
{ {
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
error<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow(); errors.make<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
return CanonPath(path); return CanonPath(path);
} }
@ -2377,7 +2378,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (auto storePath = store->maybeParseStorePath(path)) if (auto storePath = store->maybeParseStorePath(path))
return *storePath; return *storePath;
error<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); errors.make<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
} }
@ -2387,7 +2388,7 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
auto s = forceString(v, context, pos, errorCtx); auto s = forceString(v, context, pos, errorCtx);
auto csize = context.size(); auto csize = context.size();
if (csize != 1) if (csize != 1)
error<EvalError>( errors.make<EvalError>(
"string '%s' has %d entries in its context. It should only have exactly one entry", "string '%s' has %d entries in its context. It should only have exactly one entry",
s, csize) s, csize)
.withTrace(pos, errorCtx).debugThrow(); .withTrace(pos, errorCtx).debugThrow();
@ -2396,7 +2397,7 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
return std::move(o); return std::move(o);
}, },
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath { [&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
error<EvalError>( errors.make<EvalError>(
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time", "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(); s).withTrace(pos, errorCtx).debugThrow();
}, },
@ -2421,13 +2422,13 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
error message. */ error message. */
std::visit(overloaded { std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) { [&](const SingleDerivedPath::Opaque & o) {
error<EvalError>( errors.make<EvalError>(
"path string '%s' has context with the different path '%s'", "path string '%s' has context with the different path '%s'",
s, sExpected) s, sExpected)
.withTrace(pos, errorCtx).debugThrow(); .withTrace(pos, errorCtx).debugThrow();
}, },
[&](const SingleDerivedPath::Built & b) { [&](const SingleDerivedPath::Built & b) {
error<EvalError>( errors.make<EvalError>(
"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'", "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) s, b.output, b.drvPath->to_string(*store), sExpected)
.withTrace(pos, errorCtx).debugThrow(); .withTrace(pos, errorCtx).debugThrow();
@ -2512,7 +2513,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nThunk: // Must not be left by forceValue case nThunk: // Must not be left by forceValue
default: default:
error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow(); errors.make<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
} }
} }
@ -2755,7 +2756,7 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
if (path.starts_with("nix/")) if (path.starts_with("nix/"))
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4))); return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
error<ThrownError>( errors.make<ThrownError>(
evalSettings.pureEval evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
@ -2817,7 +2818,7 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{ {
state.error<TypeError>( state.errors.make<TypeError>(
"cannot coerce %1% to a string: %2%", showType(), *this "cannot coerce %1% to a string: %2%", showType(), *this
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
} }

View file

@ -348,6 +348,18 @@ struct EvalRuntimeCaches
std::map<SourcePath, std::shared_ptr<CachedEvalFile>> fileEval; std::map<SourcePath, std::shared_ptr<CachedEvalFile>> fileEval;
}; };
struct EvalErrorContext
{
const PosTable & positions;
DebugState * debug;
template<class T, typename... Args>
[[gnu::noinline]]
EvalErrorBuilder<T> make(const Args & ... args) {
return EvalErrorBuilder<T>(positions, debug, args...);
}
};
class EvalState class EvalState
{ {
@ -389,12 +401,7 @@ public:
const ref<Store> buildStore; const ref<Store> buildStore;
std::unique_ptr<DebugState> debug; std::unique_ptr<DebugState> debug;
EvalErrorContext errors;
template<class T, typename... Args>
[[gnu::noinline]]
EvalErrorBuilder<T> error(const Args & ... args) {
return EvalErrorBuilder<T>(*this, args...);
}
private: private:

View file

@ -142,14 +142,14 @@ static FlakeInput parseFlakeInput(EvalState & state,
auto intValue = attr.value->integer.value; auto intValue = attr.value->integer.value;
if (intValue < 0) { if (intValue < 0) {
state.error<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow(); state.errors.make<EvalError>("negative value given for flake input attribute %1%: %2%", state.symbols[attr.name], intValue).debugThrow();
} }
uint64_t asUnsigned = intValue; uint64_t asUnsigned = intValue;
attrs.emplace(state.symbols[attr.name], asUnsigned); attrs.emplace(state.symbols[attr.name], asUnsigned);
break; break;
} }
default: default:
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", state.errors.make<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow(); state.symbols[attr.name], showType(*attr.value)).debugThrow();
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
@ -247,7 +247,7 @@ static Flake getFlake(
// Enforce that 'flake.nix' is a direct attrset, not a computation. // Enforce that 'flake.nix' is a direct attrset, not a computation.
if (!(dynamic_cast<ExprAttrs *>(&flakeExpr))) { if (!(dynamic_cast<ExprAttrs *>(&flakeExpr))) {
state.error<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow(); state.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
} }
Value vInfo; Value vInfo;
@ -307,14 +307,14 @@ static Flake getFlake(
std::vector<std::string> ss; std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) { for (auto elem : setting.value->listItems()) {
if (elem->type() != nString) if (elem->type() != nString)
state.error<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected", state.errors.make<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value)).debugThrow(); state.symbols[setting.name], showType(*setting.value)).debugThrow();
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, "")); ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
} }
flake.config.settings.emplace(state.symbols[setting.name], ss); flake.config.settings.emplace(state.symbols[setting.name], ss);
} }
else else
state.error<TypeError>("flake configuration setting '%s' is %s", state.errors.make<TypeError>("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value)).debugThrow(); state.symbols[setting.name], showType(*setting.value)).debugThrow();
} }
} }
@ -858,7 +858,7 @@ void prim_flakeRefToString(
auto intValue = attr.value->integer.value; auto intValue = attr.value->integer.value;
if (intValue < 0) { if (intValue < 0) {
state.error<EvalError>("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); state.errors.make<EvalError>("negative value given for flake ref attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
} }
uint64_t asUnsigned = intValue; uint64_t asUnsigned = intValue;
@ -870,7 +870,7 @@ void prim_flakeRefToString(
attrs.emplace(state.symbols[attr.name], attrs.emplace(state.symbols[attr.name],
std::string(attr.value->str())); std::string(attr.value->str()));
} else { } else {
state.error<EvalError>( state.errors.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, " "flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s", "and strings, but attribute '%s' is %s",
state.symbols[attr.name], state.symbols[attr.name],

View file

@ -50,7 +50,7 @@ std::string DrvInfo::queryName()
{ {
if (name == "" && attrs) { if (name == "" && attrs) {
auto i = attrs->find(state->s.name); auto i = attrs->find(state->s.name);
if (i == attrs->end()) state->error<TypeError>("derivation name missing").debugThrow(); if (i == attrs->end()) state->errors.make<TypeError>("derivation name missing").debugThrow();
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation"); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
} }
return name; return name;
@ -446,7 +446,7 @@ static void getDerivations(EvalState & state, Value & vIn,
return; return;
} else if (v.type() != nAttrs) { } else if (v.type() != nAttrs) {
state.error<TypeError>( state.errors.make<TypeError>(
"expression was expected to be a derivation or collection of derivations, but instead was %s", "expression was expected to be a derivation or collection of derivations, but instead was %s",
showType(v.type(), true) showType(v.type(), true)
).debugThrow(); ).debugThrow();

View file

@ -364,7 +364,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
enclosing `with'. If there is no `with', then we can issue an enclosing `with'. If there is no `with', then we can issue an
"undefined variable" error now. */ "undefined variable" error now. */
if (withLevel == -1) if (withLevel == -1)
es.error<UndefinedVarError>( es.errors.make<UndefinedVarError>(
"undefined variable '%1%'", "undefined variable '%1%'",
es.symbols[name] es.symbols[name]
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();

View file

@ -47,7 +47,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & c : context) { for (auto & c : context) {
auto ensureValid = [&](const StorePath & p) { auto ensureValid = [&](const StorePath & p) {
if (!store->isValidPath(p)) if (!store->isValidPath(p))
error<InvalidPathError>(store->printStorePath(p)).debugThrow(); errors.make<InvalidPathError>(store->printStorePath(p)).debugThrow();
}; };
std::visit(overloaded { std::visit(overloaded {
[&](const NixStringContextElem::Built & b) { [&](const NixStringContextElem::Built & b) {
@ -74,7 +74,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
if (drvs.empty()) return {}; if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation) if (!evalSettings.enableImportFromDerivation)
error<EvalError>( errors.make<EvalError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store) drvs.begin()->to_string(*store)
).debugThrow(); ).debugThrow();
@ -285,16 +285,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL); void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
state.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow(); state.errors.make<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
dlerror(); dlerror();
ValueInitializer func = reinterpret_cast<ValueInitializer>(dlsym(handle, sym.c_str())); ValueInitializer func = reinterpret_cast<ValueInitializer>(dlsym(handle, sym.c_str()));
if(!func) { if(!func) {
char *message = dlerror(); char *message = dlerror();
if (message) if (message)
state.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow(); state.errors.make<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow();
else else
state.error<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow(); state.errors.make<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
} }
(func)(state, v); (func)(state, v);
@ -310,7 +310,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto elems = args[0]->listElems(); auto elems = args[0]->listElems();
auto count = args[0]->listSize(); auto count = args[0]->listSize();
if (count == 0) if (count == 0)
state.error<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow(); state.errors.make<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow();
NixStringContext context; NixStringContext context;
auto program = state.coerceToString(pos, *elems[0], context, auto program = state.coerceToString(pos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec", "while evaluating the first element of the argument passed to builtins.exec",
@ -452,7 +452,7 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat) if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer.value < v2->fpoint; return v1->integer.value < v2->fpoint;
if (v1->type() != v2->type()) if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); state.errors.make<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
// Allow selecting a subset of enum values // Allow selecting a subset of enum values
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wswitch-enum"
@ -477,7 +477,7 @@ struct CompareValues
} }
} }
default: default:
state.error<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow(); state.errors.make<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow();
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
} }
} catch (Error & e) { } catch (Error & e) {
@ -503,7 +503,7 @@ static Bindings::iterator getAttr(
{ {
Bindings::iterator value = attrSet->find(attrSym); Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) { if (value == attrSet->end()) {
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow(); state.errors.make<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
} }
return value; return value;
} }
@ -591,7 +591,7 @@ static void prim_abort(EvalState & state, const PosIdx pos, Value * * args, Valu
NixStringContext context; NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.abort").toOwned(); "while evaluating the error message passed to builtins.abort").toOwned();
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow(); state.errors.make<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
} }
static void prim_throw(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_throw(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@ -599,7 +599,7 @@ static void prim_throw(EvalState & state, const PosIdx pos, Value * * args, Valu
NixStringContext context; NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtin.throw").toOwned(); "while evaluating the error message passed to builtin.throw").toOwned();
state.error<ThrownError>(s).debugThrow(); state.errors.make<ThrownError>(s).debugThrow();
} }
static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@ -827,7 +827,7 @@ drvName, Bindings * attrs, Value & v)
experimentalFeatureSettings.require(Xp::DynamicDerivations); experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {}; ingestionMethod = TextIngestionMethod {};
} else } else
state.error<EvalError>( state.errors.make<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s "invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
}; };
@ -836,7 +836,7 @@ drvName, Bindings * attrs, Value & v)
outputs.clear(); outputs.clear();
for (auto & j : ss) { for (auto & j : ss) {
if (outputs.find(j) != outputs.end()) if (outputs.find(j) != outputs.end())
state.error<EvalError>("duplicate derivation output '%1%'", j) state.errors.make<EvalError>("duplicate derivation output '%1%'", j)
.atPos(v) .atPos(v)
.debugThrow(); .debugThrow();
/* !!! Check whether j is a valid attribute /* !!! Check whether j is a valid attribute
@ -845,13 +845,13 @@ drvName, Bindings * attrs, Value & v)
then we'd have an attribute drvPath in then we'd have an attribute drvPath in
the resulting set. */ the resulting set. */
if (j == "drv") if (j == "drv")
state.error<EvalError>("invalid derivation output name 'drv'") state.errors.make<EvalError>("invalid derivation output name 'drv'")
.atPos(v) .atPos(v)
.debugThrow(); .debugThrow();
outputs.insert(j); outputs.insert(j);
} }
if (outputs.empty()) if (outputs.empty())
state.error<EvalError>("derivation cannot have an empty set of outputs") state.errors.make<EvalError>("derivation cannot have an empty set of outputs")
.atPos(v) .atPos(v)
.debugThrow(); .debugThrow();
}; };
@ -989,12 +989,12 @@ drvName, Bindings * attrs, Value & v)
/* Do we have all required attributes? */ /* Do we have all required attributes? */
if (drv.builder == "") if (drv.builder == "")
state.error<EvalError>("required attribute 'builder' missing") state.errors.make<EvalError>("required attribute 'builder' missing")
.atPos(v) .atPos(v)
.debugThrow(); .debugThrow();
if (drv.platform == "") if (drv.platform == "")
state.error<EvalError>("required attribute 'system' missing") state.errors.make<EvalError>("required attribute 'system' missing")
.atPos(v) .atPos(v)
.debugThrow(); .debugThrow();
@ -1004,7 +1004,7 @@ drvName, Bindings * attrs, Value & v)
outputs.size() == 1 && outputs.size() == 1 &&
*(outputs.begin()) == "out")) *(outputs.begin()) == "out"))
{ {
state.error<EvalError>( state.errors.make<EvalError>(
"derivation names are allowed to end in '%s' only if they produce a single derivation file", "derivation names are allowed to end in '%s' only if they produce a single derivation file",
drvExtension drvExtension
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
@ -1016,7 +1016,7 @@ drvName, Bindings * attrs, Value & v)
Ignore `__contentAddressed` because fixed output derivations are Ignore `__contentAddressed` because fixed output derivations are
already content addressed. */ already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out") if (outputs.size() != 1 || *(outputs.begin()) != "out")
state.error<EvalError>( state.errors.make<EvalError>(
"multiple outputs are not supported in fixed-output derivations" "multiple outputs are not supported in fixed-output derivations"
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
@ -1037,7 +1037,7 @@ drvName, Bindings * attrs, Value & v)
else if (contentAddressed || isImpure) { else if (contentAddressed || isImpure) {
if (contentAddressed && isImpure) if (contentAddressed && isImpure)
state.error<EvalError>("derivation cannot be both content-addressed and impure") state.errors.make<EvalError>("derivation cannot be both content-addressed and impure")
.atPos(v).debugThrow(); .atPos(v).debugThrow();
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(HashType::SHA256); auto ht = parseHashTypeOpt(outputHashAlgo).value_or(HashType::SHA256);
@ -1079,7 +1079,7 @@ drvName, Bindings * attrs, Value & v)
for (auto & i : outputs) { for (auto & i : outputs) {
auto h = get(hashModulo.hashes, i); auto h = get(hashModulo.hashes, i);
if (!h) if (!h)
state.error<AssertionError>( state.errors.make<AssertionError>(
"derivation produced no hash for output '%s'", "derivation produced no hash for output '%s'",
i i
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
@ -1167,7 +1167,7 @@ static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Val
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
if (evalSettings.pureEval) if (evalSettings.pureEval)
state.error<EvalError>( state.errors.make<EvalError>(
"'%s' is not allowed in pure evaluation mode", "'%s' is not allowed in pure evaluation mode",
"builtins.storePath" "builtins.storePath"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -1180,7 +1180,7 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
if (!state.store->isStorePath(path.abs())) if (!state.store->isStorePath(path.abs()))
path = CanonPath(canonPath(path.abs(), true)); path = CanonPath(canonPath(path.abs(), true));
if (!state.store->isInStore(path.abs())) if (!state.store->isInStore(path.abs()))
state.error<EvalError>("path '%1%' is not in the Nix store", path) state.errors.make<EvalError>("path '%1%' is not in the Nix store", path)
.atPos(pos).debugThrow(); .atPos(pos).debugThrow();
auto path2 = state.store->toStorePath(path.abs()).first; auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
@ -1257,7 +1257,7 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
auto s = path.readFile(); auto s = path.readFile();
if (s.find((char) 0) != std::string::npos) if (s.find((char) 0) != std::string::npos)
state.error<EvalError>( state.errors.make<EvalError>(
"the contents of the file '%1%' cannot be represented as a Nix string", "the contents of the file '%1%' cannot be represented as a Nix string",
path path
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -1308,7 +1308,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
auto rewrites = state.realiseContext(context); auto rewrites = state.realiseContext(context);
path = rewriteStrings(path, rewrites); path = rewriteStrings(path, rewrites);
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
state.error<EvalError>( state.errors.make<EvalError>(
"cannot find '%1%', since path '%2%' is not valid", "cannot find '%1%', since path '%2%' is not valid",
path, path,
e.path e.path
@ -1332,7 +1332,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile"); auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
state.error<EvalError>("unknown hash type '%1%'", type).atPos(pos).debugThrow(); state.errors.make<EvalError>("unknown hash type '%1%'", type).atPos(pos).debugThrow();
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
@ -1461,7 +1461,7 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw)) if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
refs.insert(p->path); refs.insert(p->path);
else else
state.error<EvalError>( state.errors.make<EvalError>(
"files created by %1% may not reference derivations, but %2% references %3%", "files created by %1% may not reference derivations, but %2% references %3%",
"builtins.toFile", "builtins.toFile",
name, name,
@ -1548,7 +1548,7 @@ static void addPath(
auto dstPath = fetchToStore( auto dstPath = fetchToStore(
*state.store, CanonPath(path), name, method, &filter, state.repair); *state.store, CanonPath(path), name, method, &filter, state.repair);
if (expectedHash && expectedStorePath != dstPath) if (expectedHash && expectedStorePath != dstPath)
state.error<EvalError>( state.errors.make<EvalError>(
"store path mismatch in (possibly filtered) path added from '%s'", "store path mismatch in (possibly filtered) path added from '%s'",
path path
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -1595,13 +1595,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashType::SHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashType::SHA256);
else else
state.error<EvalError>( state.errors.make<EvalError>(
"unsupported argument '%1%' to 'addPath'", "unsupported argument '%1%' to 'addPath'",
state.symbols[attr.name] state.symbols[attr.name]
).atPos(attr.pos).debugThrow(); ).atPos(attr.pos).debugThrow();
} }
if (!path) if (!path)
state.error<EvalError>( state.errors.make<EvalError>(
"missing required 'path' attribute in the first argument to builtins.path" "missing required 'path' attribute in the first argument to builtins.path"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
if (name.empty()) if (name.empty())
@ -1908,7 +1908,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
return; return;
} }
if (!args[0]->isLambda()) if (!args[0]->isLambda())
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow(); state.errors.make<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
if (!args[0]->lambda.fun->hasFormals()) { if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&Bindings::EMPTY); v.mkAttrs(&Bindings::EMPTY);
@ -2005,7 +2005,7 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
{ {
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt"); state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
if (n < 0 || (unsigned int) n >= list.listSize()) if (n < 0 || (unsigned int) n >= list.listSize())
state.error<EvalError>( state.errors.make<EvalError>(
"list index %1% is out of bounds", "list index %1% is out of bounds",
n n
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -2033,7 +2033,7 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
{ {
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail"); state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
if (args[0]->listSize() == 0) if (args[0]->listSize() == 0)
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow(); state.errors.make<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
v = state.mem.newList(args[0]->listSize() - 1); v = state.mem.newList(args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n) for (unsigned int n = 0; n < v.listSize(); ++n)
@ -2180,7 +2180,7 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
auto len_ = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList").value; auto len_ = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList").value;
if (len_ < 0) if (len_ < 0)
state.error<EvalError>("cannot create list of size %1%", len_).atPos(pos).debugThrow(); state.errors.make<EvalError>("cannot create list of size %1%", len_).atPos(pos).debugThrow();
size_t len = len_; size_t len = len_;
@ -2353,7 +2353,7 @@ static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value
if (auto result = result_.valueChecked(); result.has_value()) { if (auto result = result_.valueChecked(); result.has_value()) {
v.mkInt(*result); v.mkInt(*result);
} else { } else {
state.error<EvalError>("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow(); state.errors.make<EvalError>("integer overflow in adding %1% + %2%", i1, i2).atPos(pos).debugThrow();
} }
} }
} }
@ -2374,7 +2374,7 @@ static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value
if (auto result = result_.valueChecked(); result.has_value()) { if (auto result = result_.valueChecked(); result.has_value()) {
v.mkInt(*result); v.mkInt(*result);
} else { } else {
state.error<EvalError>("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow(); state.errors.make<EvalError>("integer overflow in subtracting %1% - %2%", i1, i2).atPos(pos).debugThrow();
} }
} }
} }
@ -2395,7 +2395,7 @@ static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value
if (auto result = result_.valueChecked(); result.has_value()) { if (auto result = result_.valueChecked(); result.has_value()) {
v.mkInt(*result); v.mkInt(*result);
} else { } else {
state.error<EvalError>("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow(); state.errors.make<EvalError>("integer overflow in multiplying %1% * %2%", i1, i2).atPos(pos).debugThrow();
} }
} }
} }
@ -2407,7 +2407,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division"); NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
if (f2 == 0) if (f2 == 0)
state.error<EvalError>("division by zero").atPos(pos).debugThrow(); state.errors.make<EvalError>("division by zero").atPos(pos).debugThrow();
if (args[0]->type() == nFloat || args[1]->type() == nFloat) { if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2); v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
@ -2419,7 +2419,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
if (auto result = result_.valueChecked(); result.has_value()) { if (auto result = result_.valueChecked(); result.has_value()) {
v.mkInt(*result); v.mkInt(*result);
} else { } else {
state.error<EvalError>("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow(); state.errors.make<EvalError>("integer overflow in dividing %1% / %2%", i1, i2).atPos(pos).debugThrow();
} }
} }
} }
@ -2483,7 +2483,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
NixInt::Inner start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring").value; NixInt::Inner start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring").value;
if (start < 0) if (start < 0)
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow(); state.errors.make<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
NixInt::Inner len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring").value; NixInt::Inner len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring").value;
@ -2523,7 +2523,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString"); auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
state.error<EvalError>("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow(); state.errors.make<EvalError>("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow();
NixStringContext context; // discarded NixStringContext context; // discarded
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString"); auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
@ -2585,11 +2585,11 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re) state.errors.make<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos) .atPos(pos)
.debugThrow(); .debugThrow();
} else } else
state.error<EvalError>("invalid regular expression '%s'", re) state.errors.make<EvalError>("invalid regular expression '%s'", re)
.atPos(pos) .atPos(pos)
.debugThrow(); .debugThrow();
} }
@ -2651,11 +2651,11 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re) state.errors.make<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos) .atPos(pos)
.debugThrow(); .debugThrow();
} else } else
state.error<EvalError>("invalid regular expression '%s'", re) state.errors.make<EvalError>("invalid regular expression '%s'", re)
.atPos(pos) .atPos(pos)
.debugThrow(); .debugThrow();
} }
@ -2685,7 +2685,7 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings"); state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
if (args[0]->listSize() != args[1]->listSize()) if (args[0]->listSize() != args[1]->listSize())
state.error<EvalError>( state.errors.make<EvalError>(
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths" "'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();

View file

@ -56,7 +56,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
auto contextSize = context.size(); auto contextSize = context.size();
if (contextSize != 1) { if (contextSize != 1) {
state.error<EvalError>( state.errors.make<EvalError>(
"context of string '%s' must have exactly one element, but has %d", "context of string '%s' must have exactly one element, but has %d",
*s, *s,
contextSize contextSize
@ -66,7 +66,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
(NixStringContextElem { std::visit(overloaded { (NixStringContextElem { std::visit(overloaded {
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
if (!c.path.isDerivation()) { if (!c.path.isDerivation()) {
state.error<EvalError>( state.errors.make<EvalError>(
"path '%s' is not a derivation", "path '%s' is not a derivation",
state.store->printStorePath(c.path) state.store->printStorePath(c.path)
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -76,7 +76,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
}; };
}, },
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
state.error<EvalError>( state.errors.make<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output c.output
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -176,7 +176,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
for (auto & i : *args[1]->attrs) { for (auto & i : *args[1]->attrs) {
const auto & name = state.symbols[i.name]; const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name)) if (!state.store->isStorePath(name))
state.error<EvalError>( state.errors.make<EvalError>(
"context key '%s' is not a store path", "context key '%s' is not a store path",
name name
).atPos(i.pos).debugThrow(); ).atPos(i.pos).debugThrow();
@ -196,7 +196,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) { if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) { if (!isDerivation(name)) {
state.error<EvalError>( state.errors.make<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string", "tried to add all-outputs context of %s, which is not a derivation, to a string",
name name
).atPos(i.pos).debugThrow(); ).atPos(i.pos).debugThrow();
@ -211,7 +211,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context"); state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) { if (iter->value->listSize() && !isDerivation(name)) {
state.error<EvalError>( state.errors.make<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string", "tried to add derivation output context of %s, which is not a derivation, to a string",
name name
).atPos(i.pos).debugThrow(); ).atPos(i.pos).debugThrow();

View file

@ -38,11 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial"); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else else
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow(); state.errors.make<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
} }
if (url.empty()) if (url.empty())
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow(); state.errors.make<EvalError>("'url' argument required").atPos(pos).debugThrow();
} else } else
url = state.coerceToString(pos, *args[0], context, url = state.coerceToString(pos, *args[0], context,

View file

@ -124,12 +124,12 @@ static void fetchTree(
if (auto aType = args[0]->attrs->get(state.s.type)) { if (auto aType = args[0]->attrs->get(state.s.type)) {
if (type) if (type)
state.error<EvalError>( state.errors.make<EvalError>(
"unexpected attribute 'type'" "unexpected attribute 'type'"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree"); type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type) } else if (!type)
state.error<EvalError>( state.errors.make<EvalError>(
"attribute 'type' is missing in call to 'fetchTree'" "attribute 'type' is missing in call to 'fetchTree'"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -153,19 +153,19 @@ static void fetchTree(
auto intValue = attr.value->integer.value; auto intValue = attr.value->integer.value;
if (intValue < 0) { if (intValue < 0) {
state.error<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow(); state.errors.make<EvalError>("negative value given for fetchTree attr %1%: %2%", state.symbols[attr.name], intValue).atPos(pos).debugThrow();
} }
unsigned long asUnsigned = intValue; unsigned long asUnsigned = intValue;
attrs.emplace(state.symbols[attr.name], asUnsigned); attrs.emplace(state.symbols[attr.name], asUnsigned);
} else } else
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.errors.make<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow(); state.symbols[attr.name], showType(*attr.value)).debugThrow();
} }
if (!params.allowNameArgument) if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.error<EvalError>( state.errors.make<EvalError>(
"attribute 'name' isnt supported in call to 'fetchTree'" "attribute 'name' isnt supported in call to 'fetchTree'"
).atPos(pos).debugThrow(); ).atPos(pos).debugThrow();
@ -189,7 +189,7 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first; input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) { if (evalSettings.pureEval && !input.isLocked()) {
state.error<EvalError>("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow(); state.errors.make<EvalError>("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow();
} }
auto [tree, input2] = input.fetch(state.store); auto [tree, input2] = input.fetch(state.store);
@ -231,12 +231,12 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch"); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else else
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who) state.errors.make<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos).debugThrow(); .atPos(pos).debugThrow();
} }
if (!url) if (!url)
state.error<EvalError>( state.errors.make<EvalError>(
"'url' argument required").atPos(pos).debugThrow(); "'url' argument required").atPos(pos).debugThrow();
} else } else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
@ -250,7 +250,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
name = baseNameOf(*url); name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash) if (evalSettings.pureEval && !expectedHash)
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow(); state.errors.make<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
// early exit if pinned and already in the store // early exit if pinned and already in the store
if (expectedHash && expectedHash->type == HashType::SHA256) { if (expectedHash && expectedHash->type == HashType::SHA256) {
@ -280,7 +280,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
? state.store->queryPathInfo(storePath)->narHash ? state.store->queryPathInfo(storePath)->narHash
: hashFile(HashType::SHA256, state.store->toRealPath(storePath)); : hashFile(HashType::SHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash) { if (hash != *expectedHash) {
state.error<EvalError>( state.errors.make<EvalError>(
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, *url,
expectedHash->to_string(Base::Base32, true), expectedHash->to_string(Base::Base32, true),

View file

@ -83,7 +83,7 @@ void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value &
try { try {
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */)); visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
} catch (std::exception & e) { // TODO: toml::syntax_error } catch (std::exception & e) { // TODO: toml::syntax_error
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow(); state.errors.make<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
} }
} }

View file

@ -99,7 +99,7 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk: case nThunk:
case nFunction: case nFunction:
state.error<TypeError>( state.errors.make<TypeError>(
"cannot convert %1% to JSON", "cannot convert %1% to JSON",
showType(v) showType(v)
) )
@ -118,7 +118,7 @@ void printValueAsJSON(EvalState & state, bool strict,
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
NixStringContext & context, bool copyToStore) const NixStringContext & context, bool copyToStore) const
{ {
state.error<TypeError>("cannot convert %1% to JSON", showType()) state.errors.make<TypeError>("cannot convert %1% to JSON", showType())
.debugThrow(); .debugThrow();
} }

View file

@ -330,7 +330,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* try { * try {
* e->eval(*this, env, v); * e->eval(*this, env, v);
* if (v.type() != nAttrs) * if (v.type() != nAttrs)
* error<TypeError>("expected a set but found %1%", v); * errors.make<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) { * } catch (Error & e) {
* e.addTrace(pos, errorCtx); * e.addTrace(pos, errorCtx);
* throw; * throw;
@ -344,7 +344,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
* e->eval(*this, env, v); * e->eval(*this, env, v);
* try { * try {
* if (v.type() != nAttrs) * if (v.type() != nAttrs)
* error<TypeError>("expected a set but found %1%", v); * errors.make<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) { * } catch (Error & e) {
* e.addTrace(pos, errorCtx); * e.addTrace(pos, errorCtx);
* throw; * throw;

View file

@ -106,7 +106,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
} }
} }
else else
state->error<TypeError>("value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow(); state->errors.make<TypeError>("value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow();
}; };
recurse(*v, pos, *writeTo); recurse(*v, pos, *writeTo);

View file

@ -868,7 +868,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto templateDir = templateDirAttr->getString(); auto templateDir = templateDirAttr->getString();
if (!store->isInStore(templateDir)) if (!store->isInStore(templateDir))
evalState->error<TypeError>( evalState->errors.make<TypeError>(
"'%s' was not found in the Nix store\n" "'%s' was not found in the Nix store\n"
"If you've set '%s' to a string, try using a path instead.", "If you've set '%s' to a string, try using a path instead.",
templateDir, templateDirAttr->getAttrPathStr()).debugThrow(); templateDir, templateDirAttr->getAttrPathStr()).debugThrow();
@ -1374,7 +1374,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
{ {
auto aType = visitor.maybeGetAttr("type"); auto aType = visitor.maybeGetAttr("type");
if (!aType || aType->getString() != "app") if (!aType || aType->getString() != "app")
state->error<EvalError>("not an app definition").debugThrow(); state->errors.make<EvalError>("not an app definition").debugThrow();
if (json) { if (json) {
j.emplace("type", "app"); j.emplace("type", "app");
} else { } else {

View file

@ -12,19 +12,19 @@ namespace nix {
TEST_F(ErrorTraceTest, TraceBuilder) { TEST_F(ErrorTraceTest, TraceBuilder) {
ASSERT_THROW( ASSERT_THROW(
state.error<EvalError>("puppy").debugThrow(), state.errors.make<EvalError>("puppy").debugThrow(),
EvalError EvalError
); );
ASSERT_THROW( ASSERT_THROW(
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(), state.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(),
EvalError EvalError
); );
ASSERT_THROW( ASSERT_THROW(
try { try {
try { try {
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(); state.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[noPos], "beans"); e.addTrace(state.positions[noPos], "beans");
throw; throw;
@ -47,10 +47,10 @@ namespace nix {
TEST_F(ErrorTraceTest, NestedThrows) { TEST_F(ErrorTraceTest, NestedThrows) {
try { try {
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(); state.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
} catch (BaseError & e) { } catch (BaseError & e) {
try { try {
state.error<EvalError>("beans").debugThrow(); state.errors.make<EvalError>("beans").debugThrow();
} catch (Error & e2) { } catch (Error & e2) {
e.addTrace(state.positions[noPos], "beans2"); e.addTrace(state.positions[noPos], "beans2");
//e2.addTrace(state.positions[noPos], "Something", ""); //e2.addTrace(state.positions[noPos], "Something", "");