aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/eval-error.hh
diff options
context:
space:
mode:
authoreldritch horrors <pennae@lix.systems>2024-03-08 07:09:48 +0100
committereldritch horrors <pennae@lix.systems>2024-03-09 04:47:05 -0700
commit08252967a8c2ab15f3fb8bdfb848f007d4032d50 (patch)
tree909ec8357654d4d179f0f8e7dfc5d3d623256b46 /src/libexpr/eval-error.hh
parentd4c738fe4c587c3f09b0fda899f419f2de97ee2f (diff)
libexpr: Support structured error classes
While preparing PRs like #9753, I've had to change error messages in dozens of code paths. It would be nice if instead of EvalError("expected 'boolean' but found '%1%'", showType(v)) we could write TypeError(v, "boolean") or similar. Then, changing the error message could be a mechanical refactor with the compiler pointing out places the constructor needs to be changed, rather than the error-prone process of grepping through the codebase. Structured errors would also help prevent the "same" error from having multiple slightly different messages, and could be a first step towards error codes / an error index. This PR reworks the exception infrastructure in `libexpr` to support exception types with different constructor signatures than `BaseError`. Actually refactoring the exceptions to use structured data will come in a future PR (this one is big enough already, as it has to touch every exception in `libexpr`). The core design is in `eval-error.hh`. Generally, errors like this: state.error("'%s' is not a string", getAttrPathStr()) .debugThrow<TypeError>() are transformed like this: state.error<TypeError>("'%s' is not a string", getAttrPathStr()) .debugThrow() The type annotation has moved from `ErrorBuilder::debugThrow` to `EvalState::error`. (cherry picked from commit c6a89c1a1659b31694c0fbcd21d78a6dd521c732) Change-Id: Iced91ba4e00ca9e801518071fb43798936cbd05a
Diffstat (limited to 'src/libexpr/eval-error.hh')
-rw-r--r--src/libexpr/eval-error.hh119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/libexpr/eval-error.hh b/src/libexpr/eval-error.hh
new file mode 100644
index 000000000..1faf76781
--- /dev/null
+++ b/src/libexpr/eval-error.hh
@@ -0,0 +1,119 @@
+#pragma once
+
+#include <algorithm>
+
+#include "error.hh"
+#include "pos-idx.hh"
+
+namespace nix {
+
+struct Env;
+struct Expr;
+struct Value;
+
+class EvalState;
+template<class T>
+class EvalErrorBuilder;
+
+class EvalError : public Error
+{
+ template<class T>
+ friend class EvalErrorBuilder;
+public:
+ EvalState & state;
+
+ EvalError(EvalState & state, ErrorInfo && errorInfo)
+ : Error(errorInfo)
+ , state(state)
+ {
+ }
+
+ template<typename... Args>
+ explicit EvalError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
+ : Error(formatString, formatArgs...)
+ , state(state)
+ {
+ }
+};
+
+MakeError(ParseError, Error);
+MakeError(AssertionError, EvalError);
+MakeError(ThrownError, AssertionError);
+MakeError(Abort, EvalError);
+MakeError(TypeError, EvalError);
+MakeError(UndefinedVarError, EvalError);
+MakeError(MissingArgumentError, EvalError);
+MakeError(RestrictedPathError, Error);
+MakeError(CachedEvalError, EvalError);
+MakeError(InfiniteRecursionError, EvalError);
+
+struct InvalidPathError : public EvalError
+{
+public:
+ Path path;
+ InvalidPathError(EvalState & state, const Path & path)
+ : EvalError(state, "path '%s' is not valid", path)
+ {
+ }
+};
+
+template<class T>
+class EvalErrorBuilder final
+{
+ friend class EvalState;
+
+ template<typename... Args>
+ explicit EvalErrorBuilder(EvalState & state, const Args &... args)
+ : error(T(state, args...))
+ {
+ }
+
+public:
+ T error;
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withExitStatus(unsigned int exitStatus);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(PosIdx pos);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(Value & value, PosIdx fallback = noPos);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withTrace(PosIdx pos, const std::string_view text);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrameTrace(PosIdx pos, const std::string_view text);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withSuggestions(Suggestions & s);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrame(const Env & e, const Expr & ex);
+
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & addTrace(PosIdx pos, hintformat hint, bool frame = false);
+
+ template<typename... Args>
+ [[nodiscard, gnu::noinline]] EvalErrorBuilder<T> &
+ addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs);
+
+ [[gnu::noinline, gnu::noreturn]] void debugThrow();
+};
+
+/**
+ * The size needed to allocate any `EvalErrorBuilder<T>`.
+ *
+ * The list of classes here needs to be kept in sync with the list of `template
+ * class` declarations in `eval-error.cc`.
+ *
+ * This is used by `EvalState` to preallocate a buffer of sufficient size for
+ * any `EvalErrorBuilder<T>` to avoid allocating while evaluating Nix code.
+ */
+constexpr size_t EVAL_ERROR_BUILDER_SIZE = std::max({
+ sizeof(EvalErrorBuilder<EvalError>),
+ sizeof(EvalErrorBuilder<AssertionError>),
+ sizeof(EvalErrorBuilder<ThrownError>),
+ sizeof(EvalErrorBuilder<Abort>),
+ sizeof(EvalErrorBuilder<TypeError>),
+ sizeof(EvalErrorBuilder<UndefinedVarError>),
+ sizeof(EvalErrorBuilder<MissingArgumentError>),
+ sizeof(EvalErrorBuilder<InfiniteRecursionError>),
+ sizeof(EvalErrorBuilder<CachedEvalError>),
+ sizeof(EvalErrorBuilder<InvalidPathError>),
+});
+
+}