builtins.warn: Use new EvalBaseError + "evaluation warning"

This commit is contained in:
Robert Hensing 2024-05-22 12:51:46 +02:00
parent 831d96d8d7
commit 70b1036224
6 changed files with 53 additions and 7 deletions

View file

@ -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).

View file

@ -70,6 +70,13 @@ EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const A
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::setIsFromExpr()
{
error.err.isFromExpr = true;
return *this;
}
template<class T>
void EvalErrorBuilder<T>::debugThrow()
{
@ -85,6 +92,7 @@ void EvalErrorBuilder<T>::debugThrow()
throw error;
}
template class EvalErrorBuilder<EvalBaseError>;
template class EvalErrorBuilder<EvalError>;
template class EvalErrorBuilder<AssertionError>;
template class EvalErrorBuilder<ThrownError>;

View file

@ -15,27 +15,39 @@ class EvalState;
template<class T>
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<class T>
friend class EvalErrorBuilder;
public:
EvalState & state;
EvalError(EvalState & state, ErrorInfo && errorInfo)
EvalBaseError(EvalState & state, ErrorInfo && errorInfo)
: Error(errorInfo)
, state(state)
{
}
template<typename... Args>
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<T> & addTrace(PosIdx pos, HintFmt hint);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & setIsFromExpr();
template<typename... Args>
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> &
addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs);

View file

@ -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<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
state.error<Abort>("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<ThrownError>(s).debugThrow();
state.error<ThrownError>(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<Error>("aborting to reveal stack trace of warning, as abort-on-warn is set").debugThrow();
state.error<EvalBaseError>("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,
});

View file

@ -240,6 +240,9 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
break;
}
case Verbosity::lvlWarn: {
if (einfo.isFromExpr)
prefix = ANSI_WARNING "evaluation warning";
else
prefix = ANSI_WARNING "warning";
break;
}

View file

@ -89,6 +89,11 @@ struct ErrorInfo {
HintFmt msg;
std::shared_ptr<Pos> pos;
std::list<Trace> 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.