diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index fb7079d80..699e394c8 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -88,6 +88,7 @@ libexpr_sources = files( 'flake/lockfile.cc', 'primops/attrset.cc', 'primops/context.cc', + 'primops/control.cc', 'primops/fetchClosure.cc', 'primops/fetchMercurial.cc', 'primops/fetchTree.cc', diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index e8fa0fc31..429a41a98 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -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("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(s).debugThrow(); - } -}); - static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { try { @@ -836,57 +777,6 @@ static RegisterPrimOp primop_floor({ /* Try evaluating the argument. Success => {success=true; value=something;}, * 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 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. */ static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v) diff --git a/src/libexpr/primops/control.cc b/src/libexpr/primops/control.cc new file mode 100644 index 000000000..71d6ff6ed --- /dev/null +++ b/src/libexpr/primops/control.cc @@ -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("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(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 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, +}); +}