libexpr: Support structured error classes

While preparing PRs like #9753, I've had to change error messages in
dozens of code paths. It would be nice if instead of

    EvalError("expected 'boolean' but found '%1%'", showType(v))

we could write

    TypeError(v, "boolean")

or similar. Then, changing the error message could be a mechanical
refactor with the compiler pointing out places the constructor needs to
be changed, rather than the error-prone process of grepping through the
codebase. Structured errors would also help prevent the "same" error
from having multiple slightly different messages, and could be a first
step towards error codes / an error index.

This PR reworks the exception infrastructure in `libexpr` to
support exception types with different constructor signatures than
`BaseError`. Actually refactoring the exceptions to use structured data
will come in a future PR (this one is big enough already, as it has to
touch every exception in `libexpr`).

The core design is in `eval-error.hh`. Generally, errors like this:

    state.error("'%s' is not a string", getAttrPathStr())
      .debugThrow<TypeError>()

are transformed like this:

    state.error<TypeError>("'%s' is not a string", getAttrPathStr())
      .debugThrow()

The type annotation has moved from `ErrorBuilder::debugThrow` to
`EvalState::error`.
This commit is contained in:
Rebecca Turner 2024-01-22 17:08:29 -08:00
parent c62c21e29a
commit c6a89c1a16
No known key found for this signature in database
40 changed files with 653 additions and 545 deletions

View File

@ -422,8 +422,6 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
// Quietly ignore parse errors. // Quietly ignore parse errors.
} catch (EvalError & e) { } catch (EvalError & e) {
// Quietly ignore evaluation errors. // Quietly ignore evaluation errors.
} catch (UndefinedVarError & e) {
// Quietly ignore undefined variable errors.
} catch (BadURL & e) { } catch (BadURL & e) {
// Quietly ignore BadURL flake-related errors. // Quietly ignore BadURL flake-related errors.
} }

View File

@ -65,10 +65,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) { if (!attrIndex) {
if (v->type() != nAttrs) if (v->type() != nAttrs)
throw TypeError( state.error<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)); showType(*v)).debugThrow();
if (attr.empty()) if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath); throw Error("empty attribute name in selection path '%1%'", attrPath);
@ -88,10 +88,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
else { else {
if (!v->isList()) if (!v->isList())
throw TypeError( state.error<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)); showType(*v)).debugThrow();
if (*attrIndex >= v->listSize()) if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath); throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);

View File

@ -491,7 +491,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
if (forceErrors) if (forceErrors)
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name)); debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
else else
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name)); throw CachedEvalError(root->state, "cached failure of attribute '%s'", getAttrPathStr(name));
} else } else
return std::make_shared<AttrCursor>(root, return std::make_shared<AttrCursor>(root,
std::make_pair(shared_from_this(), name), nullptr, std::move(attr)); std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
@ -500,7 +500,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
// evaluate to see whether 'name' exists // evaluate to see whether 'name' exists
} else } else
return nullptr; return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr()); //error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
} }
} }
@ -508,7 +508,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
if (v.type() != nAttrs) if (v.type() != nAttrs)
return nullptr; return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr()); //error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(name); auto attr = v.attrs->get(name);
@ -574,14 +574,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("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<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("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
return v.type() == nString ? v.c_str() : v.path().to_string(); return v.type() == nString ? v.c_str() : v.path().to_string();
} }
@ -616,7 +616,7 @@ string_t AttrCursor::getStringWithContext()
return *s; return *s;
} }
} else } else
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
} }
} }
@ -630,7 +630,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("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
} }
bool AttrCursor::getBool() bool AttrCursor::getBool()
@ -643,14 +643,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("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<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("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
return v.boolean; return v.boolean;
} }
@ -665,14 +665,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
throw TypeError("'%s' is not an integer", getAttrPathStr()); root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nInt) if (v.type() != nInt)
throw TypeError("'%s' is not an integer", getAttrPathStr()); root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
return v.integer; return v.integer;
} }
@ -687,7 +687,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
throw TypeError("'%s' is not a list of strings", getAttrPathStr()); root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
} }
} }
@ -697,7 +697,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)
throw TypeError("'%s' is not a list", getAttrPathStr()); root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
std::vector<std::string> res; std::vector<std::string> res;
@ -720,14 +720,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("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<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("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>(); root->state.error<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)

113
src/libexpr/eval-error.cc Normal file
View File

@ -0,0 +1,113 @@
#include "eval-error.hh"
#include "eval.hh"
#include "value.hh"
namespace nix {
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
{
error.withExitStatus(exitStatus);
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(PosIdx pos)
{
error.err.pos = error.state.positions[pos];
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(Value & value, PosIdx fallback)
{
return atPos(value.determinePos(fallback));
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text)
{
error.err.traces.push_front(
Trace{.pos = error.state.positions[pos], .hint = hintfmt(std::string(text)), .frame = false});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrameTrace(PosIdx pos, const std::string_view text)
{
error.err.traces.push_front(
Trace{.pos = error.state.positions[pos], .hint = hintformat(std::string(text)), .frame = true});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withSuggestions(Suggestions & s)
{
error.err.suggestions = s;
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr)
{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
// TODO: What side-effects??
error.state.debugTraces.push_front(DebugTrace{
.pos = error.state.positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = hintformat("Fake frame for debugging purposes"),
.isError = true});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::addTrace(PosIdx pos, hintformat hint, bool frame)
{
error.addTrace(error.state.positions[pos], hint, frame);
return *this;
}
template<class T>
template<typename... Args>
EvalErrorBuilder<T> &
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs)
{
addTrace(error.state.positions[pos], hintfmt(std::string(formatString), formatArgs...));
return *this;
}
template<class T>
void EvalErrorBuilder<T>::debugThrow()
{
if (error.state.debugRepl && !error.state.debugTraces.empty()) {
const DebugTrace & last = error.state.debugTraces.front();
const Env * env = &last.env;
const Expr * expr = &last.expr;
error.state.runDebugRepl(&error, *env, *expr);
}
// `EvalState` is the only class that can construct an `EvalErrorBuilder`,
// and it does so in dynamic storage. This is the final method called on
// any such instancve and must delete itself before throwing the underlying
// error.
auto error = std::move(this->error);
delete this;
throw error;
}
template class EvalErrorBuilder<EvalError>;
template class EvalErrorBuilder<AssertionError>;
template class EvalErrorBuilder<ThrownError>;
template class EvalErrorBuilder<Abort>;
template class EvalErrorBuilder<TypeError>;
template class EvalErrorBuilder<UndefinedVarError>;
template class EvalErrorBuilder<MissingArgumentError>;
template class EvalErrorBuilder<InfiniteRecursionError>;
template class EvalErrorBuilder<CachedEvalError>;
template class EvalErrorBuilder<InvalidPathError>;
}

118
src/libexpr/eval-error.hh Normal file
View File

@ -0,0 +1,118 @@
#pragma once
#include <algorithm>
#include "error.hh"
#include "pos-idx.hh"
namespace nix {
struct Env;
struct Expr;
struct Value;
class EvalState;
template<class T>
class EvalErrorBuilder;
class EvalError : public Error
{
template<class T>
friend class EvalErrorBuilder;
public:
EvalState & state;
EvalError(EvalState & state, ErrorInfo && errorInfo)
: Error(errorInfo)
, state(state)
{
}
template<typename... Args>
explicit EvalError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
: Error(formatString, formatArgs...)
, state(state)
{
}
};
MakeError(ParseError, Error);
MakeError(AssertionError, EvalError);
MakeError(ThrownError, AssertionError);
MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, EvalError);
MakeError(MissingArgumentError, EvalError);
MakeError(CachedEvalError, EvalError);
MakeError(InfiniteRecursionError, EvalError);
struct InvalidPathError : public EvalError
{
public:
Path path;
InvalidPathError(EvalState & state, const Path & path)
: EvalError(state, "path '%s' is not valid", path)
{
}
};
template<class T>
class EvalErrorBuilder final
{
friend class EvalState;
template<typename... Args>
explicit EvalErrorBuilder(EvalState & state, const Args &... args)
: error(T(state, args...))
{
}
public:
T error;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withExitStatus(unsigned int exitStatus);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(Value & value, PosIdx fallback = noPos);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrame(const Env & e, const Expr & ex);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & addTrace(PosIdx pos, hintformat hint, bool frame = false);
template<typename... Args>
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> &
addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs);
[[gnu::noinline, gnu::noreturn]] void debugThrow();
};
/**
* The size needed to allocate any `EvalErrorBuilder<T>`.
*
* The list of classes here needs to be kept in sync with the list of `template
* class` declarations in `eval-error.cc`.
*
* This is used by `EvalState` to preallocate a buffer of sufficient size for
* any `EvalErrorBuilder<T>` to avoid allocating while evaluating Nix code.
*/
constexpr size_t EVAL_ERROR_BUILDER_SIZE = std::max({
sizeof(EvalErrorBuilder<EvalError>),
sizeof(EvalErrorBuilder<AssertionError>),
sizeof(EvalErrorBuilder<ThrownError>),
sizeof(EvalErrorBuilder<Abort>),
sizeof(EvalErrorBuilder<TypeError>),
sizeof(EvalErrorBuilder<UndefinedVarError>),
sizeof(EvalErrorBuilder<MissingArgumentError>),
sizeof(EvalErrorBuilder<InfiniteRecursionError>),
sizeof(EvalErrorBuilder<CachedEvalError>),
sizeof(EvalErrorBuilder<InvalidPathError>),
});
}

View File

@ -3,6 +3,7 @@
#include "print.hh" #include "print.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-error.hh"
namespace nix { namespace nix {
@ -115,10 +116,11 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
PosIdx pos = getPos(); PosIdx pos = getPos();
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nAttrs) { if (v.type() != nAttrs) {
error("expected a set but found %1%: %2%", error<TypeError>(
showType(v), "expected a set but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.withTrace(pos, errorCtx).debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
} }
} }
@ -128,10 +130,11 @@ 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("expected a list but found %1%: %2%", error<TypeError>(
showType(v), "expected a list but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.withTrace(pos, errorCtx).debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
} }
} }

View File

@ -339,46 +339,6 @@ void initGC()
gcInitialised = true; gcInitialised = true;
} }
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
{
info.errPos = state.positions[pos];
return *this;
}
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
return *this;
}
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
return *this;
}
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
{
info.suggestions = s;
return *this;
}
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 = nullptr,
.expr = expr,
.env = env,
.hint = hintformat("Fake frame for debugging purposes"),
.isError = true
});
return *this;
}
EvalState::EvalState( EvalState::EvalState(
const SearchPath & _searchPath, const SearchPath & _searchPath,
ref<Store> store, ref<Store> store,
@ -811,7 +771,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>( ? std::make_unique<DebugTraceStacker>(
*this, *this,
DebugTrace { DebugTrace {
.pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()], .pos = error->info().pos ? error->info().pos : positions[expr.getPos()],
.expr = expr, .expr = expr,
.env = env, .env = env,
.hint = error->info().msg, .hint = error->info().msg,
@ -930,7 +890,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("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>(); error<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;
} }
@ -1136,7 +1096,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
// computation. // computation.
if (mustBeTrivial && if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e))) !(dynamic_cast<ExprAttrs *>(e)))
error("file '%s' must be an attribute set", path).debugThrow<EvalError>(); error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
@ -1167,10 +1127,11 @@ 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("expected a Boolean but found %1%: %2%", error<TypeError>(
showType(v), "expected a Boolean but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.withFrame(env, *e).debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).withFrame(env, *e).debugThrow();
return v.boolean; return v.boolean;
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
@ -1184,10 +1145,11 @@ 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("expected a set but found %1%: %2%", error<TypeError>(
showType(v), "expected a set but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.withFrame(env, *e).debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).withFrame(env, *e).debugThrow();
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
throw; throw;
@ -1296,7 +1258,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
auto nameSym = state.symbols.create(nameVal.string_view()); auto nameSym = state.symbols.create(nameVal.string_view());
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("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>(); state.error<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 */
@ -1408,8 +1370,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
for (auto & attr : *vAttrs->attrs) for (auto & attr : *vAttrs->attrs)
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("attribute '%1%' missing", state.symbols[name]) state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>(); .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
} }
} }
vAttrs = j->value; vAttrs = j->value;
@ -1482,7 +1444,7 @@ public:
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("stack overflow; max-call-depth exceeded").atPos(pos).template debugThrow<EvalError>(); error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
CallDepth _level(callDepth); CallDepth _level(callDepth);
auto trace = evalSettings.traceFunctionCalls auto trace = evalSettings.traceFunctionCalls
@ -1540,13 +1502,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto j = args[0]->attrs->get(i.name); auto j = args[0]->attrs->get(i.name);
if (!j) { if (!j) {
if (!i.def) { if (!i.def) {
error("function '%1%' called without required argument '%2%'", error<TypeError>("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name]) symbols[i.name])
.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)
.debugThrow<TypeError>(); .debugThrow();
} }
env2.values[displ++] = i.def->maybeThunk(*this, env2); env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else { } else {
@ -1566,14 +1528,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & formal : lambda.formals->formals) for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]); formalNames.insert(symbols[formal.name]);
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]); auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
error("function '%1%' called with unexpected argument '%2%'", error<TypeError>("function '%1%' called with unexpected argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"), (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name]) symbols[i.name])
.atPos(lambda.pos) .atPos(lambda.pos)
.withTrace(pos, "from call site") .withTrace(pos, "from call site")
.withSuggestions(suggestions) .withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda) .withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>(); .debugThrow();
} }
abort(); // can't happen abort(); // can't happen
} }
@ -1705,11 +1667,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
} }
else else
error("attempt to call something which is not a function but %1%: %2%", error<TypeError>(
"attempt to call something which is not a function but %1%: %2%",
showType(vCur), showType(vCur),
ValuePrinter(*this, vCur, errorPrintOptions)) ValuePrinter(*this, vCur, errorPrintOptions))
.atPos(pos) .atPos(pos)
.debugThrow<TypeError>(); .debugThrow();
} }
vRes = vCur; vRes = vCur;
@ -1779,12 +1742,12 @@ 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(R"(cannot evaluate a function that has an argument without a value ('%1%') error<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
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name]) https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>(); .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow();
} }
} }
} }
@ -1815,7 +1778,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("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>(); state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1993,14 +1956,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n; nf = n;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>(); state.error<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; nf += vTmp.integer;
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>(); state.error<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
@ -2022,7 +1985,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("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>(); state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
v.mkPath(state.rootPath(CanonPath(canonPath(str())))); v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -2037,8 +2000,9 @@ 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("infinite recursion encountered") state.error<InfiniteRecursionError>("infinite recursion encountered")
.debugThrow<InfiniteRecursionError>(); .atPos(v.determinePos(noPos))
.debugThrow();
} }
// always force this to be separate, otherwise forceValue may inline it and take // always force this to be separate, otherwise forceValue may inline it and take
@ -2052,7 +2016,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
try { try {
std::rethrow_exception(e); std::rethrow_exception(e);
} catch (InfiniteRecursionError & e) { } catch (InfiniteRecursionError & e) {
e.err.errPos = positions[pos]; e.atPos(positions[pos]);
} catch (...) { } catch (...) {
} }
} }
@ -2100,15 +2064,18 @@ 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("expected an integer but found %1%: %2%", error<TypeError>(
showType(v), "expected an integer but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.integer; return v.integer;
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
throw; throw;
} }
return v.integer;
} }
@ -2119,10 +2086,11 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
if (v.type() == nInt) if (v.type() == nInt)
return v.integer; return v.integer;
else if (v.type() != nFloat) else if (v.type() != nFloat)
error("expected a float but found %1%: %2%", error<TypeError>(
showType(v), "expected a float but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.fpoint; return v.fpoint;
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
@ -2136,15 +2104,18 @@ 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("expected a Boolean but found %1%: %2%", error<TypeError>(
showType(v), "expected a Boolean but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.boolean; return v.boolean;
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
throw; throw;
} }
return v.boolean;
} }
@ -2159,10 +2130,11 @@ 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("expected a function but found %1%: %2%", error<TypeError>(
showType(v), "expected a function but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
throw; throw;
@ -2175,10 +2147,11 @@ 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("expected a string but found %1%: %2%", error<TypeError>(
showType(v), "expected a string but found %1%: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
.debugThrow<TypeError>(); ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.string_view(); return v.string_view();
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
@ -2207,7 +2180,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.context()) { if (v.context()) {
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow<EvalError>(); error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
} }
return s; return s;
} }
@ -2272,11 +2245,13 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString); return std::move(*maybeString);
auto i = v.attrs->find(sOutPath); auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) { if (i == v.attrs->end()) {
error("cannot coerce %1% to a string: %2%", error<TypeError>(
showType(v), "cannot coerce %1% to a string: %2%",
ValuePrinter(*this, v, errorPrintOptions)) showType(v),
ValuePrinter(*this, v, errorPrintOptions)
)
.withTrace(pos, errorCtx) .withTrace(pos, errorCtx)
.debugThrow<TypeError>(); .debugThrow();
} }
return coerceToString(pos, *i->value, context, errorCtx, return coerceToString(pos, *i->value, context, errorCtx,
coerceMore, copyToStore, canonicalizePath); coerceMore, copyToStore, canonicalizePath);
@ -2284,7 +2259,7 @@ BackedStringView EvalState::coerceToString(
if (v.type() == nExternal) { if (v.type() == nExternal) {
try { try {
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
} catch (Error & e) { } catch (Error & e) {
e.addTrace(nullptr, errorCtx); e.addTrace(nullptr, errorCtx);
throw; throw;
@ -2320,18 +2295,19 @@ BackedStringView EvalState::coerceToString(
} }
} }
error("cannot coerce %1% to a string: %2%", error<TypeError>("cannot coerce %1% to a string: %2%",
showType(v), showType(v),
ValuePrinter(*this, v, errorPrintOptions)) ValuePrinter(*this, v, errorPrintOptions)
)
.withTrace(pos, errorCtx) .withTrace(pos, errorCtx)
.debugThrow<TypeError>(); .debugThrow();
} }
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("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>(); error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
@ -2380,7 +2356,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
relative to the root filesystem. */ relative to the root filesystem. */
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("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>(); error<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
return rootPath(CanonPath(path)); return rootPath(CanonPath(path));
} }
@ -2390,7 +2366,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("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>(); error<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
} }
@ -2400,18 +2376,18 @@ 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( error<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<EvalError>(); .withTrace(pos, errorCtx).debugThrow();
auto derivedPath = std::visit(overloaded { auto derivedPath = std::visit(overloaded {
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath { [&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
return std::move(o); return std::move(o);
}, },
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath { [&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
error( error<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<EvalError>(); s).withTrace(pos, errorCtx).debugThrow();
}, },
[&](NixStringContextElem::Built && b) -> SingleDerivedPath { [&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b); return std::move(b);
@ -2434,16 +2410,16 @@ 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( error<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<EvalError>(); .withTrace(pos, errorCtx).debugThrow();
}, },
[&](const SingleDerivedPath::Built & b) { [&](const SingleDerivedPath::Built & b) {
error( error<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<EvalError>(); .withTrace(pos, errorCtx).debugThrow();
} }
}, derivedPath.raw()); }, derivedPath.raw());
} }
@ -2528,7 +2504,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("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>(); error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
} }
} }
@ -2767,13 +2743,12 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return {corepkgsFS, CanonPath(path.substr(3))}; return {corepkgsFS, CanonPath(path.substr(3))};
debugThrow(ThrownError({ error<ThrownError>(
.msg = hintfmt(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)",
path), path
.errPos = positions[pos] ).atPos(pos).debugThrow();
}), 0, 0);
} }
@ -2856,11 +2831,11 @@ Expr * EvalState::parse(
} }
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{ {
throw TypeError({ state.error<TypeError>(
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this) "cannot coerce %1% to a string: %2%", showType(), *this
}); ).atPos(pos).debugThrow();
} }

View File

@ -2,6 +2,7 @@
///@file ///@file
#include "attr-set.hh" #include "attr-set.hh"
#include "eval-error.hh"
#include "types.hh" #include "types.hh"
#include "value.hh" #include "value.hh"
#include "nixexpr.hh" #include "nixexpr.hh"
@ -151,45 +152,6 @@ struct DebugTrace {
bool isError; bool isError;
}; };
void debugError(Error * e, Env & env, Expr & expr);
class ErrorBuilder
{
private:
EvalState & state;
ErrorInfo info;
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
public:
template<typename... Args>
[[nodiscard, gnu::noinline]]
static ErrorBuilder * create(EvalState & s, const Args & ... args)
{
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
}
[[nodiscard, gnu::noinline]]
ErrorBuilder & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
template<class ErrorType>
[[gnu::noinline, gnu::noreturn]]
void debugThrow();
};
class EvalState : public std::enable_shared_from_this<EvalState> class EvalState : public std::enable_shared_from_this<EvalState>
{ {
public: public:
@ -274,39 +236,10 @@ public:
void runDebugRepl(const Error * error, const Env & env, const Expr & expr); void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
template<class E> template<class T, typename... Args>
[[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && error)
{
debugThrow(error, nullptr, nullptr);
}
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env * env, const Expr * expr)
{
if (debugRepl && ((env && expr) || !debugTraces.empty())) {
if (!env || !expr) {
const DebugTrace & last = debugTraces.front();
env = &last.env;
expr = &last.expr;
}
runDebugRepl(&error, *env, *expr);
}
throw std::move(error);
}
// This is dangerous, but gets in line with the idea that error creation and
// throwing should not allocate on the stack of hot functions.
// as long as errors are immediately thrown, it works.
ErrorBuilder * errorBuilder;
template<typename... Args>
[[nodiscard, gnu::noinline]] [[nodiscard, gnu::noinline]]
ErrorBuilder & error(const Args & ... args) { EvalErrorBuilder<T> & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...); return *new EvalErrorBuilder<T>(*this, args...);
return *errorBuilder;
} }
private: private:
@ -845,22 +778,6 @@ SourcePath resolveExprPath(SourcePath path);
*/ */
bool isAllowedURI(std::string_view uri, const Strings & allowedPaths); bool isAllowedURI(std::string_view uri, const Strings & allowedPaths);
struct InvalidPathError : EvalError
{
Path path;
InvalidPathError(const Path & path);
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~InvalidPathError() throw () { };
#endif
};
template<class ErrorType>
void ErrorBuilder::debugThrow()
{
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
state.debugThrowLastTrace(ErrorType(info));
}
} }
#include "eval-inline.hh" #include "eval-inline.hh"

View File

@ -147,8 +147,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
NixStringContext emptyContext = {}; NixStringContext emptyContext = {};
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump()); attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
} else } else
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value)); state.symbols[attr.name], showType(*attr.value)).debugThrow();
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
} }
@ -295,15 +295,15 @@ 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)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", state.error<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value)); 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
throw TypeError("flake configuration setting '%s' is %s", state.error<TypeError>("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value)); state.symbols[setting.name], showType(*setting.value)).debugThrow();
} }
} }
@ -865,11 +865,11 @@ static void prim_flakeRefToString(
attrs.emplace(state.symbols[attr.name], attrs.emplace(state.symbols[attr.name],
std::string(attr.value->string_view())); std::string(attr.value->string_view()));
} else { } else {
state.error( state.error<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],
showType(*attr.value)).debugThrow<EvalError>(); showType(*attr.value)).debugThrow();
} }
} }
auto flakeRef = FlakeRef::fromAttrs(attrs); auto flakeRef = FlakeRef::fromAttrs(attrs);

View File

@ -49,7 +49,7 @@ std::string PackageInfo::queryName() const
{ {
if (name == "" && attrs) { if (name == "" && attrs) {
auto i = attrs->find(state->sName); auto i = attrs->find(state->sName);
if (i == attrs->end()) throw TypeError("derivation name missing"); if (i == attrs->end()) state->error<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;
@ -396,7 +396,8 @@ static void getDerivations(EvalState & state, Value & vIn,
} }
} }
else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)"); else
state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
} }

View File

@ -1,4 +1,6 @@
#include "json-to-value.hh" #include "json-to-value.hh"
#include "value.hh"
#include "eval.hh"
#include <variant> #include <variant>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -159,7 +161,7 @@ public:
} }
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) { bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
throw JSONParseError(ex.what()); throw JSONParseError("%s", ex.what());
} }
}; };

View File

@ -1,13 +1,16 @@
#pragma once #pragma once
///@file ///@file
#include "eval.hh" #include "error.hh"
#include <string> #include <string>
namespace nix { namespace nix {
MakeError(JSONParseError, EvalError); class EvalState;
struct Value;
MakeError(JSONParseError, Error);
void parseJSON(EvalState & state, const std::string_view & s, Value & v); void parseJSON(EvalState & state, const std::string_view & s, Value & v);

View File

@ -146,9 +146,9 @@ or { return OR_KW; }
try { try {
yylval->n = boost::lexical_cast<int64_t>(yytext); yylval->n = boost::lexical_cast<int64_t>(yytext);
} catch (const boost::bad_lexical_cast &) { } catch (const boost::bad_lexical_cast &) {
throw ParseError({ throw ParseError(ErrorInfo{
.msg = hintfmt("invalid integer '%1%'", yytext), .msg = hintfmt("invalid integer '%1%'", yytext),
.errPos = state->positions[CUR_POS], .pos = state->positions[CUR_POS],
}); });
} }
return INT_LIT; return INT_LIT;
@ -156,9 +156,9 @@ or { return OR_KW; }
{FLOAT} { errno = 0; {FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0); yylval->nf = strtod(yytext, 0);
if (errno != 0) if (errno != 0)
throw ParseError({ throw ParseError(ErrorInfo{
.msg = hintfmt("invalid float '%1%'", yytext), .msg = hintfmt("invalid float '%1%'", yytext),
.errPos = state->positions[CUR_POS], .pos = state->positions[CUR_POS],
}); });
return FLOAT_LIT; return FLOAT_LIT;
} }
@ -285,9 +285,9 @@ or { return OR_KW; }
<INPATH_SLASH>{ANY} | <INPATH_SLASH>{ANY} |
<INPATH_SLASH><<EOF>> { <INPATH_SLASH><<EOF>> {
throw ParseError({ throw ParseError(ErrorInfo{
.msg = hintfmt("path has a trailing slash"), .msg = hintfmt("path has a trailing slash"),
.errPos = state->positions[CUR_POS], .pos = state->positions[CUR_POS],
}); });
} }

View File

@ -296,10 +296,10 @@ 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)
throw UndefinedVarError({ es.error<UndefinedVarError>(
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]), "undefined variable '%1%'",
.errPos = es.positions[pos] es.symbols[name]
}); ).atPos(pos).debugThrow();
for (auto * e = env.get(); e && !fromWith; e = e->up) for (auto * e = env.get(); e && !fromWith; e = e->up)
fromWith = e->isWith; fromWith = e->isWith;
this->level = withLevel; this->level = withLevel;

View File

@ -9,28 +9,13 @@
#include "error.hh" #include "error.hh"
#include "chunked-vector.hh" #include "chunked-vector.hh"
#include "position.hh" #include "position.hh"
#include "eval-error.hh"
#include "pos-idx.hh" #include "pos-idx.hh"
#include "pos-table.hh" #include "pos-table.hh"
namespace nix { namespace nix {
MakeError(EvalError, Error);
MakeError(ParseError, Error);
MakeError(AssertionError, EvalError);
MakeError(ThrownError, AssertionError);
MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
class InfiniteRecursionError : public EvalError
{
friend class EvalState;
public:
using EvalError::EvalError;
};
struct Env; struct Env;
struct Value; struct Value;
class EvalState; class EvalState;

View File

@ -66,7 +66,7 @@ inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, co
throw ParseError({ throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%", .msg = hintfmt("attribute '%1%' already defined at %2%",
showAttrPath(symbols, attrPath), positions[prevPos]), showAttrPath(symbols, attrPath), positions[prevPos]),
.errPos = positions[pos] .pos = positions[pos]
}); });
} }
@ -74,7 +74,7 @@ inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx pre
{ {
throw ParseError({ throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]), .msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
.errPos = positions[pos] .pos = positions[pos]
}); });
} }
@ -155,13 +155,13 @@ inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Sym
if (duplicate) if (duplicate)
throw ParseError({ throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]), .msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
.errPos = positions[duplicate->second] .pos = positions[duplicate->second]
}); });
if (arg && formals->has(arg)) if (arg && formals->has(arg))
throw ParseError({ throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]), .msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]),
.errPos = positions[pos] .pos = positions[pos]
}); });
return formals; return formals;

View File

@ -66,7 +66,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
{ {
throw ParseError({ throw ParseError({
.msg = hintfmt(error), .msg = hintfmt(error),
.errPos = state->positions[state->at(*loc)] .pos = state->positions[state->at(*loc)]
}); });
} }
@ -155,7 +155,7 @@ expr_function
{ if (!$2->dynamicAttrs.empty()) { if (!$2->dynamicAttrs.empty())
throw ParseError({ throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in let"), .msg = hintfmt("dynamic attributes not allowed in let"),
.errPos = state->positions[CUR_POS] .pos = state->positions[CUR_POS]
}); });
$$ = new ExprLet($2, $4); $$ = new ExprLet($2, $4);
} }
@ -245,7 +245,7 @@ expr_simple
if (noURLLiterals) if (noURLLiterals)
throw ParseError({ throw ParseError({
.msg = hintfmt("URL literals are disabled"), .msg = hintfmt("URL literals are disabled"),
.errPos = state->positions[CUR_POS] .pos = state->positions[CUR_POS]
}); });
$$ = new ExprString(std::string($1)); $$ = new ExprString(std::string($1));
} }
@ -341,7 +341,7 @@ attrs
} else } else
throw ParseError({ throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in inherit"), .msg = hintfmt("dynamic attributes not allowed in inherit"),
.errPos = state->positions[state->at(@2)] .pos = state->positions[state->at(@2)]
}); });
} }
| { $$ = new AttrPath; } | { $$ = new AttrPath; }

View File

@ -39,10 +39,6 @@ namespace nix {
* Miscellaneous * Miscellaneous
*************************************************************/ *************************************************************/
InvalidPathError::InvalidPathError(const Path & path) :
EvalError("path '%s' is not valid", path), path(path) {}
StringMap EvalState::realiseContext(const NixStringContext & context) StringMap EvalState::realiseContext(const NixStringContext & context)
{ {
std::vector<DerivedPath::Built> drvs; std::vector<DerivedPath::Built> drvs;
@ -51,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))
debugThrowLastTrace(InvalidPathError(store->printStorePath(p))); error<InvalidPathError>(store->printStorePath(p)).debugThrow();
}; };
std::visit(overloaded { std::visit(overloaded {
[&](const NixStringContextElem::Built & b) { [&](const NixStringContextElem::Built & b) {
@ -78,9 +74,10 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
if (drvs.empty()) return {}; if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation) if (!evalSettings.enableImportFromDerivation)
debugThrowLastTrace(Error( error<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();
/* Build/substitute the context. */ /* Build/substitute the context. */
std::vector<DerivedPath> buildReqs; std::vector<DerivedPath> buildReqs;
@ -340,16 +337,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.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror())); state.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
dlerror(); dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str()); ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) { if(!func) {
char *message = dlerror(); char *message = dlerror();
if (message) if (message)
state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message)); state.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow();
else else
state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path)); state.error<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
} }
(func)(state, v); (func)(state, v);
@ -365,7 +362,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("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>(); state.error<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",
@ -380,7 +377,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
try { try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>(); state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow();
} }
auto output = runProgram(program, true, commandArgs); auto output = runProgram(program, true, commandArgs);
@ -582,7 +579,7 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat) if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint; return v1->integer < v2->fpoint;
if (v1->type() != v2->type()) if (v1->type() != v2->type())
state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>(); state.error<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"
@ -610,7 +607,7 @@ struct CompareValues
} }
} }
default: default:
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>(); state.error<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) {
@ -637,7 +634,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("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>(); state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
} }
return value; return value;
} }
@ -758,7 +755,7 @@ static RegisterPrimOp primop_break({
auto error = Error(ErrorInfo { auto error = Error(ErrorInfo {
.level = lvlInfo, .level = lvlInfo,
.msg = hintfmt("breakpoint reached"), .msg = hintfmt("breakpoint reached"),
.errPos = state.positions[pos], .pos = state.positions[pos],
}); });
auto & dt = state.debugTraces.front(); auto & dt = state.debugTraces.front();
@ -769,7 +766,7 @@ static RegisterPrimOp primop_break({
throw Error(ErrorInfo{ throw Error(ErrorInfo{
.level = lvlInfo, .level = lvlInfo,
.msg = hintfmt("quit the debugger"), .msg = hintfmt("quit the debugger"),
.errPos = nullptr, .pos = nullptr,
}); });
} }
} }
@ -790,7 +787,7 @@ static RegisterPrimOp primop_abort({
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.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s)); state.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
} }
}); });
@ -809,7 +806,7 @@ static RegisterPrimOp primop_throw({
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.debugThrowLastTrace(ThrownError(s)); state.error<ThrownError>(s).debugThrow();
} }
}); });
@ -1128,37 +1125,33 @@ drvName, Bindings * attrs, Value & v)
experimentalFeatureSettings.require(Xp::DynamicDerivations); experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {}; ingestionMethod = TextIngestionMethod {};
} else } else
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), "invalid value '%s' for 'outputHashMode' attribute", s
.errPos = state.positions[noPos] ).atPos(v).debugThrow();
}));
}; };
auto handleOutputs = [&](const Strings & ss) { auto handleOutputs = [&](const Strings & ss) {
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.debugThrowLastTrace(EvalError({ state.error<EvalError>("duplicate derivation output '%1%'", j)
.msg = hintfmt("duplicate derivation output '%1%'", j), .atPos(v)
.errPos = state.positions[noPos] .debugThrow();
}));
/* !!! Check whether j is a valid attribute /* !!! Check whether j is a valid attribute
name. */ name. */
/* Derivations cannot be named drv, because /* Derivations cannot be named drv, because
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.debugThrowLastTrace(EvalError({ state.error<EvalError>("invalid derivation output name 'drv'")
.msg = hintfmt("invalid derivation output name 'drv'" ), .atPos(v)
.errPos = state.positions[noPos] .debugThrow();
}));
outputs.insert(j); outputs.insert(j);
} }
if (outputs.empty()) if (outputs.empty())
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("derivation cannot have an empty set of outputs")
.msg = hintfmt("derivation cannot have an empty set of outputs"), .atPos(v)
.errPos = state.positions[noPos] .debugThrow();
}));
}; };
try { try {
@ -1281,16 +1274,14 @@ drvName, Bindings * attrs, Value & v)
/* Do we have all required attributes? */ /* Do we have all required attributes? */
if (drv.builder == "") if (drv.builder == "")
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("required attribute 'builder' missing")
.msg = hintfmt("required attribute 'builder' missing"), .atPos(v)
.errPos = state.positions[noPos] .debugThrow();
}));
if (drv.platform == "") if (drv.platform == "")
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("required attribute 'system' missing")
.msg = hintfmt("required attribute 'system' missing"), .atPos(v)
.errPos = state.positions[noPos] .debugThrow();
}));
/* Check whether the derivation name is valid. */ /* Check whether the derivation name is valid. */
if (isDerivation(drvName) && if (isDerivation(drvName) &&
@ -1298,10 +1289,10 @@ drvName, Bindings * attrs, Value & v)
outputs.size() == 1 && outputs.size() == 1 &&
*(outputs.begin()) == "out")) *(outputs.begin()) == "out"))
{ {
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension), "derivation names are allowed to end in '%s' only if they produce a single derivation file",
.errPos = state.positions[noPos] drvExtension
})); ).atPos(v).debugThrow();
} }
if (outputHash) { if (outputHash) {
@ -1310,10 +1301,9 @@ 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.debugThrowLastTrace(Error({ state.error<EvalError>(
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), "multiple outputs are not supported in fixed-output derivations"
.errPos = state.positions[noPos] ).atPos(v).debugThrow();
}));
auto h = newHashAllowEmpty(*outputHash, parseHashAlgoOpt(outputHashAlgo)); auto h = newHashAllowEmpty(*outputHash, parseHashAlgoOpt(outputHashAlgo));
@ -1332,10 +1322,8 @@ drvName, Bindings * attrs, Value & v)
else if (contentAddressed || isImpure) { else if (contentAddressed || isImpure) {
if (contentAddressed && isImpure) if (contentAddressed && isImpure)
throw EvalError({ state.error<EvalError>("derivation cannot be both content-addressed and impure")
.msg = hintfmt("derivation cannot be both content-addressed and impure"), .atPos(v).debugThrow();
.errPos = state.positions[noPos]
});
auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256); auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive); auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
@ -1376,10 +1364,10 @@ 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)
throw AssertionError({ state.error<AssertionError>(
.msg = hintfmt("derivation produced no hash for output '%s'", i), "derivation produced no hash for output '%s'",
.errPos = state.positions[noPos], i
}); ).atPos(v).debugThrow();
auto outPath = state.store->makeOutputPath(i, *h, drvName); auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath); drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign( drv.outputs.insert_or_assign(
@ -1485,10 +1473,10 @@ static RegisterPrimOp primop_toPath({
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.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"), "'%s' is not allowed in pure evaluation mode",
.errPos = state.positions[pos] "builtins.storePath"
})); ).atPos(pos).debugThrow();
NixStringContext context; NixStringContext context;
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'").path; auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'").path;
@ -1498,10 +1486,8 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>("path '%1%' is not in the Nix store", path)
.msg = hintfmt("path '%1%' is not in the Nix store", path), .atPos(pos).debugThrow();
.errPos = state.positions[pos]
}));
auto path2 = state.store->toStorePath(path.abs()).first; auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(path2); state.store->ensurePath(path2);
@ -1616,7 +1602,10 @@ 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.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path)); state.error<EvalError>(
"the contents of the file '%1%' cannot be represented as a Nix string",
path
).atPos(pos).debugThrow();
StorePathSet refs; StorePathSet refs;
if (state.store->isInStore(path.path.abs())) { if (state.store->isInStore(path.path.abs())) {
try { try {
@ -1673,10 +1662,11 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), "cannot find '%1%', since path '%2%' is not valid",
.errPos = state.positions[pos] path,
})); e.path
).atPos(pos).debugThrow();
} }
searchPath.elements.emplace_back(SearchPath::Elem { searchPath.elements.emplace_back(SearchPath::Elem {
@ -1745,10 +1735,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile"); auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
std::optional<HashAlgorithm> ha = parseHashAlgo(algo); std::optional<HashAlgorithm> ha = parseHashAlgo(algo);
if (!ha) if (!ha)
state.debugThrowLastTrace(Error({ state.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
@ -2068,13 +2055,12 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt( "files created by %1% may not reference derivations, but %2% references %3%",
"in 'toFile': the file named '%1%' must not contain a reference " "builtins.toFile",
"to a derivation but contains (%2%)", name,
name, c.to_string()), c.to_string()
.errPos = state.positions[pos] ).atPos(pos).debugThrow();
}));
} }
auto storePath = settings.readOnlyMode auto storePath = settings.readOnlyMode
@ -2243,7 +2229,10 @@ static void addPath(
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) { if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
auto dstPath = fetchToStore(*state.store, path.resolveSymlinks(), name, method, filter.get(), state.repair); auto dstPath = fetchToStore(*state.store, path.resolveSymlinks(), name, method, filter.get(), state.repair);
if (expectedHash && expectedStorePath != dstPath) if (expectedHash && expectedStorePath != dstPath)
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path)); state.error<EvalError>(
"store path mismatch in (possibly filtered) path added from '%s'",
path
).atPos(pos).debugThrow();
state.allowAndSetStorePathString(dstPath, v); state.allowAndSetStorePathString(dstPath, v);
} else } else
state.allowAndSetStorePathString(*expectedStorePath, v); state.allowAndSetStorePathString(*expectedStorePath, v);
@ -2343,16 +2332,15 @@ 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"), HashAlgorithm::SHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
else else
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]), "unsupported argument '%1%' to 'addPath'",
.errPos = state.positions[attr.pos] state.symbols[attr.name]
})); ).atPos(attr.pos).debugThrow();
} }
if (!path) if (!path)
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"), "missing required 'path' attribute in the first argument to builtins.path"
.errPos = state.positions[pos] ).atPos(pos).debugThrow();
}));
if (name.empty()) if (name.empty())
name = path->baseName(); name = path->baseName();
@ -2770,10 +2758,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
return; return;
} }
if (!args[0]->isLambda()) if (!args[0]->isLambda())
state.debugThrowLastTrace(TypeError({ state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
.msg = hintfmt("'functionArgs' requires a function"),
.errPos = state.positions[pos]
}));
if (!args[0]->lambda.fun->hasFormals()) { if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings); v.mkAttrs(&state.emptyBindings);
@ -2943,10 +2928,10 @@ 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.debugThrowLastTrace(Error({ state.error<EvalError>(
.msg = hintfmt("list index %1% is out of bounds", n), "list index %1% is out of bounds",
.errPos = state.positions[pos] n
})); ).atPos(pos).debugThrow();
state.forceValue(*list.listElems()[n], pos); state.forceValue(*list.listElems()[n], pos);
v = *list.listElems()[n]; v = *list.listElems()[n];
} }
@ -2991,10 +2976,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.debugThrowLastTrace(Error({ state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
.msg = hintfmt("'tail' called on an empty list"),
.errPos = state.positions[pos]
}));
state.mkList(v, args[0]->listSize() - 1); state.mkList(v, args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n) for (unsigned int n = 0; n < v.listSize(); ++n)
@ -3251,7 +3233,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"); auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
if (len < 0) if (len < 0)
state.error("cannot create list of size %1%", len).debugThrow<EvalError>(); state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
// More strict than striclty (!) necessary, but acceptable // More strict than striclty (!) necessary, but acceptable
// as evaluating map without accessing any values makes little sense. // as evaluating map without accessing any values makes little sense.
@ -3568,10 +3550,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.debugThrowLastTrace(EvalError({ state.error<EvalError>("division by zero").atPos(pos).debugThrow();
.msg = hintfmt("division by zero"),
.errPos = state.positions[pos]
}));
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);
@ -3580,10 +3559,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division"); NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
/* Avoid division overflow as it might raise SIGFPE. */ /* Avoid division overflow as it might raise SIGFPE. */
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
.msg = hintfmt("overflow in integer division"),
.errPos = state.positions[pos]
}));
v.mkInt(i1 / i2); v.mkInt(i1 / i2);
} }
@ -3714,10 +3690,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring"); int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
if (start < 0) if (start < 0)
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
.msg = hintfmt("negative start position in 'substring'"),
.errPos = state.positions[pos]
}));
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring"); int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
@ -3782,10 +3755,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString"); auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
std::optional<HashAlgorithm> ha = parseHashAlgo(algo); std::optional<HashAlgorithm> ha = parseHashAlgo(algo);
if (!ha) if (!ha)
state.debugThrowLastTrace(Error({ state.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
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");
@ -3951,15 +3921,13 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .atPos(pos)
.errPos = state.positions[pos] .debugThrow();
}));
} else } else
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("invalid regular expression '%s'", re)
.msg = hintfmt("invalid regular expression '%s'", re), .atPos(pos)
.errPos = state.positions[pos] .debugThrow();
}));
} }
} }
@ -4055,15 +4023,13 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .atPos(pos)
.errPos = state.positions[pos] .debugThrow();
}));
} else } else
state.debugThrowLastTrace(EvalError({ state.error<EvalError>("invalid regular expression '%s'", re)
.msg = hintfmt("invalid regular expression '%s'", re), .atPos(pos)
.errPos = state.positions[pos] .debugThrow();
}));
} }
} }
@ -4139,7 +4105,9 @@ 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("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>(); state.error<EvalError>(
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
).atPos(pos).debugThrow();
std::vector<std::string> from; std::vector<std::string> from;
from.reserve(args[0]->listSize()); from.reserve(args[0]->listSize());

View File

@ -98,30 +98,30 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
auto contextSize = context.size(); auto contextSize = context.size();
if (contextSize != 1) { if (contextSize != 1) {
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("context of string '%s' must have exactly one element, but has %d", *s, contextSize), "context of string '%s' must have exactly one element, but has %d",
.errPos = state.positions[pos] *s,
}); contextSize
).atPos(pos).debugThrow();
} }
NixStringContext context2 { NixStringContext context2 {
(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()) {
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("path '%s' is not a derivation", "path '%s' is not a derivation",
state.store->printStorePath(c.path)), state.store->printStorePath(c.path)
.errPos = state.positions[pos], ).atPos(pos).debugThrow();
});
} }
return NixStringContextElem::DrvDeep { return NixStringContextElem::DrvDeep {
.drvPath = c.path, .drvPath = c.path,
}; };
}, },
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output), "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
.errPos = state.positions[pos], c.output
}); ).atPos(pos).debugThrow();
}, },
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */ /* Reuse original item because we want this to be idempotent. */
@ -261,10 +261,10 @@ 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))
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("context key '%s' is not a store path", name), "context key '%s' is not a store path",
.errPos = state.positions[i.pos] name
}); ).atPos(i.pos).debugThrow();
auto namePath = state.store->parseStorePath(name); auto namePath = state.store->parseStorePath(name);
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(namePath); state.store->ensurePath(namePath);
@ -281,10 +281,10 @@ 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)) {
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name), "tried to add all-outputs context of %s, which is not a derivation, to a string",
.errPos = state.positions[i.pos] name
}); ).atPos(i.pos).debugThrow();
} }
context.emplace(NixStringContextElem::DrvDeep { context.emplace(NixStringContextElem::DrvDeep {
.drvPath = namePath, .drvPath = namePath,
@ -296,10 +296,10 @@ 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)) {
throw EvalError({ state.error<EvalError>(
.msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name), "tried to add derivation output context of %s, which is not a derivation, to a string",
.errPos = state.positions[i.pos] name
}); ).atPos(i.pos).debugThrow();
} }
for (auto elem : iter->value->listItems()) { for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context"); auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");

View File

@ -27,7 +27,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
state.store->printStorePath(fromPath), state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath), state.store->printStorePath(rewrittenPath),
state.store->printStorePath(*toPathMaybe)), state.store->printStorePath(*toPathMaybe)),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
if (!toPathMaybe) if (!toPathMaybe)
throw Error({ throw Error({
@ -36,7 +36,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
"Use this value for the 'toPath' attribute passed to 'fetchClosure'", "Use this value for the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(fromPath), state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath)), state.store->printStorePath(rewrittenPath)),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
@ -54,7 +54,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n" "The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.", "Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
state.store->printStorePath(toPath)), state.store->printStorePath(toPath)),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
@ -80,7 +80,7 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
"to the 'fetchClosure' arguments.\n\n" "to the 'fetchClosure' arguments.\n\n"
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.", "Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
state.store->printStorePath(fromPath)), state.store->printStorePath(fromPath)),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
@ -103,7 +103,7 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n" "The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed", "Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
state.store->printStorePath(fromPath)), state.store->printStorePath(fromPath)),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
@ -154,14 +154,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
else else
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName), .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
if (!fromPath) if (!fromPath)
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
bool inputAddressed = inputAddressedMaybe.value_or(false); bool inputAddressed = inputAddressedMaybe.value_or(false);
@ -172,14 +172,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
.msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them", .msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
"inputAddressed", "inputAddressed",
"toPath"), "toPath"),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
} }
if (!fromStoreUrl) if (!fromStoreUrl)
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
auto parsedURL = parseURL(*fromStoreUrl); auto parsedURL = parseURL(*fromStoreUrl);
@ -189,13 +189,13 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({ throw Error({
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
if (!parsedURL.query.empty()) if (!parsedURL.query.empty())
throw Error({ throw Error({
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.errPos = state.positions[pos] .pos = state.positions[pos]
}); });
auto fromStore = openStore(parsedURL.to_string()); auto fromStore = openStore(parsedURL.to_string());

View File

@ -38,17 +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
throw EvalError({ state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
});
} }
if (url.empty()) if (url.empty())
throw EvalError({ state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
.msg = hintfmt("'url' argument required"),
.errPos = state.positions[pos]
});
} else } else
url = state.coerceToString(pos, *args[0], context, url = state.coerceToString(pos, *args[0], context,

View File

@ -100,16 +100,14 @@ static void fetchTree(
if (auto aType = args[0]->attrs->get(state.sType)) { if (auto aType = args[0]->attrs->get(state.sType)) {
if (type) if (type)
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("unexpected attribute 'type'"), "unexpected attribute 'type'"
.errPos = state.positions[pos] ).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.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), "attribute 'type' is missing in call to 'fetchTree'"
.errPos = state.positions[pos] ).atPos(pos).debugThrow();
}));
attrs.emplace("type", type.value()); attrs.emplace("type", type.value());
@ -132,8 +130,8 @@ static void fetchTree(
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump()); attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
} }
else else
state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value))); state.symbols[attr.name], showType(*attr.value)).debugThrow();
} }
if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) { if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
@ -142,10 +140,9 @@ static void fetchTree(
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.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"), "attribute 'name' isnt supported in call to 'fetchTree'"
.errPos = state.positions[pos] ).atPos(pos).debugThrow();
}));
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
@ -163,10 +160,9 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes)) if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"), "passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"
.errPos = state.positions[pos] ).atPos(pos).debugThrow();
}));
input = fetchers::Input::fromURL(url); input = fetchers::Input::fromURL(url);
} }
} }
@ -175,10 +171,14 @@ 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()) {
auto fetcher = "fetchTree";
if (params.isFetchGit) if (params.isFetchGit)
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchGit' requires a locked input, at %s", state.positions[pos])); fetcher = "fetchGit";
else
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos])); state.error<EvalError>(
"in pure evaluation mode, %s requires a locked input",
fetcher
).atPos(pos).debugThrow();
} }
state.checkURI(input.toURLString()); state.checkURI(input.toURLString());
@ -432,17 +432,13 @@ 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.debugThrowLastTrace(EvalError({ state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who), .atPos(pos).debugThrow();
.errPos = state.positions[attr.pos]
}));
} }
if (!url) if (!url)
state.debugThrowLastTrace(EvalError({ state.error<EvalError>(
.msg = hintfmt("'url' argument required"), "'url' argument required").atPos(pos).debugThrow();
.errPos = state.positions[pos]
}));
} 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");
@ -455,7 +451,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.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who)); state.error<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->algo == HashAlgorithm::SHA256) { if (expectedHash && expectedHash->algo == HashAlgorithm::SHA256) {
@ -484,9 +480,15 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
auto hash = unpack auto hash = unpack
? state.store->queryPathInfo(storePath)->narHash ? state.store->queryPathInfo(storePath)->narHash
: hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath)); : hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash) if (hash != *expectedHash) {
state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", state.error<EvalError>(
*url, expectedHash->to_string(HashFormat::Nix32, true), hash.to_string(HashFormat::Nix32, true))); "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url,
expectedHash->to_string(HashFormat::Nix32, true),
hash.to_string(HashFormat::Nix32, true)
).withExitStatus(102)
.debugThrow();
}
} }
state.allowAndSetStorePathString(storePath, v); state.allowAndSetStorePathString(storePath, v);

View File

@ -83,10 +83,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
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
throw EvalError({ state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
.errPos = state.positions[pos]
});
} }
} }

View File

@ -80,7 +80,7 @@ json printValueAsJSON(EvalState & state, bool strict,
try { try {
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore)); out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, e.addTrace(state.positions[pos],
hintfmt("while evaluating list element at index %1%", i)); hintfmt("while evaluating list element at index %1%", i));
throw; throw;
} }
@ -99,13 +99,12 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk: case nThunk:
case nFunction: case nFunction:
auto e = TypeError({ state.error<TypeError>(
.msg = hintfmt("cannot convert %1% to JSON", showType(v)), "cannot convert %1% to JSON",
.errPos = state.positions[v.determinePos(pos)] showType(v)
}); )
e.addTrace(state.positions[pos], hintfmt("message for the trace")); .atPos(v.determinePos(pos))
state.debugThrowLastTrace(e); .debugThrow();
throw e;
} }
return out; return out;
} }
@ -119,7 +118,8 @@ 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.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType())); state.error<TypeError>("cannot convert %1% to JSON", showType())
.debugThrow();
} }

View File

@ -105,7 +105,7 @@ class ExternalValueBase
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an * Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error. * error.
*/ */
virtual std::string coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const; virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
/** /**
* Compare to another value of the same type. Defaults to uncomparable, * Compare to another value of the same type. Defaults to uncomparable,

View File

@ -340,7 +340,7 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
return 1; return 1;
} catch (BaseError & e) { } catch (BaseError & e) {
logError(e.info()); logError(e.info());
return e.status; return e.info().status;
} catch (std::bad_alloc & e) { } catch (std::bad_alloc & e) {
printError(error + "out of memory"); printError(error + "out of memory");
return 1; return 1;

View File

@ -33,7 +33,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
} }
if (failed.size() == 1 && ex) { if (failed.size() == 1 && ex) {
ex->status = worker.failingExitStatus(); ex->withExitStatus(worker.failingExitStatus());
throw std::move(*ex); throw std::move(*ex);
} else if (!failed.empty()) { } else if (!failed.empty()) {
if (ex) logError(ex->info()); if (ex) logError(ex->info());
@ -104,7 +104,7 @@ void Store::ensurePath(const StorePath & path)
if (goal->exitCode != Goal::ecSuccess) { if (goal->exitCode != Goal::ecSuccess) {
if (goal->ex) { if (goal->ex) {
goal->ex->status = worker.failingExitStatus(); goal->ex->withExitStatus(worker.failingExitStatus());
throw std::move(*goal->ex); throw std::move(*goal->ex);
} else } else
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path)); throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));

View File

@ -119,7 +119,7 @@ struct TunnelLogger : public Logger
if (GET_PROTOCOL_MINOR(clientVersion) >= 26) { if (GET_PROTOCOL_MINOR(clientVersion) >= 26) {
to << STDERR_ERROR << *ex; to << STDERR_ERROR << *ex;
} else { } else {
to << STDERR_ERROR << ex->what() << ex->status; to << STDERR_ERROR << ex->what() << ex->info().status;
} }
} }
} }

View File

@ -335,7 +335,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)
* throwTypeError("expected a set but found %1%", v); * error<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) { * } catch (Error & e) {
* e.addTrace(pos, errorCtx); * e.addTrace(pos, errorCtx);
* throw; * throw;
@ -349,7 +349,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)
* throwTypeError("expected a set but found %1%", v); * error<TypeError>("expected a set but found %1%", v);
* } catch (Error & e) { * } catch (Error & e) {
* e.addTrace(pos, errorCtx); * e.addTrace(pos, errorCtx);
* throw; * throw;
@ -411,7 +411,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
oss << einfo.msg << "\n"; oss << einfo.msg << "\n";
printPosMaybe(oss, "", einfo.errPos); printPosMaybe(oss, "", einfo.pos);
auto suggestions = einfo.suggestions.trim(); auto suggestions = einfo.suggestions.trim();
if (!suggestions.suggestions.empty()) { if (!suggestions.suggestions.empty()) {

View File

@ -84,9 +84,14 @@ inline bool operator>=(const Trace& lhs, const Trace& rhs);
struct ErrorInfo { struct ErrorInfo {
Verbosity level; Verbosity level;
hintformat msg; hintformat msg;
std::shared_ptr<Pos> errPos; std::shared_ptr<Pos> pos;
std::list<Trace> traces; std::list<Trace> traces;
/**
* Exit status.
*/
unsigned int status = 1;
Suggestions suggestions; Suggestions suggestions;
static std::optional<std::string> programName; static std::optional<std::string> programName;
@ -103,18 +108,21 @@ class BaseError : public std::exception
protected: protected:
mutable ErrorInfo err; mutable ErrorInfo err;
/**
* Cached formatted contents of `err.msg`.
*/
mutable std::optional<std::string> what_; mutable std::optional<std::string> what_;
/**
* Format `err.msg` and set `what_` to the resulting value.
*/
const std::string & calcWhat() const; const std::string & calcWhat() const;
public: public:
unsigned int status = 1; // exit status
BaseError(const BaseError &) = default; BaseError(const BaseError &) = default;
template<typename... Args> template<typename... Args>
BaseError(unsigned int status, const Args & ... args) BaseError(unsigned int status, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(args...) } : err { .level = lvlError, .msg = hintfmt(args...), .status = status }
, status(status)
{ } { }
template<typename... Args> template<typename... Args>
@ -149,6 +157,15 @@ public:
const std::string & msg() const { return calcWhat(); } const std::string & msg() const { return calcWhat(); }
const ErrorInfo & info() const { calcWhat(); return err; } const ErrorInfo & info() const { calcWhat(); return err; }
void withExitStatus(unsigned int status)
{
err.status = status;
}
void atPos(std::shared_ptr<Pos> pos) {
err.pos = pos;
}
void pushTrace(Trace trace) void pushTrace(Trace trace)
{ {
err.traces.push_front(trace); err.traces.push_front(trace);

View File

@ -199,7 +199,7 @@ struct JSONLogger : Logger {
json["level"] = ei.level; json["level"] = ei.level;
json["msg"] = oss.str(); json["msg"] = oss.str();
json["raw_msg"] = ei.msg.str(); json["raw_msg"] = ei.msg.str();
to_json(json, ei.errPos); to_json(json, ei.pos);
if (loggerSettings.showTrace.get() && !ei.traces.empty()) { if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
nlohmann::json traces = nlohmann::json::array(); nlohmann::json traces = nlohmann::json::array();

View File

@ -950,8 +950,8 @@ static void opServe(Strings opFlags, Strings opArgs)
store->buildPaths(toDerivedPaths(paths)); store->buildPaths(toDerivedPaths(paths));
out << 0; out << 0;
} catch (Error & e) { } catch (Error & e) {
assert(e.status); assert(e.info().status);
out << e.status << e.msg(); out << e.info().status << e.msg();
} }
break; break;
} }

View File

@ -104,7 +104,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
} }
} }
else else
throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]); state->error<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

@ -848,10 +848,10 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto templateDir = templateDirAttr->getString(); auto templateDir = templateDirAttr->getString();
if (!store->isInStore(templateDir)) if (!store->isInStore(templateDir))
throw TypeError( evalState->error<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()); templateDir, templateDirAttr->getAttrPathStr()).debugThrow();
std::vector<Path> changedFiles; std::vector<Path> changedFiles;
std::vector<Path> conflictedFiles; std::vector<Path> conflictedFiles;
@ -1321,7 +1321,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")
throw EvalError("not an app definition"); state->error<EvalError>("not an app definition").debugThrow();
if (json) { if (json) {
j.emplace("type", "app"); j.emplace("type", "app");
} else { } else {

View File

@ -67,7 +67,7 @@ path2=$(nix eval --raw --expr "(builtins.fetchGit { url = file://$repo; rev = \"
[[ $(nix eval --raw --expr "builtins.readFile (fetchGit { url = file://$repo; rev = \"$rev2\"; } + \"/hello\")") = world ]] [[ $(nix eval --raw --expr "builtins.readFile (fetchGit { url = file://$repo; rev = \"$rev2\"; } + \"/hello\")") = world ]]
# But without a hash, it fails # But without a hash, it fails
expectStderr 1 nix eval --expr 'builtins.fetchGit "file:///foo"' | grepQuiet "'fetchGit' requires a locked input" expectStderr 1 nix eval --expr 'builtins.fetchGit "file:///foo"' | grepQuiet "fetchGit requires a locked input"
# Fetch again. This should be cached. # Fetch again. This should be cached.
mv $repo ${repo}-tmp mv $repo ${repo}-tmp
@ -208,7 +208,7 @@ path6=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; ur
[[ $path3 = $path6 ]] [[ $path3 = $path6 ]]
[[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]] [[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]]
expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "'fetchTree' requires a locked input" expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "fetchTree requires a locked input"
# Explicit ref = "HEAD" should work, and produce the same outPath as without ref # Explicit ref = "HEAD" should work, and produce the same outPath as without ref
path7=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).outPath") path7=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).outPath")

View File

@ -14,3 +14,8 @@ error:
8| 8|
error: expected a string but found an integer: 1 error: expected a string but found an integer: 1
at /pwd/lang/eval-fail-attr-name-type.nix:7:17:
6| in
7| attrs.puppy.${key}
| ^
8|

View File

@ -5,4 +5,4 @@ error:
| ^ | ^
2| key = "value" 2| key = "value"
error: while parsing a TOML string: Dates and times are not supported error: while parsing TOML: Dates and times are not supported

View File

@ -20,6 +20,11 @@ error:
3| true 3| true
… while evaluating list element at index 3 … while evaluating list element at index 3
at /pwd/lang/eval-fail-toJSON.nix:2:3:
1| builtins.toJSON {
2| a.b = [
| ^
3| true
… while evaluating attribute 'c' … while evaluating attribute 'c'
at /pwd/lang/eval-fail-toJSON.nix:7:7: at /pwd/lang/eval-fail-toJSON.nix:7:7:

View File

@ -7,3 +7,8 @@ error:
6| 6|
error: expected a string but found a set: { } error: expected a string but found a set: { }
at /pwd/lang/eval-fail-using-set-as-attr-name.nix:5:10:
4| in
5| attr.${key}
| ^
6|

View File

@ -12,33 +12,33 @@ namespace nix {
TEST_F(ErrorTraceTest, TraceBuilder) { TEST_F(ErrorTraceTest, TraceBuilder) {
ASSERT_THROW( ASSERT_THROW(
state.error("Not much").debugThrow<EvalError>(), state.error<EvalError>("puppy").debugThrow(),
EvalError EvalError
); );
ASSERT_THROW( ASSERT_THROW(
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>(), state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(),
EvalError EvalError
); );
ASSERT_THROW( ASSERT_THROW(
try { try {
try { try {
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>(); state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[noPos], "Something", ""); e.addTrace(state.positions[noPos], "beans", "");
throw; throw;
} }
} catch (BaseError & e) { } catch (BaseError & e) {
ASSERT_EQ(PrintToString(e.info().msg), ASSERT_EQ(PrintToString(e.info().msg),
PrintToString(hintfmt("Not much"))); PrintToString(hintfmt("puppy")));
auto trace = e.info().traces.rbegin(); auto trace = e.info().traces.rbegin();
ASSERT_EQ(e.info().traces.size(), 2); ASSERT_EQ(e.info().traces.size(), 2);
ASSERT_EQ(PrintToString(trace->hint), ASSERT_EQ(PrintToString(trace->hint),
PrintToString(hintfmt("No more"))); PrintToString(hintfmt("doggy")));
trace++; trace++;
ASSERT_EQ(PrintToString(trace->hint), ASSERT_EQ(PrintToString(trace->hint),
PrintToString(hintfmt("Something"))); PrintToString(hintfmt("beans")));
throw; throw;
} }
, EvalError , EvalError
@ -47,12 +47,12 @@ namespace nix {
TEST_F(ErrorTraceTest, NestedThrows) { TEST_F(ErrorTraceTest, NestedThrows) {
try { try {
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>(); state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
} catch (BaseError & e) { } catch (BaseError & e) {
try { try {
state.error("Not much more").debugThrow<EvalError>(); state.error<EvalError>("beans").debugThrow();
} catch (Error & e2) { } catch (Error & e2) {
e.addTrace(state.positions[noPos], "Something", ""); e.addTrace(state.positions[noPos], "beans2", "");
//e2.addTrace(state.positions[noPos], "Something", ""); //e2.addTrace(state.positions[noPos], "Something", "");
ASSERT_TRUE(e.info().traces.size() == 2); ASSERT_TRUE(e.info().traces.size() == 2);
ASSERT_TRUE(e2.info().traces.size() == 0); ASSERT_TRUE(e2.info().traces.size() == 0);