libexpr: mark eval error handling NeverAsync

technically it doesn't *have* to be NeverAsync, but not marking it as
such unconditionally requires templating DebugState over asyncness of
its callback (which then requires templating EvalState, which, *NO*.)

Change-Id: I4980d45b541c2e40328beac139b18c6c1ba0957c
This commit is contained in:
eldritch horrors
2025-03-25 21:26:47 +01:00
parent 8a90195cdb
commit fc58e7b217
12 changed files with 55 additions and 35 deletions
+2 -1
View File
@@ -2,10 +2,11 @@
///@file
#include "lix/libexpr/eval.hh"
#include "lix/libutil/types.hh"
namespace nix {
struct AbstractNixRepl
struct AbstractNixRepl : NeverAsync
{
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
+2 -1
View File
@@ -1,6 +1,7 @@
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/value.hh"
#include "lix/libutil/types.hh"
namespace nix {
@@ -72,7 +73,7 @@ EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const A
}
template<class T>
void EvalErrorBuilder<T>::debugThrow() &&
void EvalErrorBuilder<T>::debugThrow(NeverAsync) &&
{
if (debug) {
if (auto last = debug->traces().next()) {
+1 -1
View File
@@ -91,7 +91,7 @@ public:
/**
* Throw the underlying exception, invoking the debug state callback.
*/
[[gnu::noinline, gnu::noreturn]] void debugThrow() &&;
[[gnu::noinline, gnu::noreturn]] void debugThrow(NeverAsync = {}) &&;
/**
* Throw the underlying exception, bypassing the debug state callback.
+9 -6
View File
@@ -1,5 +1,6 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-settings.hh"
#include "lix/libstore/path.hh"
#include "lix/libutil/archive.hh"
#include "lix/libutil/ansicolor.hh"
#include "lix/libutil/async.hh"
@@ -337,7 +338,7 @@ Evaluator::Evaluator(
debugRepl ? std::make_unique<DebugState>(
positions,
symbols,
[this, debugRepl](const ValMap & extraEnv) {
[this, debugRepl](const ValMap & extraEnv, NeverAsync) {
return activeEval
? debugRepl(*activeEval, extraEnv)
: ReplExitStatus::Continue;
@@ -731,7 +732,9 @@ public:
}
};
void DebugState::onEvalError(const EvalError * error, const Env & env, const Expr & expr)
void DebugState::onEvalError(
const EvalError * error, const Env & env, const Expr & expr, NeverAsync
)
{
// Make sure we have a debugger to run and we're not already in a debugger.
if (inDebugger)
@@ -765,7 +768,7 @@ void DebugState::onEvalError(const EvalError * error, const Env & env, const Exp
if (se) {
auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
DebuggerGuard _guard(inDebugger);
auto exitStatus = errorCallback(*vm);
auto exitStatus = errorCallback(*vm, {});
switch (exitStatus) {
case ReplExitStatus::QuitAll:
if (error)
@@ -2458,7 +2461,7 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
ctx.errors.make<EvalError>(
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time",
s).withTrace(pos, errorCtx).debugThrow();
s).withTrace(pos, errorCtx).debugThrow(always_progresses);
},
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b);
@@ -2484,13 +2487,13 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
ctx.errors.make<EvalError>(
"path string '%s' has context with the different path '%s'",
s, sExpected)
.withTrace(pos, errorCtx).debugThrow();
.withTrace(pos, errorCtx).debugThrow(always_progresses);
},
[&](const SingleDerivedPath::Built & b) {
ctx.errors.make<EvalError>(
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
s, b.output, b.drvPath->to_string(*ctx.store), sExpected)
.withTrace(pos, errorCtx).debugThrow();
.withTrace(pos, errorCtx).debugThrow(always_progresses);
}
}, derivedPath.raw());
}
+5 -4
View File
@@ -7,6 +7,7 @@
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/generator.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/source-path.hh"
#include "lix/libutil/types.hh"
#include "lix/libexpr/value.hh"
#include "lix/libexpr/nixexpr.hh"
@@ -149,7 +150,7 @@ private:
const SymbolTable & symbols;
public:
std::function<ReplExitStatus(ValMap const & extraEnv)> errorCallback;
std::function<ReplExitStatus(ValMap const & extraEnv, NeverAsync)> errorCallback;
bool stop = false;
bool inDebugger = false;
std::map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
@@ -158,7 +159,7 @@ public:
explicit DebugState(
const PosTable & positions,
const SymbolTable & symbols,
std::function<ReplExitStatus(ValMap const & extraEnv)> errorCallback
std::function<ReplExitStatus(ValMap const & extraEnv, NeverAsync)> errorCallback
)
: positions(positions)
, symbols(symbols)
@@ -167,7 +168,7 @@ public:
assert(errorCallback);
}
void onEvalError(const EvalError * error, const Env & env, const Expr & expr);
void onEvalError(const EvalError * error, const Env & env, const Expr & expr, NeverAsync = {});
const std::shared_ptr<const StaticEnv> staticEnvFor(const Expr & expr) const
{
@@ -469,7 +470,7 @@ public:
overloaded{
[](T & p) -> T { return std::move(p); },
[](EvalErrorBuilder<E> & e) -> T {
std::move(e).debugThrow();
std::move(e).debugThrow(always_progresses);
}
},
static_cast<std::variant<T, EvalErrorBuilder<E>> &>(*this)
+5 -4
View File
@@ -19,6 +19,7 @@
#include "lix/libexpr/primops.hh"
#include "lix/libfetchers/fetch-to-store.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/types.hh"
#include <boost/container/small_vector.hpp>
#include <kj/async.h>
@@ -50,7 +51,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & c : context) {
auto ensureValid = [&](const StorePath & p) {
if (!aio.blockOn(ctx.store->isValidPath(p)))
ctx.errors.make<InvalidPathError>(ctx.store->printStorePath(p)).debugThrow();
ctx.errors.make<InvalidPathError>(ctx.store->printStorePath(p)).debugThrow(always_progresses);
};
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
@@ -432,7 +433,7 @@ template<typename Callable>
}
}
struct CompareValues
struct CompareValues : NeverAsync
{
EvalState & state;
const PosIdx pos;
@@ -827,7 +828,7 @@ drvName, Bindings * attrs, Value & v)
const std::string & key = state.ctx.symbols[i->name];
vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string_view s) {
auto handleHashMode = [&](const std::string_view s, NeverAsync = {}) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else if (s == "text") {
@@ -839,7 +840,7 @@ drvName, Bindings * attrs, Value & v)
).atPos(v).debugThrow();
};
auto handleOutputs = [&](const Strings & ss) {
auto handleOutputs = [&](const Strings & ss, NeverAsync = {}) {
outputs.clear();
for (auto & j : ss) {
if (outputs.find(j) != outputs.end())
+3 -2
View File
@@ -3,6 +3,7 @@
#include "lix/libexpr/extra-primops.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/types.hh"
namespace nix {
@@ -69,7 +70,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
state.ctx.errors.make<EvalError>(
"path '%s' is not a derivation",
state.ctx.store->printStorePath(c.path)
).atPos(pos).debugThrow();
).atPos(pos).debugThrow(always_progresses);
}
return NixStringContextElem::DrvDeep {
.drvPath = c.path,
@@ -79,7 +80,7 @@ void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value *
state.ctx.errors.make<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output
).atPos(pos).debugThrow();
).atPos(pos).debugThrow(always_progresses);
},
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */
+5 -4
View File
@@ -7,6 +7,7 @@
#include "lix/libexpr/eval-inline.hh"
#include "lix/libexpr/value-to-json.hh"
#include "eval.hh"
#include "lix/libutil/types.hh"
namespace nix {
@@ -80,9 +81,9 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
if (pathExists(*writeTo))
throw Error("path '%s' already exists", *writeTo);
std::function<void(Value & v, const PosIdx pos, const Path & path)> recurse;
std::function<void(Value & v, const PosIdx pos, const Path & path, NeverAsync)> recurse;
recurse = [&](Value & v, const PosIdx pos, const Path & path)
recurse = [&](Value & v, const PosIdx pos, const Path & path, NeverAsync)
{
state->forceValue(v, pos);
if (v.type() == nString)
@@ -96,7 +97,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
try {
if (name == "." || name == "..")
throw Error("invalid file name '%s'", name);
recurse(*attr.value, attr.pos, concatStrings(path, "/", name));
recurse(*attr.value, attr.pos, concatStrings(path, "/", name), {});
} catch (Error & e) {
e.addTrace(
evaluator->positions[attr.pos],
@@ -109,7 +110,7 @@ struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption
evaluator->errors.make<TypeError>("value at '%s' is not a string or an attribute set", evaluator->positions[pos]).debugThrow();
};
recurse(*v, pos, *writeTo);
recurse(*v, pos, *writeTo, {});
}
else if (raw) {
+9 -6
View File
@@ -19,6 +19,7 @@
#include "lix/libutil/terminal.hh"
#include "lix/libutil/signals.hh"
#include "flake.hh"
#include "lix/libutil/types.hh"
#include <limits>
#include <iomanip>
@@ -1194,13 +1195,15 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
eval_cache::AttrCursor & visitor,
const std::vector<std::string> & attrPath,
const std::string & headerPrefix,
const std::string & nextPrefix)> visit;
const std::string & nextPrefix,
NeverAsync)> visit;
visit = [&](
eval_cache::AttrCursor & visitor,
const std::vector<std::string> & attrPath,
const std::string & headerPrefix,
const std::string & nextPrefix)
const std::string & nextPrefix,
NeverAsync)
-> JSON
{
auto j = JSON::object();
@@ -1209,7 +1212,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
fmt("evaluating '%s'", concatStringsSep(".", attrPath)));
try {
auto recurse = [&]()
auto recurse = [&](NeverAsync = {})
{
if (!json)
logger->cout("%s", headerPrefix);
@@ -1226,12 +1229,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
attrPath2.push_back(attr);
auto j2 = visit(*visitor2, attrPath2,
fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr),
nextPrefix + (last ? treeNull : treeLine));
nextPrefix + (last ? treeNull : treeLine), {});
if (json) j.emplace(attr, std::move(j2));
}
};
auto showDerivation = [&]()
auto showDerivation = [&](NeverAsync = {})
{
auto name = visitor.getAttr(*state, "name")->getString(*state);
std::optional<std::string> description;
@@ -1410,7 +1413,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto cache = openEvalCache(*evaluator, flake);
auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "", {});
if (json)
logger->cout("%s", j.dump());
}
+1 -1
View File
@@ -89,7 +89,7 @@ readConstituents(const nix::Value *v, nix::box_ptr<nix::EvalState> &state,
state->ctx.errors
.make<nix::EvalError>("derivation must have a constituents "
"attribute")
.debugThrow();
.debugThrow(nix::always_progresses); // we can't have a debugger here
nix::NixStringContext context;
state->coerceToString(a->pos, *a->value, context,
+1
View File
@@ -10,6 +10,7 @@
#include "lix/libstore/filetransfer.hh"
#include "lix/libmain/shared.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/types.hh"
constexpr std::string_view INVALID_CHANNEL = "channel:example";
constexpr std::string_view CHANNEL_URL = "https://nixos.org/channels/example/nixexprs.tar.xz";
+12 -5
View File
@@ -1,6 +1,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "lix/libutil/types.hh"
#include "tests/libexpr.hh"
namespace nix {
@@ -12,19 +13,23 @@ namespace nix {
TEST_F(ErrorTraceTest, TraceBuilder) {
ASSERT_THROW(
evaluator.errors.make<EvalError>("puppy").debugThrow(),
evaluator.errors.make<EvalError>("puppy").debugThrow(always_progresses),
EvalError
);
ASSERT_THROW(
evaluator.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(),
evaluator.errors.make<EvalError>("puppy")
.withTrace(noPos, "doggy")
.debugThrow(always_progresses),
EvalError
);
ASSERT_THROW(
try {
try {
evaluator.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
evaluator.errors.make<EvalError>("puppy")
.withTrace(noPos, "doggy")
.debugThrow(always_progresses);
} catch (Error & e) {
e.addTrace(evaluator.positions[noPos], "beans");
throw;
@@ -47,10 +52,12 @@ namespace nix {
TEST_F(ErrorTraceTest, NestedThrows) {
try {
evaluator.errors.make<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
evaluator.errors.make<EvalError>("puppy")
.withTrace(noPos, "doggy")
.debugThrow(always_progresses);
} catch (BaseError & e) {
try {
evaluator.errors.make<EvalError>("beans").debugThrow();
evaluator.errors.make<EvalError>("beans").debugThrow(always_progresses);
} catch (Error & e2) {
e.addTrace(evaluator.positions[noPos], "beans2");
//e2.addTrace(state.positions[noPos], "Something", "");