Merge pull request #6204 from layus/coerce-string

Add context to better locate runtime coercions
This commit is contained in:
Eelco Dolstra 2023-01-02 22:35:23 +01:00 committed by GitHub
commit a75b7ba30f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 985 additions and 863 deletions

View file

@ -18,3 +18,6 @@
* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently. * Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
Historical release notes were not changed. Historical release notes were not changed.
* Error traces have been reworked to provide detailed explanations and more
accurate error locations. A short excerpt of the trace is now shown by
default when an error occurs.

View file

@ -591,7 +591,7 @@ ref<eval_cache::EvalCache> openEvalCache(
auto vFlake = state.allocValue(); auto vFlake = state.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake); flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(*vFlake, noPos); state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);

View file

@ -397,7 +397,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
Expr * e = parseString(expr); Expr * e = parseString(expr);
Value v; Value v;
e->eval(*state, *env, v); e->eval(*state, *env, v);
state->forceAttrs(v, noPos); state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
for (auto & i : *v.attrs) { for (auto & i : *v.attrs) {
std::string_view name = state->symbols[i.name]; std::string_view name = state->symbols[i.name];
@ -590,7 +590,7 @@ bool NixRepl::processLine(std::string line)
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> { const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
if (v.type() == nPath || v.type() == nString) { if (v.type() == nPath || v.type() == nString) {
PathSet context; PathSet context;
auto path = state->coerceToPath(noPos, v, context); auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0}; return {path, 0};
} else if (v.isLambda()) { } else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos]; auto pos = state->positions[v.lambda.fun->pos];
@ -834,7 +834,7 @@ void NixRepl::loadFiles()
void NixRepl::addAttrsToScope(Value & attrs) void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }); state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
if (displ + attrs.attrs->size() >= envSize) if (displ + attrs.attrs->size() >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
@ -939,7 +939,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
Bindings::iterator i = v.attrs->find(state->sDrvPath); Bindings::iterator i = v.attrs->find(state->sDrvPath);
PathSet context; PathSet context;
if (i != v.attrs->end()) if (i != v.attrs->end())
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context)); str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
else else
str << "???"; str << "???";
str << "»"; str << "»";

View file

@ -118,7 +118,7 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this // FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing? // toString + parsing?
auto pos = state.forceString(*v2); auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':'); auto colon = pos.rfind(':');
if (colon == std::string::npos) if (colon == std::string::npos)

View file

@ -385,7 +385,7 @@ Value & AttrCursor::getValue()
if (!_value) { if (!_value) {
if (parent) { if (parent) {
auto & vParent = parent->first->getValue(); auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent, noPos); root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(parent->second); auto attr = vParent.attrs->get(parent->second);
if (!attr) if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
@ -571,14 +571,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.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nString && v.type() != nPath) if (v.type() != nString && v.type() != nPath)
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path; return v.type() == nString ? v.string.s : v.path;
} }
@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext()
return *s; return *s;
} }
} else } else
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath) else if (v.type() == nPath)
return {v.path, {}}; return {v.path, {}};
else else
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
} }
bool AttrCursor::getBool() bool AttrCursor::getBool()
@ -626,14 +626,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.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nBool) if (v.type() != nBool)
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
return v.boolean; return v.boolean;
} }
@ -685,7 +685,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
std::vector<std::string> res; std::vector<std::string> res;
for (auto & elem : v.listItems()) for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem))); res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
if (root->db) if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res}; cachedValue = {root->db->setListOfStrings(getKey(), res), res};
@ -703,14 +703,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.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nAttrs) if (v.type() != nAttrs)
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs) for (auto & attr : *getValue().attrs)

View file

@ -103,33 +103,36 @@ void EvalState::forceValue(Value & v, Callable getPos)
else if (v.isApp()) else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos); callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isBlackhole()) else if (v.isBlackhole())
throwEvalError(getPos(), "infinite recursion encountered"); error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos) inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceAttrs(v, [&]() { return pos; }); forceAttrs(v, [&]() { return pos; }, errorCtx);
} }
template <typename Callable> template <typename Callable>
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos) inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
{ {
forceValue(v, getPos); forceValue(v, noPos);
if (v.type() != nAttrs) if (v.type() != nAttrs) {
throwTypeError(getPos(), "value is %1% while a set was expected", v); PosIdx pos = getPos();
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos) inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); forceValue(v, noPos);
if (!v.isList()) if (!v.isList()) {
throwTypeError(pos, "value is %1% while a list was expected", v); error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
} }

View file

@ -318,7 +318,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
} else { } else {
Value nameValue; Value nameValue;
name.expr->eval(state, env, nameValue); name.expr->eval(state, env, nameValue);
state.forceStringNoCtx(nameValue); state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
return state.symbols.create(nameValue.string.s); return state.symbols.create(nameValue.string.s);
} }
} }
@ -414,6 +414,44 @@ static Strings parseNixPath(const std::string & s)
return res; return res;
} }
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 Strings & _searchPath, const Strings & _searchPath,
@ -646,25 +684,7 @@ void EvalState::addConstant(const std::string & name, Value * v)
Value * EvalState::addPrimOp(const std::string & name, Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp) size_t arity, PrimOpFun primOp)
{ {
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
auto sym = symbols.create(name2);
/* Hack to make constants lazy: turn them into a application of
the primop to a dummy value. */
if (arity == 0) {
auto vPrimOp = allocValue();
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
Value v;
v.mkApp(vPrimOp, vPrimOp);
return addConstant(name, v);
}
Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v;
} }
@ -842,176 +862,14 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
} }
} }
/* Every "format" object (even temporary) takes up a few hundred bytes
of stack space, which is a real killer in the recursive
evaluator. So here are some helper functions for throwing
exceptions. */
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError(s, s2));
}
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError(ErrorInfo{
.msg = hintfmt(s, s2),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[noPos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
{
// p1 is where the error occurred; p2 is a position mentioned in the message.
debugThrow(EvalError({
.msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = positions[p1]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[expr.getPos()],
}), env, expr);
}
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(AssertionError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(UndefinedVarError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(MissingArgumentError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{ {
e.addTrace(nullptr, s, s2); e.addTrace(nullptr, s, s2);
} }
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
{ {
e.addTrace(positions[pos], s, s2); e.addTrace(positions[pos], hintfmt(s, s2), frame);
} }
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker( static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
@ -1088,7 +946,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
if (env->type == Env::HasWithExpr) { if (env->type == Env::HasWithExpr) {
if (noEval) return 0; if (noEval) return 0;
Value * v = allocValue(); Value * v = allocValue();
evalAttrs(*env->up, (Expr *) env->values[0], *v); evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, "<borked>");
env->values[0] = v; env->values[0] = v;
env->type = Env::HasWithAttrs; env->type = Env::HasWithAttrs;
} }
@ -1098,7 +956,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value; return j->value;
} }
if (!env->prevWith) if (!env->prevWith)
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var)); error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
for (size_t l = env->prevWith; l; --l, env = env->up) ; for (size_t l = env->prevWith; l; --l, env = env->up) ;
} }
} }
@ -1248,7 +1106,7 @@ void EvalState::cacheFile(
// computation. // computation.
if (mustBeTrivial && if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e))) !(dynamic_cast<ExprAttrs *>(e)))
throw EvalError("file '%s' must be an attribute set", path); error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
@ -1266,31 +1124,31 @@ void EvalState::eval(Expr * e, Value & v)
} }
inline bool EvalState::evalBool(Env & env, Expr * e) inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
{ {
try {
Value v; Value v;
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nBool) if (v.type() != nBool)
throwTypeError(noPos, "value is %1% while a Boolean was expected", v, env, *e); error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
return v.boolean; return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos) inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
{
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v, env, *e);
return v.boolean;
}
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
{ {
try {
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nAttrs) if (v.type() != nAttrs)
throwTypeError(noPos, "value is %1% while a set was expected", v, env, *e); error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
@ -1363,7 +1221,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Hence we need __overrides.) */ Hence we need __overrides.) */
if (hasOverrides) { if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value; Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }); state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
newBnds->push_back(i); newBnds->push_back(i);
@ -1391,11 +1249,11 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
state.forceValue(nameVal, i.pos); state.forceValue(nameVal, i.pos);
if (nameVal.type() == nNull) if (nameVal.type() == nNull)
continue; continue;
state.forceStringNoCtx(nameVal); state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
auto nameSym = state.symbols.create(nameVal.string.s); auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym); Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end()) if (j != v.attrs->end())
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this); state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
i.valueExpr->setName(nameSym); i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */ /* Keep sorted order so find can catch duplicates */
@ -1492,15 +1350,14 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
return; return;
} }
} else { } else {
state.forceAttrs(*vAttrs, pos); state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames; std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs) for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(state.symbols[attr.name]); allAttrNames.insert(state.symbols[attr.name]);
state.throwEvalError( auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
pos, state.error("attribute '%1%' missing", state.symbols[name])
Suggestions::bestMatches(allAttrNames, state.symbols[name]), .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
"attribute '%1%' missing", state.symbols[name], env, *this);
} }
} }
vAttrs = j->value; vAttrs = j->value;
@ -1595,7 +1452,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals()) if (!lambda.hasFormals())
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
else { else {
forceAttrs(*args[0], pos); try {
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
} catch (Error & e) {
if (pos) e.addTrace(positions[pos], "from call site");
throw;
}
if (lambda.arg) if (lambda.arg)
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
@ -1607,8 +1469,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & i : lambda.formals->formals) { for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name); auto j = args[0]->attrs->get(i.name);
if (!j) { if (!j) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", if (!i.def) {
lambda, i.name, *fun.lambda.env, lambda); error("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2); env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else { } else {
attrsUsed++; attrsUsed++;
@ -1626,11 +1495,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
std::set<std::string> formalNames; std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals) for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]); formalNames.insert(symbols[formal.name]);
throwTypeError( auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
pos, error("function '%1%' called with unexpected argument '%2%'",
Suggestions::bestMatches(formalNames, symbols[i.name]), (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
"%1% called with unexpected argument '%2%'", symbols[i.name])
lambda, i.name, *fun.lambda.env, lambda); .atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
} }
abort(); // can't happen abort(); // can't happen
} }
@ -1653,11 +1526,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
lambda.body->eval(*this, env2, vCur); lambda.body->eval(*this, env2, vCur);
} catch (Error & e) { } catch (Error & e) {
if (loggerSettings.showTrace.get()) { if (loggerSettings.showTrace.get()) {
addErrorTrace(e, lambda.pos, "while calling %s", addErrorTrace(
(lambda.name e,
lambda.pos,
"while calling %s",
lambda.name
? concatStrings("'", symbols[lambda.name], "'") ? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda")); : "anonymous lambda",
addErrorTrace(e, pos, "while evaluating call site%s", ""); true);
if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
} }
throw; throw;
} }
@ -1676,9 +1553,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return; return;
} else { } else {
/* We have all the arguments, so call the primop. */ /* We have all the arguments, so call the primop. */
auto name = vCur.primOp->name;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[vCur.primOp->name]++; if (countCalls) primOpCalls[name]++;
vCur.primOp->fun(*this, pos, args, vCur);
try {
vCur.primOp->fun(*this, noPos, args, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
nrArgs -= argsLeft; nrArgs -= argsLeft;
args += argsLeft; args += argsLeft;
@ -1713,9 +1598,20 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i) for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i]; vArgs[argsDone + i] = args[i];
auto name = primOp->primOp->name;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[primOp->primOp->name]++; if (countCalls) primOpCalls[name]++;
primOp->primOp->fun(*this, pos, vArgs, vCur);
try {
// TODO:
// 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin.
primOp->primOp->fun(*this, noPos, vArgs, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
nrArgs -= argsLeft; nrArgs -= argsLeft;
args += argsLeft; args += argsLeft;
@ -1728,14 +1624,18 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
heap-allocate a copy and use that instead. */ heap-allocate a copy and use that instead. */
Value * args2[] = {allocValue(), args[0]}; Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur; *args2[0] = vCur;
/* !!! Should we use the attr pos here? */ try {
callFunction(*functor->value, 2, args2, vCur, pos); callFunction(*functor->value, 2, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
nrArgs--; nrArgs--;
args++; args++;
} }
else else
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur); error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow<TypeError>();
} }
vRes = vCur; vRes = vCur;
@ -1799,13 +1699,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) {
throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') error(R"(cannot evaluate a function that has an argument without a value ('%1%')
Nix attempted to evaluate a function as a top level expression; in 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])
*fun.lambda.env, *fun.lambda.fun); .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
} }
} }
} }
@ -1828,16 +1727,17 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v)
{ {
(state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v); // We cheat in the parser, and pass the position of the condition as the position of the if itself.
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
} }
void ExprAssert::eval(EvalState & state, Env & env, Value & v) void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{ {
if (!state.evalBool(env, cond, pos)) { 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.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1845,7 +1745,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(!state.evalBool(env, e)); v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: !
} }
@ -1853,7 +1753,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(state.eqValues(v1, v2)); v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
} }
@ -1861,33 +1761,33 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(!state.eqValues(v1, v2)); v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
} }
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v) void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos)); v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
} }
void ExprOpOr::eval(EvalState & state, Env & env, Value & v) void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
} }
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v) void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
} }
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1, v2; Value v1, v2;
state.evalAttrs(env, e1, v1); state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2); state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.nrOpUpdates++; state.nrOpUpdates++;
@ -1926,18 +1826,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
Value * lists[2] = { &v1, &v2 }; Value * lists[2] = { &v1, &v2 };
state.concatLists(v, 2, lists, pos); state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate");
} }
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos) void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
{ {
nrListConcats++; nrListConcats++;
Value * nonEmpty = 0; Value * nonEmpty = 0;
size_t len = 0; size_t len = 0;
for (size_t n = 0; n < nrLists; ++n) { for (size_t n = 0; n < nrLists; ++n) {
forceList(*lists[n], pos); forceList(*lists[n], pos, errorCtx);
auto l = lists[n]->listSize(); auto l = lists[n]->listSize();
len += l; len += l;
if (l) nonEmpty = lists[n]; if (l) nonEmpty = lists[n];
@ -2014,20 +1914,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n; nf = n;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this); state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} 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.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this); state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} 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
canonized in the first place if it's coming from a ./${foo} type canonized in the first place if it's coming from a ./${foo} type
path */ path */
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first); auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
sSize += part->size(); sSize += part->size();
s.emplace_back(std::move(part)); s.emplace_back(std::move(part));
} }
@ -2041,7 +1941,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.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this); state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(canonPath(str())); v.mkPath(canonPath(str()));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -2091,33 +1991,47 @@ void EvalState::forceValueDeep(Value & v)
} }
NixInt EvalState::forceInt(Value & v, const PosIdx pos) NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nInt) if (v.type() != nInt)
throwTypeError(pos, "value is %1% while an integer was expected", v); error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
return v.integer; return v.integer;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos) NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() == nInt) if (v.type() == nInt)
return v.integer; return v.integer;
else if (v.type() != nFloat) else if (v.type() != nFloat)
throwTypeError(pos, "value is %1% while a float was expected", v); error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
return v.fpoint; return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
bool EvalState::forceBool(Value & v, const PosIdx pos) bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nBool) if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v); error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
return v.boolean; return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
@ -2127,21 +2041,30 @@ bool EvalState::isFunctor(Value & fun)
} }
void EvalState::forceFunction(Value & v, const PosIdx pos) void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v)) if (v.type() != nFunction && !isFunctor(v))
throwTypeError(pos, "value is %1% while a function was expected", v); error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
std::string_view EvalState::forceString(Value & v, const PosIdx pos) std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
try {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nString) { if (v.type() != nString)
throwTypeError(pos, "value is %1% while a string was expected", v); error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
}
return v.string.s; return v.string.s;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
@ -2185,24 +2108,19 @@ NixStringContext Value::getContext(const Store & store)
} }
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos) std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos, errorCtx);
copyContext(v, context); copyContext(v, context);
return s; return s;
} }
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos) std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos, errorCtx);
if (v.string.context) { if (v.string.context) {
if (pos) error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
else
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
} }
return s; return s;
} }
@ -2226,14 +2144,15 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
if (i != v.attrs->end()) { if (i != v.attrs->end()) {
Value v1; Value v1;
callFunction(*i->value, v, v1, pos); callFunction(*i->value, v, v1, pos);
return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned(); return coerceToString(pos, v1, context, coerceMore, copyToStore,
"while evaluating the result of the `toString` attribute").toOwned();
} }
return {}; return {};
} }
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context, BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore, bool copyToStore, bool canonicalizePath) bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -2257,12 +2176,12 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
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())
throwTypeError(pos, "cannot coerce a set to a string"); error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
return coerceToString(pos, *i->value, context, coerceMore, copyToStore); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
} }
if (v.type() == nExternal) if (v.type() == nExternal)
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
if (coerceMore) { if (coerceMore) {
/* Note that `false' is represented as an empty string for /* Note that `false' is represented as an empty string for
@ -2276,7 +2195,13 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (v.isList()) { if (v.isList()) {
std::string result; std::string result;
for (auto [n, v2] : enumerate(v.listItems())) { for (auto [n, v2] : enumerate(v.listItems())) {
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore); try {
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
"while evaluating one element of the list");
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
if (n < v.listSize() - 1 if (n < v.listSize() - 1
/* !!! not quite correct */ /* !!! not quite correct */
&& (!v2->isList() || v2->listSize() != 0)) && (!v2->isList() || v2->listSize() != 0))
@ -2286,14 +2211,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
} }
} }
throwTypeError(pos, "cannot coerce %1% to a string", v); error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
} }
std::string EvalState::copyPathToStore(PathSet & context, const Path & path) std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
{ {
if (nix::isDerivation(path)) if (nix::isDerivation(path))
throwEvalError("file names are not allowed to end in '%1%'", drvExtension); error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
Path dstPath; Path dstPath;
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
@ -2314,28 +2239,25 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
} }
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context) Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path; return path;
} }
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context) StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (auto storePath = store->maybeParseStorePath(path)) if (auto storePath = store->maybeParseStorePath(path))
return *storePath; return *storePath;
throw EvalError({ error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = positions[pos]
});
} }
bool EvalState::eqValues(Value & v1, Value & v2) bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v1, noPos); forceValue(v1, noPos);
forceValue(v2, noPos); forceValue(v2, noPos);
@ -2355,7 +2277,6 @@ bool EvalState::eqValues(Value & v1, Value & v2)
if (v1.type() != v2.type()) return false; if (v1.type() != v2.type()) return false;
switch (v1.type()) { switch (v1.type()) {
case nInt: case nInt:
return v1.integer == v2.integer; return v1.integer == v2.integer;
@ -2374,7 +2295,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
case nList: case nList:
if (v1.listSize() != v2.listSize()) return false; if (v1.listSize() != v2.listSize()) return false;
for (size_t n = 0; n < v1.listSize(); ++n) for (size_t n = 0; n < v1.listSize(); ++n)
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false;
return true; return true;
case nAttrs: { case nAttrs: {
@ -2384,7 +2305,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
Bindings::iterator i = v1.attrs->find(sOutPath); Bindings::iterator i = v1.attrs->find(sOutPath);
Bindings::iterator j = v2.attrs->find(sOutPath); Bindings::iterator j = v2.attrs->find(sOutPath);
if (i != v1.attrs->end() && j != v2.attrs->end()) if (i != v1.attrs->end() && j != v2.attrs->end())
return eqValues(*i->value, *j->value); return eqValues(*i->value, *j->value, pos, errorCtx);
} }
if (v1.attrs->size() != v2.attrs->size()) return false; if (v1.attrs->size() != v2.attrs->size()) return false;
@ -2392,7 +2313,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
/* Otherwise, compare the attributes one by one. */ /* Otherwise, compare the attributes one by one. */
Bindings::iterator i, j; Bindings::iterator i, j;
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
if (i->name != j->name || !eqValues(*i->value, *j->value)) if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
return false; return false;
return true; return true;
@ -2409,9 +2330,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return v1.fpoint == v2.fpoint; return v1.fpoint == v2.fpoint;
default: default:
throwEvalError("cannot compare %1% with %2%", error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
showType(v1),
showType(v2));
} }
} }
@ -2535,12 +2454,13 @@ void EvalState::printStats()
} }
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
{ {
throw TypeError({ auto e = TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType()), .msg = hintfmt("cannot coerce %1% to a string", showType())
.errPos = pos
}); });
e.addTrace(pos, errorCtx);
throw e;
} }

View file

@ -86,6 +86,43 @@ struct DebugTrace {
void debugError(Error * e, Env & env, Expr & expr); 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:
@ -145,29 +182,35 @@ public:
template<class E> template<class E>
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env & env, const Expr & expr) void debugThrowLastTrace(E && error)
{ {
if (debugRepl) debugThrow(error, nullptr, nullptr);
runDebugRepl(&error, env, expr);
throw std::move(error);
} }
template<class E> template<class E>
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && e) void debugThrow(E && error, const Env * env, const Expr * expr)
{ {
// Call this in the situation where Expr and Env are inaccessible. if (debugRepl && ((env && expr) || !debugTraces.empty())) {
// The debugger will start in the last context that's in the if (!env || !expr) {
// DebugTrace stack.
if (debugRepl && !debugTraces.empty()) {
const DebugTrace & last = debugTraces.front(); const DebugTrace & last = debugTraces.front();
runDebugRepl(&e, last.env, last.expr); env = &last.env;
expr = &last.expr;
}
runDebugRepl(&error, *env, *expr);
} }
throw std::move(e); throw std::move(error);
} }
ErrorBuilder * errorBuilder;
template<typename... Args>
[[nodiscard, gnu::noinline]]
ErrorBuilder & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...);
return *errorBuilder;
}
private: private:
SrcToStore srcToStore; SrcToStore srcToStore;
@ -282,8 +325,8 @@ public:
/* Evaluation the expression, then verify that it has the expected /* Evaluation the expression, then verify that it has the expected
type. */ type. */
inline bool evalBool(Env & env, Expr * e); inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos); inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v); inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
/* If `v' is a thunk, enter it and overwrite `v' with the result /* If `v' is a thunk, enter it and overwrite `v' with the result
of the evaluation of the thunk. If `v' is a delayed function of the evaluation of the thunk. If `v' is a delayed function
@ -299,89 +342,25 @@ public:
void forceValueDeep(Value & v); void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */ /* Force `v', and then verify that it has the expected type. */
NixInt forceInt(Value & v, const PosIdx pos); NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
NixFloat forceFloat(Value & v, const PosIdx pos); NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
bool forceBool(Value & v, const PosIdx pos); bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceAttrs(Value & v, const PosIdx pos); void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
template <typename Callable> template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos); inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
inline void forceList(Value & v, const PosIdx pos); inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceFunction(Value & v, const PosIdx pos); // either lambda or primop void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
std::string_view forceString(Value & v, const PosIdx pos = noPos); std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos); std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const char * s, const std::string & s2) const; void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const;
public: public:
/* Return true iff the value `v' denotes a derivation (i.e. a /* Return true iff the value `v' denotes a derivation (i.e. a
@ -397,17 +376,18 @@ public:
referenced paths are copied to the Nix store as a side effect. */ referenced paths are copied to the Nix store as a side effect. */
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context, BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true, bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true,
std::string_view errorCtx = "");
std::string copyPathToStore(PathSet & context, const Path & path); std::string copyPathToStore(PathSet & context, const Path & path);
/* Path coercion. Converts strings, paths and derivations to a /* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */ path. Nothing is copied to the store. */
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context); Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
/* Like coerceToPath, but the result must be a store path. */ /* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context); StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
public: public:
@ -467,7 +447,7 @@ public:
/* Do a deep equality test between two values. That is, list /* Do a deep equality test between two values. That is, list
elements and attributes are compared recursively. */ elements and attributes are compared recursively. */
bool eqValues(Value & v1, Value & v2); bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
bool isFunctor(Value & fun); bool isFunctor(Value & fun);
@ -502,7 +482,7 @@ public:
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos); void mkPos(Value & v, PosIdx pos);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos); void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/* Print statistics. */ /* Print statistics. */
void printStats(); void printStats();
@ -669,6 +649,13 @@ extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"}; static const std::string corepkgsPrefix{"/__corepkgs__/"};
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

@ -259,28 +259,28 @@ static Flake getFlake(
if (setting.value->type() == nString) if (setting.value->type() == nString)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos))); std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) { else if (setting.value->type() == nPath) {
PathSet emptyContext = {}; PathSet emptyContext = {};
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
} }
else if (setting.value->type() == nInt) else if (setting.value->type() == nInt)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
state.forceInt(*setting.value, setting.pos)); state.forceInt(*setting.value, setting.pos, ""));
else if (setting.value->type() == nBool) else if (setting.value->type() == nBool)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos) }); Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) { else if (setting.value->type() == nList) {
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", throw 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));
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);
} }
@ -741,7 +741,7 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked()) if (evalSettings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]); throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);

View file

@ -51,7 +51,7 @@ std::string DrvInfo::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()) throw TypeError("derivation name missing");
name = state->forceStringNoCtx(*i->value); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
} }
return name; return name;
} }
@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
{ {
if (system == "" && attrs) { if (system == "" && attrs) {
auto i = attrs->find(state->sSystem); auto i = attrs->find(state->sSystem);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos); system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
} }
return system; return system;
} }
@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
if (i == attrs->end()) if (i == attrs->end())
drvPath = {std::nullopt}; drvPath = {std::nullopt};
else else
drvPath = {state->coerceToStorePath(i->pos, *i->value, context)}; drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
} }
return drvPath.value_or(std::nullopt); return drvPath.value_or(std::nullopt);
} }
@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
Bindings::iterator i = attrs->find(state->sOutPath); Bindings::iterator i = attrs->find(state->sOutPath);
PathSet context; PathSet context;
if (i != attrs->end()) if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context); outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
} }
if (!outPath) if (!outPath)
throw UnimplementedError("CA derivations are not yet supported"); throw UnimplementedError("CA derivations are not yet supported");
@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Get the outputs list. */ /* Get the outputs list. */
Bindings::iterator i; Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
state->forceList(*i->value, i->pos); state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, i->pos)); std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
if (withPaths) { if (withPaths) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output)); Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error? if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value, i->pos); state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* And evaluate its outPath attribute. */ /* And evaluate its outPath attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context; PathSet context;
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context)); outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else } else
outputs.emplace(output, std::nullopt); outputs.emplace(output, std::nullopt);
} }
@ -137,7 +137,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
return outputs; return outputs;
Bindings::iterator i; Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) { if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
Outputs result; Outputs result;
auto out = outputs.find(queryOutputName()); auto out = outputs.find(queryOutputName());
if (out == outputs.end()) if (out == outputs.end())
@ -169,7 +169,7 @@ std::string DrvInfo::queryOutputName() const
{ {
if (outputName == "" && attrs) { if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->sOutputName); Bindings::iterator i = attrs->find(state->sOutputName);
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : ""; outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
} }
return outputName; return outputName;
} }
@ -181,7 +181,7 @@ Bindings * DrvInfo::getMeta()
if (!attrs) return 0; if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta); Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0; if (a == attrs->end()) return 0;
state->forceAttrs(*a->value, a->pos); state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs; meta = a->value->attrs;
return meta; return meta;
} }
@ -382,7 +382,7 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */ `recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) { if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos)) if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
} }
} }

View file

@ -8,7 +8,6 @@
#include "error.hh" #include "error.hh"
#include "chunked-vector.hh" #include "chunked-vector.hh"
namespace nix { namespace nix {

View file

@ -400,21 +400,21 @@ expr_op
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); } | '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
| expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); } | expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); } | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); } | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); } | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); } | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); } | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op | expr_op '+' expr_op
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *>>({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } { $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } | expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } | expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } | expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); } | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); }
| expr_app | expr_app
; ;
@ -782,13 +782,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4)); return concatStrings(corepkgsPrefix, path.substr(4));
debugThrowLastTrace(ThrownError({ debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(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] .errPos = positions[pos]
})); }), 0, 0);
} }

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s); v.mkString(*s);
} }
@ -18,7 +18,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
state.forceString(*args[0], context, pos); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
v.mkBool(!context.empty()); v.mkBool(!context.empty());
} }
@ -34,7 +34,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
PathSet context2; PathSet context2;
for (auto & p : context) for (auto & p : context)
@ -73,7 +73,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
Strings outputs; Strings outputs;
}; };
PathSet context; PathSet context;
state.forceString(*args[0], context, pos); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<Path, ContextInfo>(); auto contextInfos = std::map<Path, ContextInfo>();
for (const auto & p : context) { for (const auto & p : context) {
Path drv; Path drv;
@ -137,9 +137,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto orig = state.forceString(*args[0], context, pos); auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
state.forceAttrs(*args[1], pos); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
auto sPath = state.symbols.create("path"); auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs"); auto sAllOutputs = state.symbols.create("allOutputs");
@ -147,24 +147,24 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
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({ throw EvalError({
.msg = hintfmt("Context key '%s' is not a store path", name), .msg = hintfmt("context key '%s' is not a store path", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(state.store->parseStorePath(name)); state.store->ensurePath(state.store->parseStorePath(name));
state.forceAttrs(*i.value, i.pos); state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(sPath); auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos)) if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(name); context.emplace(name);
} }
iter = i.value->attrs->find(sAllOutputs); iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos)) { if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) { if (!isDerivation(name)) {
throw EvalError({ throw EvalError({
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name), .msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
} }
@ -174,15 +174,15 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
iter = i.value->attrs->find(state.sOutputs); iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos); 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({ throw EvalError({
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name), .msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
} }
for (auto elem : iter->value->listItems()) { for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos); auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.insert(concatStrings("!", outputName, "!", name)); context.insert(concatStrings("!", outputName, "!", name));
} }
} }

View file

@ -7,7 +7,7 @@ namespace nix {
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl; std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath; std::optional<StorePath> fromPath;
@ -19,7 +19,8 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
if (attrName == "fromPath") { if (attrName == "fromPath") {
PathSet context; PathSet context;
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context); fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
} }
else if (attrName == "toPath") { else if (attrName == "toPath") {
@ -27,12 +28,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
toCA = true; toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) { if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context; PathSet context;
toPath = state.coerceToStorePath(attr.pos, *attr.value, context); toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
} }
} }
else if (attrName == "fromStore") else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos); fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
"while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
else else
throw Error({ throw Error({

View file

@ -19,23 +19,21 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
else if (n == "rev") { else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can // Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name. // be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(*attr.value, attr.pos); auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex)) if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1); rev = Hash::parseAny(value, htSHA1);
else else
ref = value; ref = value;
} }
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else else
throw EvalError({ throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]), .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
@ -50,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
}); });
} else } else
url = state.coerceToString(pos, *args[0], context, false, false).toOwned(); url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
// FIXME: git externals probably can be used to bypass the URI // FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well. // whitelist. Ah well.

View file

@ -102,7 +102,7 @@ static void fetchTree(
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -112,7 +112,7 @@ static void fetchTree(
.msg = hintfmt("unexpected attribute 'type'"), .msg = hintfmt("unexpected attribute 'type'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
type = state.forceStringNoCtx(*aType->value, aType->pos); 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.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
@ -125,7 +125,7 @@ static void fetchTree(
if (attr.name == state.sType) continue; if (attr.name == state.sType) continue;
state.forceValue(*attr.value, attr.pos); state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) { if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
attrs.emplace(state.symbols[attr.name], attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url" state.symbols[attr.name] == "url"
? type == "git" ? type == "git"
@ -151,7 +151,7 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned(); auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
if (type == "git") { if (type == "git") {
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -195,16 +195,14 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos); url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else else
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who), .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
@ -218,7 +216,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
} else } else
url = state.forceStringNoCtx(*args[0], pos); url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
state.checkURI(*url); state.checkURI(*url);

View file

@ -7,7 +7,7 @@ namespace nix {
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val) static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
{ {
auto toml = state.forceStringNoCtx(*args[0], pos); auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
std::istringstream tomlStream(std::string{toml}); std::istringstream tomlStream(std::string{toml});

View file

@ -0,0 +1,94 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libexprtests.hh"
namespace nix {
using namespace testing;
// Testing eval of PrimOp's
class ErrorTraceTest : public LibExprTest { };
#define ASSERT_TRACE1(args, type, message) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
#define ASSERT_TRACE2(args, type, message, context) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(context)); \
++trace; \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
TEST_F(ErrorTraceTest, genericClosure) { \
ASSERT_TRACE2("genericClosure 1",
TypeError,
hintfmt("value is %s while a set was expected", "an integer"),
hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
ASSERT_TRACE1("genericClosure {}",
TypeError,
hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = 1; }",
TypeError,
hintfmt("value is %s while a list was expected", "an integer"),
hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
// Okay: "genericClosure { startSet = []; }"
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
TypeError,
hintfmt("value is %s while a function was expected", "a Boolean"),
hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
TypeError,
hintfmt("value is %s while a list was expected", "a Boolean"),
hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
TypeError,
hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
EvalError,
hintfmt("cannot compare %s with %s", "a string", "an integer"),
hintfmt("while comparing the `key` attributes of two genericClosure elements"));
ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
}
} /* namespace nix */

View file

@ -823,4 +823,10 @@ namespace nix {
for (const auto [n, elem] : enumerate(v.listItems())) for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n])); ASSERT_THAT(*elem, IsStringEq(expected[n]));
} }
TEST_F(PrimOpTest, genericClosure_not_strict) {
// Operator should not be used when startSet is empty
auto v = eval("builtins.genericClosure { startSet = []; }");
ASSERT_THAT(v, IsListOfSize(0));
}
} /* namespace nix */ } /* namespace nix */

View file

@ -90,7 +90,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, PathSet & context, bool copyMore, bool copyToStore) const; virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
/* Compare to another value of the same type. Defaults to uncomparable, /* Compare to another value of the same type. Defaults to uncomparable,
* i.e. always false. * i.e. always false.

View file

@ -403,8 +403,6 @@ 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());
if (e.hasTrace() && !loggerSettings.showTrace.get())
printError("(use '--show-trace' to show detailed location information)");
return e.status; return e.status;
} catch (std::bad_alloc & e) { } catch (std::bad_alloc & e) {
printError(error + "out of memory"); printError(error + "out of memory");

View file

@ -9,9 +9,9 @@ namespace nix {
const std::string nativeSystem = SYSTEM; const std::string nativeSystem = SYSTEM;
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint) void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame)
{ {
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint }); err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
} }
// c++ std::exception descendants must have a 'const char* what()' function. // c++ std::exception descendants must have a 'const char* what()' function.
@ -200,13 +200,125 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n"; auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
// traces /*
if (showTrace && !einfo.traces.empty()) { * Traces
* ------
*
* The semantics of traces is a bit weird. We have only one option to
* print them and to make them verbose (--show-trace). In the code they
* are always collected, but they are not printed by default. The code
* also collects more traces when the option is on. This means that there
* is no way to print the simplified traces at all.
*
* I (layus) designed the code to attach positions to a restricted set of
* messages. This means that we have a lot of traces with no position at
* all, including most of the base error messages. For example "type
* error: found a string while a set was expected" has no position, but
* will come with several traces detailing it's precise relation to the
* closest know position. This makes erroring without printing traces
* quite useless.
*
* This is why I introduced the idea to always print a few traces on
* error. The number 3 is quite arbitrary, and was selected so as not to
* clutter the console on error. For the same reason, a trace with an
* error position takes more space, and counts as two traces towards the
* limit.
*
* The rest is truncated, unless --show-trace is passed. This preserves
* the same bad semantics of --show-trace to both show the trace and
* augment it with new data. Not too sure what is the best course of
* action.
*
* The issue is that it is fundamentally hard to provide a trace for a
* lazy language. The trace will only cover the current spine of the
* evaluation, missing things that have been evaluated before. For
* example, most type errors are hard to inspect because there is not
* trace for the faulty value. These errors should really print the faulty
* value itself.
*
* In function calls, the --show-trace flag triggers extra traces for each
* function invocation. These work as scopes, allowing to follow the
* current spine of the evaluation graph. Without that flag, the error
* trace should restrict itself to a restricted prefix of that trace,
* until the first scope. If we ever get to such a precise error
* reporting, there would be no need to add an arbitrary limit here. We
* could always print the full trace, and it would just be small without
* the flag.
*
* One idea I had is for XxxError.addTrace() to perform nothing if one
* scope has already been traced. Alternatively, we could stop here when
* we encounter such a scope instead of after an arbitrary number of
* traces. This however requires to augment traces with the notion of
* "scope".
*
* This is particularly visible in code like evalAttrs(...) where we have
* to make a decision between the two following options.
*
* ``` long traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* try {
* e->eval(*this, env, v);
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* ``` short traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* e->eval(*this, env, v);
* try {
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* The second example can be rewritten more concisely, but kept in this
* form to highlight the symmetry. The first option adds more information,
* because whatever caused an error down the line, in the generic eval
* function, will get annotated with the code location that uses and
* required it. The second option is less verbose, but does not provide
* any context at all as to where and why a failing value was required.
*
* Scopes would fix that, by adding context only when --show-trace is
* passed, and keeping the trace terse otherwise.
*
*/
// Enough indent to align with with the `... `
// prepended to each element of the trace
auto ellipsisIndent = " ";
bool frameOnly = false;
if (!einfo.traces.empty()) {
size_t count = 0;
for (const auto & trace : einfo.traces) { for (const auto & trace : einfo.traces) {
if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
break;
}
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
count++;
frameOnly = trace.frame;
oss << "\n" << "" << trace.hint.str() << "\n"; oss << "\n" << "" << trace.hint.str() << "\n";
if (trace.pos) { if (trace.pos) {
oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":"; count++;
oss << "\n" << ellipsisIndent << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
if (auto loc = trace.pos->getCodeLines()) { if (auto loc = trace.pos->getCodeLines()) {
oss << "\n"; oss << "\n";

View file

@ -86,6 +86,7 @@ void printCodeLines(std::ostream & out,
struct Trace { struct Trace {
std::shared_ptr<AbstractPos> pos; std::shared_ptr<AbstractPos> pos;
hintformat hint; hintformat hint;
bool frame;
}; };
struct ErrorInfo { struct ErrorInfo {
@ -114,6 +115,8 @@ protected:
public: public:
unsigned int status = 1; // exit status unsigned int status = 1; // exit status
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...) }
@ -152,15 +155,22 @@ 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; }
template<typename... Args> void pushTrace(Trace trace)
void addTrace(std::shared_ptr<AbstractPos> && e, const std::string & fs, const Args & ... args)
{ {
addTrace(std::move(e), hintfmt(fs, args...)); err.traces.push_front(trace);
} }
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint); template<typename... Args>
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args)
{
addTrace(std::move(e), hintfmt(std::string(fs), args...));
}
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false);
bool hasTrace() const { return !err.traces.empty(); } bool hasTrace() const { return !err.traces.empty(); }
const ErrorInfo & info() { return err; };
}; };
#define MakeError(newClass, superClass) \ #define MakeError(newClass, superClass) \

View file

@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
PathSet context; PathSet context;
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context); auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context); auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
/* Realise the resulting store expression. */ /* Realise the resulting store expression. */
debug("building user environment"); debug("building user environment");

View file

@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2; PathSet context2;
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2); auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto attr2 = vRes->attrs->get(evalState->sOutPath); auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2) if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2); auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
store->buildPaths({ DerivedPath::Built { drvPath } }); store->buildPaths({ DerivedPath::Built { drvPath } });
@ -113,7 +113,7 @@ struct CmdBundle : InstallableCommand
auto * attr = vRes->attrs->get(evalState->sName); auto * attr = vRes->attrs->get(evalState->sName);
if (!attr) if (!attr)
throw Error("attribute 'name' missing"); throw Error("attribute 'name' missing");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos); outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
} }
// TODO: will crash if not a localFSStore? // TODO: will crash if not a localFSStore?

View file

@ -112,7 +112,7 @@ struct CmdEval : MixJSON, InstallableCommand
else if (raw) { else if (raw) {
stopProgressBar(); stopProgressBar();
std::cout << *state->coerceToString(noPos, *v, context); std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output");
} }
else if (json) { else if (json) {

View file

@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback) std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
{ {
auto pos = vFlake.determinePos(noPos); auto pos = vFlake.determinePos(noPos);
state.forceAttrs(vFlake, pos); state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
state.forceAttrs(*aOutputs->value, pos); state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
auto sHydraJobs = state.symbols.create("hydraJobs"); auto sHydraJobs = state.symbols.create("hydraJobs");
@ -408,13 +408,13 @@ struct CmdFlakeCheck : FlakeCommand
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) { checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try { try {
state->forceAttrs(v, pos); state->forceAttrs(v, pos, "");
if (state->isDerivation(v)) if (state->isDerivation(v))
throw Error("jobset should not be a derivation at top-level"); throw Error("jobset should not be a derivation at top-level");
for (auto & attr : *v.attrs) { for (auto & attr : *v.attrs) {
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]); auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
if (state->isDerivation(*attr.value)) { if (state->isDerivation(*attr.value)) {
Activity act(*logger, lvlChatty, actUnknown, Activity act(*logger, lvlChatty, actUnknown,
@ -436,7 +436,7 @@ struct CmdFlakeCheck : FlakeCommand
fmt("checking NixOS configuration '%s'", attrPath)); fmt("checking NixOS configuration '%s'", attrPath));
Bindings & bindings(*state->allocBindings(0)); Bindings & bindings(*state->allocBindings(0));
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first; auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
state->forceAttrs(*vToplevel, pos); state->forceValue(*vToplevel, pos);
if (!state->isDerivation(*vToplevel)) if (!state->isDerivation(*vToplevel))
throw Error("attribute 'config.system.build.toplevel' is not a derivation"); throw Error("attribute 'config.system.build.toplevel' is not a derivation");
} catch (Error & e) { } catch (Error & e) {
@ -450,12 +450,12 @@ struct CmdFlakeCheck : FlakeCommand
Activity act(*logger, lvlChatty, actUnknown, Activity act(*logger, lvlChatty, actUnknown,
fmt("checking template '%s'", attrPath)); fmt("checking template '%s'", attrPath));
state->forceAttrs(v, pos); state->forceAttrs(v, pos, "");
if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) { if (attr->name == state->symbols.create("path")) {
PathSet context; PathSet context;
auto path = state->coerceToPath(attr->pos, *attr->value, context); auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
if (!store->isInStore(path)) if (!store->isInStore(path))
throw Error("template '%s' has a bad 'path' attribute"); throw Error("template '%s' has a bad 'path' attribute");
// TODO: recursively check the flake in 'path'. // TODO: recursively check the flake in 'path'.
@ -464,7 +464,7 @@ struct CmdFlakeCheck : FlakeCommand
throw Error("template '%s' lacks attribute 'path'", attrPath); throw Error("template '%s' lacks attribute 'path'", attrPath);
if (auto attr = v.attrs->get(state->symbols.create("description"))) if (auto attr = v.attrs->get(state->symbols.create("description")))
state->forceStringNoCtx(*attr->value, attr->pos); state->forceStringNoCtx(*attr->value, attr->pos, "");
else else
throw Error("template '%s' lacks attribute 'description'", attrPath); throw Error("template '%s' lacks attribute 'description'", attrPath);
@ -521,11 +521,11 @@ struct CmdFlakeCheck : FlakeCommand
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement); warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
if (name == "checks") { if (name == "checks") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) { for (auto & attr2 : *attr.value->attrs) {
auto drvPath = checkDerivation( auto drvPath = checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@ -537,7 +537,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "formatter") { else if (name == "formatter") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -548,11 +548,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "packages" || name == "devShells") { else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) for (auto & attr2 : *attr.value->attrs)
checkDerivation( checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@ -561,11 +561,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "apps") { else if (name == "apps") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) for (auto & attr2 : *attr.value->attrs)
checkApp( checkApp(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@ -574,7 +574,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "defaultPackage" || name == "devShell") { else if (name == "defaultPackage" || name == "devShell") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -585,7 +585,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "defaultApp") { else if (name == "defaultApp") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -596,7 +596,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "legacyPackages") { else if (name == "legacyPackages") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
checkSystemName(state->symbols[attr.name], attr.pos); checkSystemName(state->symbols[attr.name], attr.pos);
// FIXME: do getDerivations? // FIXME: do getDerivations?
@ -607,7 +607,7 @@ struct CmdFlakeCheck : FlakeCommand
checkOverlay(name, vOutput, pos); checkOverlay(name, vOutput, pos);
else if (name == "overlays") { else if (name == "overlays") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]), checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
@ -617,14 +617,14 @@ struct CmdFlakeCheck : FlakeCommand
checkModule(name, vOutput, pos); checkModule(name, vOutput, pos);
else if (name == "nixosModules") { else if (name == "nixosModules") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkModule(fmt("%s.%s", name, state->symbols[attr.name]), checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
} }
else if (name == "nixosConfigurations") { else if (name == "nixosConfigurations") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]), checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
@ -637,14 +637,14 @@ struct CmdFlakeCheck : FlakeCommand
checkTemplate(name, vOutput, pos); checkTemplate(name, vOutput, pos);
else if (name == "templates") { else if (name == "templates") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]), checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
} }
else if (name == "defaultBundler") { else if (name == "defaultBundler") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -655,11 +655,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "bundlers") { else if (name == "bundlers") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) { for (auto & attr2 : *attr.value->attrs) {
checkBundler( checkBundler(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),

View file

@ -199,7 +199,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
if (!attr) if (!attr)
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
auto markdown = state.forceString(*attr->value); auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text");
RunPager pager; RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n"; std::cout << renderMarkdownToTerminal(markdown) << "\n";

View file

@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors; Value vMirrors;
// FIXME: use nixpkgs flake // FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors); state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
state.forceAttrs(vMirrors, noPos); state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end()) if (mirrorList == vMirrors.attrs->end())
throw Error("unknown mirror name '%s'", mirrorName); throw Error("unknown mirror name '%s'", mirrorName);
state.forceList(*mirrorList->value, noPos); state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
if (mirrorList->value->listSize() < 1) if (mirrorList->value->listSize() < 1)
throw Error("mirror URL '%s' did not expand to anything", url); throw Error("mirror URL '%s' did not expand to anything", url);
std::string mirror(state.forceString(*mirrorList->value->listElems()[0])); std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
} }
@ -196,29 +196,29 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Value vRoot; Value vRoot;
state->evalFile(path, vRoot); state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos); state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
/* Extract the URL. */ /* Extract the URL. */
auto * attr = v.attrs->get(state->symbols.create("urls")); auto * attr = v.attrs->get(state->symbols.create("urls"));
if (!attr) if (!attr)
throw Error("attribute 'urls' missing"); throw Error("attribute 'urls' missing");
state->forceList(*attr->value, noPos); state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
if (attr->value->listSize() < 1) if (attr->value->listSize() < 1)
throw Error("'urls' list is empty"); throw Error("'urls' list is empty");
url = state->forceString(*attr->value->listElems()[0]); url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
/* Extract the hash mode. */ /* Extract the hash mode. */
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
if (!attr2) if (!attr2)
printInfo("warning: this does not look like a fetchurl call"); printInfo("warning: this does not look like a fetchurl call");
else else
unpack = state->forceString(*attr2->value) == "recursive"; unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
/* Extract the name. */ /* Extract the name. */
if (!name) { if (!name) {
auto attr3 = v.attrs->get(state->symbols.create("name")); auto attr3 = v.attrs->get(state->symbols.create("name"));
if (!attr3) if (!attr3)
name = state->forceString(*attr3->value); name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
} }
} }

View file

@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
Bindings & bindings(*state->allocBindings(0)); Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
return store->parseStorePath(state->forceString(*v2)); return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
} }
}; };