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`.
2024-01-23 03:08:29 +02:00
|
|
|
#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(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)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-02-07 02:49:28 +02:00
|
|
|
/**
|
|
|
|
* `EvalErrorBuilder`s may only be constructed by `EvalState`. The `debugThrow`
|
|
|
|
* method must be the final method in any such `EvalErrorBuilder` usage, and it
|
|
|
|
* handles deleting the object.
|
|
|
|
*/
|
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`.
2024-01-23 03:08:29 +02:00
|
|
|
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);
|
|
|
|
|
2024-02-07 02:49:28 +02:00
|
|
|
/**
|
|
|
|
* Delete the `EvalErrorBuilder` and throw the underlying exception.
|
|
|
|
*/
|
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`.
2024-01-23 03:08:29 +02:00
|
|
|
[[gnu::noinline, gnu::noreturn]] void debugThrow();
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|