primops: Move functions to primops/control.cc

Moved builtins: abort, break, throw, tryEval

Change-Id: I6198b201325a7029cf011b868f87bef7f4272f8a
This commit is contained in:
Tom Hubrecht 2024-05-30 01:29:46 +02:00
parent 0ba37444da
commit 84e80fa97d
3 changed files with 150 additions and 110 deletions

View file

@ -88,6 +88,7 @@ libexpr_sources = files(
'flake/lockfile.cc', 'flake/lockfile.cc',
'primops/attrset.cc', 'primops/attrset.cc',
'primops/context.cc', 'primops/context.cc',
'primops/control.cc',
'primops/fetchClosure.cc', 'primops/fetchClosure.cc',
'primops/fetchMercurial.cc', 'primops/fetchMercurial.cc',
'primops/fetchTree.cc', 'primops/fetchTree.cc',

View file

@ -715,65 +715,6 @@ static RegisterPrimOp primop_genericClosure(PrimOp {
}); });
static RegisterPrimOp primop_break({
.name = "break",
.args = {"v"},
.doc = R"(
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
Otherwise, return the argument `v`.
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (state.debugRepl && !state.debugTraces.empty()) {
auto error = Error(ErrorInfo {
.level = lvlInfo,
.msg = HintFmt("breakpoint reached"),
.pos = state.positions[pos],
});
auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr);
}
// Return the value we were passed.
v = *args[0];
}
});
static RegisterPrimOp primop_abort({
.name = "abort",
.args = {"s"},
.doc = R"(
Abort Nix expression evaluation and print the error message *s*.
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.abort").toOwned();
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
}
});
static RegisterPrimOp primop_throw({
.name = "throw",
.args = {"s"},
.doc = R"(
Throw an error message *s*. This usually aborts Nix expression
evaluation, but in `nix-env -qa` and other commands that try to
evaluate a set of derivations to get information about those
derivations, a derivation that throws an error is silently skipped
(which is not the case for `abort`).
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtin.throw").toOwned();
state.error<ThrownError>(s).debugThrow();
}
});
static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
try { try {
@ -836,57 +777,6 @@ static RegisterPrimOp primop_floor({
/* Try evaluating the argument. Success => {success=true; value=something;}, /* Try evaluating the argument. Success => {success=true; value=something;},
* else => {success=false; value=false;} */ * else => {success=false; value=false;} */
static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto attrs = state.buildBindings(2);
/* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel);
ReplExitStatus (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
{
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebugRepl = state.debugRepl;
state.debugRepl = nullptr;
}
try {
state.forceValue(*args[0], pos);
attrs.insert(state.sValue, args[0]);
attrs.alloc("success").mkBool(true);
} catch (AssertionError & e) {
attrs.alloc(state.sValue).mkBool(false);
attrs.alloc("success").mkBool(false);
}
// restore the debugRepl pointer if we saved it earlier.
if (savedDebugRepl)
state.debugRepl = savedDebugRepl;
v.mkAttrs(attrs);
}
static RegisterPrimOp primop_tryEval({
.name = "__tryEval",
.args = {"e"},
.doc = R"(
Try to shallowly evaluate *e*. Return a set containing the
attributes `success` (`true` if *e* evaluated successfully,
`false` if an error was thrown) and `value`, equalling *e* if
successful and `false` otherwise. `tryEval` will only prevent
errors created by `throw` or `assert` from being thrown.
Errors `tryEval` will not catch are for example those created
by `abort` and type errors generated by builtins. Also note that
this doesn't evaluate *e* deeply, so `let e = { x = throw ""; };
in (builtins.tryEval e).success` will be `true`. Using
`builtins.deepSeq` one can get the expected result:
`let e = { x = throw ""; }; in
(builtins.tryEval (builtins.deepSeq e e)).success` will be
`false`.
)",
.fun = prim_tryEval,
});
/* Return an environment variable. Use with care. */ /* Return an environment variable. Use with care. */
static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v)

View file

@ -0,0 +1,149 @@
#include "eval-settings.hh"
#include "primops.hh"
namespace nix {
/**
* builtins.abort
*/
static void prim_abort(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
NixStringContext context;
auto s = state
.coerceToString(
pos,
*args[0],
context,
"while evaluating the error message passed to builtins.abort"
)
.toOwned();
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s)
.debugThrow();
}
static RegisterPrimOp primop_abort({
.name = "abort",
.args = {"s"},
.doc = R"(
Abort Nix expression evaluation and print the error message *s*.
)",
.fun = prim_abort,
});
/**
* builtins.break
*/
static void prim_break(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
if (state.debugRepl && !state.debugTraces.empty()) {
auto error = Error(ErrorInfo{
.level = lvlInfo,
.msg = HintFmt("breakpoint reached"),
.pos = state.positions[pos],
});
auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr);
}
// Return the value we were passed.
v = *args[0];
}
static RegisterPrimOp primop_break({
.name = "break",
.args = {"v"},
.doc = R"(
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
Otherwise, return the argument `v`.
)",
.fun = prim_break,
});
/**
* builtins.throw
*/
static void prim_throw(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
NixStringContext context;
auto s =
state
.coerceToString(
pos, *args[0], context, "while evaluating the error message passed to builtin.throw"
)
.toOwned();
state.error<ThrownError>(s).debugThrow();
}
static RegisterPrimOp primop_throw({
.name = "throw",
.args = {"s"},
.doc = R"(
Throw an error message *s*. This usually aborts Nix expression
evaluation, but in `nix-env -qa` and other commands that try to
evaluate a set of derivations to get information about those
derivations, a derivation that throws an error is silently skipped
(which is not the case for `abort`).
)",
.fun = prim_throw,
});
/**
* builtins.tryEval
*/
static void prim_tryEval(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
auto attrs = state.buildBindings(2);
/* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel);
ReplExitStatus (*savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry) {
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebugRepl = state.debugRepl;
state.debugRepl = nullptr;
}
try {
state.forceValue(*args[0], pos);
attrs.insert(state.sValue, args[0]);
attrs.alloc("success").mkBool(true);
} catch (AssertionError & e) {
attrs.alloc(state.sValue).mkBool(false);
attrs.alloc("success").mkBool(false);
}
// restore the debugRepl pointer if we saved it earlier.
if (savedDebugRepl) {
state.debugRepl = savedDebugRepl;
}
v.mkAttrs(attrs);
}
static RegisterPrimOp primop_tryEval({
.name = "__tryEval",
.args = {"e"},
.doc = R"(
Try to shallowly evaluate *e*. Return a set containing the
attributes `success` (`true` if *e* evaluated successfully,
`false` if an error was thrown) and `value`, equalling *e* if
successful and `false` otherwise. `tryEval` will only prevent
errors created by `throw` or `assert` from being thrown.
Errors `tryEval` will not catch are for example those created
by `abort` and type errors generated by builtins. Also note that
this doesn't evaluate *e* deeply, so `let e = { x = throw ""; };
in (builtins.tryEval e).success` will be `true`. Using
`builtins.deepSeq` one can get the expected result:
`let e = { x = throw ""; }; in
(builtins.tryEval (builtins.deepSeq e e)).success` will be
`false`.
)",
.fun = prim_tryEval,
});
}