From 2d4c9d8f4a41520e53441985b1fce5be87b731c8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 23 Apr 2024 01:21:50 +0200 Subject: [PATCH 1/7] Add builtins.warn --- src/libexpr/eval-settings.cc | 4 ++++ src/libexpr/eval-settings.hh | 34 +++++++++++++++++++++++++++++---- src/libexpr/primops.cc | 37 ++++++++++++++++++++++++++++++++++++ tests/functional/lang.sh | 7 +++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/libexpr/eval-settings.cc b/src/libexpr/eval-settings.cc index 2ccbe327f..85b1677ea 100644 --- a/src/libexpr/eval-settings.cc +++ b/src/libexpr/eval-settings.cc @@ -48,6 +48,10 @@ EvalSettings::EvalSettings() { auto var = getEnv("NIX_PATH"); if (var) nixPath = parseNixPath(*var); + + var = getEnv("NIX_ABORT_ON_WARN"); + if (var && (var == "1" || var == "yes" || var == "true")) + builtinsAbortOnWarn = true; } Strings EvalSettings::getDefaultNixPath() diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh index dbfc3b2c7..f1fb539bd 100644 --- a/src/libexpr/eval-settings.hh +++ b/src/libexpr/eval-settings.hh @@ -158,13 +158,39 @@ struct EvalSettings : Config Setting builtinsTraceDebugger{this, false, "debugger-on-trace", R"( - If set to true and the `--debugger` flag is given, - [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace) will - enter the debugger like - [`builtins.break`](@docroot@/language/builtins.md#builtins-break). + If set to true and the `--debugger` flag is given, the following functions + will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break). + + * [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace) + * [`builtins.traceVerbose`](@docroot@/language/builtins.md#builtins-traceVerbose) + if [`trace-verbose`](#conf-trace-verbose) is set to true. + * [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) This is useful for debugging warnings in third-party Nix code. )"}; + + Setting builtinsDebuggerOnWarn{this, false, "debugger-on-warn", + R"( + If set to true and the `--debugger` flag is given, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) + will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break). + + This is useful for debugging warnings in third-party Nix code. + + Use [`debugger-on-trace`](#conf-debugger-on-trace) to also enter the debugger on legacy warnings that are logged with [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace). + )"}; + + Setting builtinsAbortOnWarn{this, false, "abort-on-warn", + R"( + If set to true, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) will throw an error when logging a warning. + + This will give you a stack trace that leads to the location of the warning. + + This is useful for finding information about warnings in third-party Nix code when you can not start the interactive debugger, such as when Nix is called from a non-interactive script. See [`debugger-on-warn`](#conf-debugger-on-warn). + + Currently, a stack trace can only be produced when the debugger is enabled, or when evaluation is aborted. + + This option can be enabled by setting `NIX_ABORT_ON_WARN=1` in the environment. + )"}; }; extern EvalSettings evalSettings; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7371bd488..9319709c1 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1043,6 +1043,43 @@ static RegisterPrimOp primop_trace({ .fun = prim_trace, }); +static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value & v) +{ + state.forceValue(*args[0], pos); + if (args[0]->type() == nString) + printMsg(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " %1%", args[0]->string_view()); + else + printMsg(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " %1%", ValuePrinter(state, *args[0])); + + if (evalSettings.builtinsAbortOnWarn) { + state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); + } + if ((evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) && state.debugRepl && !state.debugTraces.empty()) { + const DebugTrace & last = state.debugTraces.front(); + state.runDebugRepl(nullptr, last.env, last.expr); + } + state.forceValue(*args[1], pos); + v = *args[1]; +} + +static RegisterPrimOp primop_warn({ + .name = "__warn", + .args = {"e1", "e2"}, + .doc = R"( + Evaluate *e1*, which must be a string and print iton standard error as a warning. + Then return *e2*. + This function is useful for non-critical situations where attention is advisable. + + If the + [`debugger-on-trace`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace) + or [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn) + option is set to `true` and the `--debugger` flag is given, the + interactive debugger will be started when `warn` is called (like + [`break`](@docroot@/language/builtins.md#builtins-break)). + )", + .fun = prim_warn, +}); + /* Takes two arguments and evaluates to the second one. Used as the * builtins.traceVerbose implementation when --trace-verbose is not enabled diff --git a/tests/functional/lang.sh b/tests/functional/lang.sh index a853cfd81..843ff7cfa 100755 --- a/tests/functional/lang.sh +++ b/tests/functional/lang.sh @@ -36,6 +36,13 @@ nix-instantiate --eval -E 'let x = builtins.trace { x = x; } true; in x' \ nix-instantiate --eval -E 'let x = { repeating = x; tracing = builtins.trace x true; }; in x.tracing'\ 2>&1 | grepQuiet -F 'trace: { repeating = «repeated»; tracing = «potential infinite recursion»; }' +nix-instantiate --eval -E 'builtins.warn "Hello" 123' 2>&1 | grepQuiet 'warning: Hello' +nix-instantiate --eval -E 'builtins.addErrorContext "while doing ${"something"} interesting" (builtins.warn "Hello" 123)' 2>/dev/null | grepQuiet 123 +nix-instantiate --eval -E 'let x = builtins.warn { x = x; } true; in x' \ + 2>&1 | grepQuiet -E 'warning: { x = «potential infinite recursion»; }' +expectStderr 1 nix-instantiate --eval --abort-on-warn -E 'builtins.warn "Hello" 123' | grepQuiet Hello +NIX_ABORT_ON_WARN=1 expectStderr 1 nix-instantiate --eval -E 'builtins.addErrorContext "while doing ${"something"} interesting" (builtins.warn "Hello" 123)' | grepQuiet "while doing something interesting" + set +x badDiff=0 From 923cbea2af8eb1ccbd29b6b3542c974081bdddc1 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 23 Apr 2024 18:07:45 +0200 Subject: [PATCH 2/7] builtins.warn: Use logWarning Constructing ErrorInfo is a little awkward for now, but this does produce a richer log entry. --- src/libexpr/primops.cc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9319709c1..c20e0c359 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1046,10 +1046,20 @@ static RegisterPrimOp primop_trace({ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); - if (args[0]->type() == nString) - printMsg(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " %1%", args[0]->string_view()); - else - printMsg(lvlWarn, ANSI_WARNING "warning:" ANSI_NORMAL " %1%", ValuePrinter(state, *args[0])); + + { + BaseError msg(args[0]->type() == nString + ? std::string(args[0]->string_view()) + : ({ + std::stringstream s; + s << ValuePrinter(state, *args[0]); + s.str(); + })); + msg.atPos(state.positions[pos]); + auto info = msg.info(); + info.level = lvlWarn; + logWarning(info); + } if (evalSettings.builtinsAbortOnWarn) { state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); From da82d67022fd77f7f38556f2dabac416110ec55e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 24 Apr 2024 10:56:32 +0200 Subject: [PATCH 3/7] builtins.warn: Require string argument ... so that we may perhaps later extend the interface. Note that Nixpkgs' lib.warn already requires a string coercible argument, so this is reasonable. Also note that string coercible values aren't all strings, but in practice, for warn, they are. --- src/libexpr/primops.cc | 12 ++++-------- tests/functional/lang.sh | 6 ++++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index c20e0c359..5ff7d5314 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1045,16 +1045,12 @@ static RegisterPrimOp primop_trace({ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value & v) { - state.forceValue(*args[0], pos); + // We only accept a string argument for now. The use case for pretty printing a value is covered by `trace`. + // By rejecting non-strings we allow future versions to add more features without breaking existing code. + auto msgStr = state.forceString(*args[0], pos, "while evaluating the first argument; the message passed to builtins.warn"); { - BaseError msg(args[0]->type() == nString - ? std::string(args[0]->string_view()) - : ({ - std::stringstream s; - s << ValuePrinter(state, *args[0]); - s.str(); - })); + BaseError msg(std::string{msgStr}); msg.atPos(state.positions[pos]); auto info = msg.info(); info.level = lvlWarn; diff --git a/tests/functional/lang.sh b/tests/functional/lang.sh index 843ff7cfa..569e7082e 100755 --- a/tests/functional/lang.sh +++ b/tests/functional/lang.sh @@ -38,8 +38,10 @@ nix-instantiate --eval -E 'let x = { repeating = x; tracing = builtins.trace x t nix-instantiate --eval -E 'builtins.warn "Hello" 123' 2>&1 | grepQuiet 'warning: Hello' nix-instantiate --eval -E 'builtins.addErrorContext "while doing ${"something"} interesting" (builtins.warn "Hello" 123)' 2>/dev/null | grepQuiet 123 -nix-instantiate --eval -E 'let x = builtins.warn { x = x; } true; in x' \ - 2>&1 | grepQuiet -E 'warning: { x = «potential infinite recursion»; }' + +# warn does not accept non-strings for now +expectStderr 1 nix-instantiate --eval -E 'let x = builtins.warn { x = x; } true; in x' \ + | grepQuiet "expected a string but found a set" expectStderr 1 nix-instantiate --eval --abort-on-warn -E 'builtins.warn "Hello" 123' | grepQuiet Hello NIX_ABORT_ON_WARN=1 expectStderr 1 nix-instantiate --eval -E 'builtins.addErrorContext "while doing ${"something"} interesting" (builtins.warn "Hello" 123)' | grepQuiet "while doing something interesting" From c07500e14dce5036ea4b8cf956cf34df4b282873 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 1 May 2024 23:02:43 +0200 Subject: [PATCH 4/7] refactor: Extract EvalState::{runDebugRepl,canDebug} --- src/libexpr/eval-error.cc | 7 +------ src/libexpr/eval.cc | 18 ++++++++++++++++++ src/libexpr/eval.hh | 12 ++++++++++++ src/libexpr/primops.cc | 15 ++++++--------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/libexpr/eval-error.cc b/src/libexpr/eval-error.cc index 282f5554a..5a0c67514 100644 --- a/src/libexpr/eval-error.cc +++ b/src/libexpr/eval-error.cc @@ -73,12 +73,7 @@ EvalErrorBuilder::addTrace(PosIdx pos, std::string_view formatString, const A template void EvalErrorBuilder::debugThrow() { - if (error.state.debugRepl && !error.state.debugTraces.empty()) { - const DebugTrace & last = error.state.debugTraces.front(); - const Env * env = &last.env; - const Expr * expr = &last.expr; - error.state.runDebugRepl(&error, *env, *expr); - } + error.state.runDebugRepl(&error); // `EvalState` is the only class that can construct an `EvalErrorBuilder`, // and it does so in dynamic storage. This is the final method called on diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c1dadeee0..6a38bbe45 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -785,6 +785,24 @@ public: } }; +bool EvalState::canDebug() +{ + return debugRepl && !debugTraces.empty(); +} + +void EvalState::runDebugRepl(const Error * error) +{ + if (!canDebug()) + return; + + assert(!debugTraces.empty()); + const DebugTrace & last = debugTraces.front(); + const Env & env = last.env; + const Expr & expr = last.expr; + + runDebugRepl(error, env, expr); +} + void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & expr) { // Make sure we have a debugger to run and we're not already in a debugger. diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 7ca2d6227..06a687620 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -276,6 +276,18 @@ public: return std::shared_ptr();; } + /** Whether a debug repl can be started. If `false`, `runDebugRepl(error)` will return without starting a repl. */ + bool canDebug(); + + /** Use front of `debugTraces`; see `runDebugRepl(error,env,expr)` */ + void runDebugRepl(const Error * error); + + /** + * Run a debug repl with the given error, environment and expression. + * @param error The error to debug, may be nullptr. + * @param env The environment to debug, matching the expression. + * @param expr The expression to debug, matching the environment. + */ void runDebugRepl(const Error * error, const Env & env, const Expr & expr); template diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 5ff7d5314..459d19e05 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -780,15 +780,14 @@ static RegisterPrimOp primop_break({ )", .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { - if (state.debugRepl && !state.debugTraces.empty()) { + if (state.canDebug()) { 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); + state.runDebugRepl(&error); } // Return the value we were passed. @@ -1018,9 +1017,8 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu printError("trace: %1%", args[0]->string_view()); else printError("trace: %1%", ValuePrinter(state, *args[0])); - if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) { - const DebugTrace & last = state.debugTraces.front(); - state.runDebugRepl(nullptr, last.env, last.expr); + if (evalSettings.builtinsTraceDebugger) { + state.runDebugRepl(nullptr); } state.forceValue(*args[1], pos); v = *args[1]; @@ -1060,9 +1058,8 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value if (evalSettings.builtinsAbortOnWarn) { state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); } - if ((evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) && state.debugRepl && !state.debugTraces.empty()) { - const DebugTrace & last = state.debugTraces.front(); - state.runDebugRepl(nullptr, last.env, last.expr); + if (evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) { + state.runDebugRepl(nullptr); } state.forceValue(*args[1], pos); v = *args[1]; From 831d96d8d79424595b955791dd007c3317b76b3d Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 7 May 2024 09:13:58 +0200 Subject: [PATCH 5/7] builtins.warn: Do not throw EvalError --- src/libexpr/primops.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 459d19e05..9741e3177 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1056,7 +1056,8 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value } if (evalSettings.builtinsAbortOnWarn) { - state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); + // Not an EvalError or subclass, which would cause the error to be stored in the eval cache. + state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); } if (evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) { state.runDebugRepl(nullptr); From 70b10362247ef563ef4cad02709ec2bdb5564206 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 22 May 2024 12:51:46 +0200 Subject: [PATCH 6/7] builtins.warn: Use new EvalBaseError + "evaluation warning" --- doc/manual/rl-next/builtins-warn.md | 10 ++++++++++ src/libexpr/eval-error.cc | 8 ++++++++ src/libexpr/eval-error.hh | 20 +++++++++++++++++--- src/libexpr/primops.cc | 12 +++++++++--- src/libutil/error.cc | 5 ++++- src/libutil/error.hh | 5 +++++ 6 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 doc/manual/rl-next/builtins-warn.md diff --git a/doc/manual/rl-next/builtins-warn.md b/doc/manual/rl-next/builtins-warn.md new file mode 100644 index 000000000..0f7a6ebba --- /dev/null +++ b/doc/manual/rl-next/builtins-warn.md @@ -0,0 +1,10 @@ +--- +synopsis: "New builtin: `builtins.warn`" +issues: 306026 +prs: 10592 +--- + +`builtins.warn` behaves like `builtins.trace "warning: ${msg}`, has an accurate log level, and is controlled by the options +[`debugger-on-trace`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace), +[`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn) and +[`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn). diff --git a/src/libexpr/eval-error.cc b/src/libexpr/eval-error.cc index 5a0c67514..bd84e0428 100644 --- a/src/libexpr/eval-error.cc +++ b/src/libexpr/eval-error.cc @@ -70,6 +70,13 @@ EvalErrorBuilder::addTrace(PosIdx pos, std::string_view formatString, const A return *this; } +template +EvalErrorBuilder & EvalErrorBuilder::setIsFromExpr() +{ + error.err.isFromExpr = true; + return *this; +} + template void EvalErrorBuilder::debugThrow() { @@ -85,6 +92,7 @@ void EvalErrorBuilder::debugThrow() throw error; } +template class EvalErrorBuilder; template class EvalErrorBuilder; template class EvalErrorBuilder; template class EvalErrorBuilder; diff --git a/src/libexpr/eval-error.hh b/src/libexpr/eval-error.hh index 27407eb6e..fe48e054b 100644 --- a/src/libexpr/eval-error.hh +++ b/src/libexpr/eval-error.hh @@ -15,27 +15,39 @@ class EvalState; template class EvalErrorBuilder; -class EvalError : public Error +/** + * Base class for all errors that occur during evaluation. + * + * Most subclasses should inherit from `EvalError` instead of this class. + */ +class EvalBaseError : public Error { template friend class EvalErrorBuilder; public: EvalState & state; - EvalError(EvalState & state, ErrorInfo && errorInfo) + EvalBaseError(EvalState & state, ErrorInfo && errorInfo) : Error(errorInfo) , state(state) { } template - explicit EvalError(EvalState & state, const std::string & formatString, const Args &... formatArgs) + explicit EvalBaseError(EvalState & state, const std::string & formatString, const Args &... formatArgs) : Error(formatString, formatArgs...) , state(state) { } }; +/** + * `EvalError` is the base class for almost all errors that occur during evaluation. + * + * All instances of `EvalError` should show a degree of purity that allows them to be + * cached in pure mode. This means that they should not depend on the configuration or the overall environment. + */ +MakeError(EvalError, EvalBaseError); MakeError(ParseError, Error); MakeError(AssertionError, EvalError); MakeError(ThrownError, AssertionError); @@ -90,6 +102,8 @@ public: [[nodiscard, gnu::noinline]] EvalErrorBuilder & addTrace(PosIdx pos, HintFmt hint); + [[nodiscard, gnu::noinline]] EvalErrorBuilder & setIsFromExpr(); + template [[nodiscard, gnu::noinline]] EvalErrorBuilder & addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9741e3177..22d7f188f 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -806,7 +806,7 @@ static RegisterPrimOp primop_abort({ 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(); + state.error("evaluation aborted with the following error message: '%1%'", s).setIsFromExpr().debugThrow(); } }); @@ -825,7 +825,7 @@ static RegisterPrimOp primop_throw({ NixStringContext context; auto s = state.coerceToString(pos, *args[0], context, "while evaluating the error message passed to builtin.throw").toOwned(); - state.error(s).debugThrow(); + state.error(s).setIsFromExpr().debugThrow(); } }); @@ -1052,12 +1052,13 @@ static void prim_warn(EvalState & state, const PosIdx pos, Value * * args, Value msg.atPos(state.positions[pos]); auto info = msg.info(); info.level = lvlWarn; + info.isFromExpr = true; logWarning(info); } if (evalSettings.builtinsAbortOnWarn) { // Not an EvalError or subclass, which would cause the error to be stored in the eval cache. - state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow(); + state.error("aborting to reveal stack trace of warning, as abort-on-warn is set").setIsFromExpr().debugThrow(); } if (evalSettings.builtinsTraceDebugger || evalSettings.builtinsDebuggerOnWarn) { state.runDebugRepl(nullptr); @@ -1080,6 +1081,11 @@ static RegisterPrimOp primop_warn({ option is set to `true` and the `--debugger` flag is given, the interactive debugger will be started when `warn` is called (like [`break`](@docroot@/language/builtins.md#builtins-break)). + + If the + [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn) + option is set, the evaluation will be aborted after the warning is printed. + This is useful to reveal the stack trace of the warning, when the context is non-interactive and a debugger can not be launched. )", .fun = prim_warn, }); diff --git a/src/libutil/error.cc b/src/libutil/error.cc index fd4f4efd1..e01f06448 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -240,7 +240,10 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s break; } case Verbosity::lvlWarn: { - prefix = ANSI_WARNING "warning"; + if (einfo.isFromExpr) + prefix = ANSI_WARNING "evaluation warning"; + else + prefix = ANSI_WARNING "warning"; break; } case Verbosity::lvlInfo: { diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 87d181c94..269000016 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -89,6 +89,11 @@ struct ErrorInfo { HintFmt msg; std::shared_ptr pos; std::list traces; + /** + * Some messages are generated directly by expressions; notably `builtins.warn`, `abort`, `throw`. + * These may be rendered differently, so that users can distinguish them. + */ + bool isFromExpr = false; /** * Exit status. From 3a0b0af2ace69bb98ba1f86e1da4ec8f13c31840 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 3 Jun 2024 15:30:35 +0200 Subject: [PATCH 7/7] Fix typo in doc/manual/rl-next/builtins-warn.md Co-authored-by: Eelco Dolstra --- doc/manual/rl-next/builtins-warn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/rl-next/builtins-warn.md b/doc/manual/rl-next/builtins-warn.md index 0f7a6ebba..f805e1610 100644 --- a/doc/manual/rl-next/builtins-warn.md +++ b/doc/manual/rl-next/builtins-warn.md @@ -4,7 +4,7 @@ issues: 306026 prs: 10592 --- -`builtins.warn` behaves like `builtins.trace "warning: ${msg}`, has an accurate log level, and is controlled by the options +`builtins.warn` behaves like `builtins.trace "warning: ${msg}"`, has an accurate log level, and is controlled by the options [`debugger-on-trace`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace), [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn) and [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn).