forked from lix-project/lix
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
This commit is contained in:
parent
d4c738fe4c
commit
08252967a8
|
@ -427,8 +427,6 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
|
||||||
// Quietly ignore parse errors.
|
// Quietly ignore parse errors.
|
||||||
} catch (EvalError & e) {
|
} catch (EvalError & e) {
|
||||||
// Quietly ignore evaluation errors.
|
// Quietly ignore evaluation errors.
|
||||||
} catch (UndefinedVarError & e) {
|
|
||||||
// Quietly ignore undefined variable errors.
|
|
||||||
} catch (BadURL & e) {
|
} catch (BadURL & e) {
|
||||||
// Quietly ignore BadURL flake-related errors.
|
// Quietly ignore BadURL flake-related errors.
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,10 +66,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
|
||||||
if (!attrIndex) {
|
if (!attrIndex) {
|
||||||
|
|
||||||
if (v->type() != nAttrs)
|
if (v->type() != nAttrs)
|
||||||
throw TypeError(
|
state.error<TypeError>(
|
||||||
"the expression selected by the selection path '%1%' should be a set but is %2%",
|
"the expression selected by the selection path '%1%' should be a set but is %2%",
|
||||||
attrPath,
|
attrPath,
|
||||||
showType(*v));
|
showType(*v)).debugThrow();
|
||||||
if (attr.empty())
|
if (attr.empty())
|
||||||
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||||
|
|
||||||
|
@ -89,10 +89,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
|
||||||
else {
|
else {
|
||||||
|
|
||||||
if (!v->isList())
|
if (!v->isList())
|
||||||
throw TypeError(
|
state.error<TypeError>(
|
||||||
"the expression selected by the selection path '%1%' should be a list but is %2%",
|
"the expression selected by the selection path '%1%' should be a list but is %2%",
|
||||||
attrPath,
|
attrPath,
|
||||||
showType(*v));
|
showType(*v)).debugThrow();
|
||||||
if (*attrIndex >= v->listSize())
|
if (*attrIndex >= v->listSize())
|
||||||
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
|
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
|
||||||
|
|
||||||
|
|
|
@ -490,7 +490,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
if (forceErrors)
|
if (forceErrors)
|
||||||
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
|
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
|
||||||
else
|
else
|
||||||
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name));
|
throw CachedEvalError(root->state, "cached failure of attribute '%s'", getAttrPathStr(name));
|
||||||
} else
|
} else
|
||||||
return std::make_shared<AttrCursor>(root,
|
return std::make_shared<AttrCursor>(root,
|
||||||
std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
|
std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
|
||||||
|
@ -499,7 +499,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
// evaluate to see whether 'name' exists
|
// evaluate to see whether 'name' exists
|
||||||
} else
|
} else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
|
|
||||||
if (v.type() != nAttrs)
|
if (v.type() != nAttrs)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
auto attr = v.attrs->get(name);
|
auto attr = v.attrs->get(name);
|
||||||
|
|
||||||
|
@ -573,14 +573,14 @@ std::string AttrCursor::getString()
|
||||||
debug("using cached string attribute '%s'", getAttrPathStr());
|
debug("using cached string attribute '%s'", getAttrPathStr());
|
||||||
return s->first;
|
return s->first;
|
||||||
} else
|
} else
|
||||||
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nString && v.type() != nPath)
|
if (v.type() != nString && v.type() != nPath)
|
||||||
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
return v.type() == nString ? v.string.s : v.path().to_string();
|
return v.type() == nString ? v.string.s : v.path().to_string();
|
||||||
}
|
}
|
||||||
|
@ -615,7 +615,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
return *s;
|
return *s;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
else if (v.type() == nPath)
|
else if (v.type() == nPath)
|
||||||
return {v.path().to_string(), {}};
|
return {v.path().to_string(), {}};
|
||||||
else
|
else
|
||||||
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AttrCursor::getBool()
|
bool AttrCursor::getBool()
|
||||||
|
@ -642,14 +642,14 @@ bool AttrCursor::getBool()
|
||||||
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
||||||
return *b;
|
return *b;
|
||||||
} else
|
} else
|
||||||
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nBool)
|
if (v.type() != nBool)
|
||||||
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
}
|
}
|
||||||
|
@ -664,14 +664,14 @@ NixInt AttrCursor::getInt()
|
||||||
debug("using cached integer attribute '%s'", getAttrPathStr());
|
debug("using cached integer attribute '%s'", getAttrPathStr());
|
||||||
return i->x;
|
return i->x;
|
||||||
} else
|
} else
|
||||||
throw TypeError("'%s' is not an integer", getAttrPathStr());
|
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nInt)
|
if (v.type() != nInt)
|
||||||
throw TypeError("'%s' is not an integer", getAttrPathStr());
|
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
return v.integer;
|
return v.integer;
|
||||||
}
|
}
|
||||||
|
@ -686,7 +686,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
|
||||||
debug("using cached list of strings attribute '%s'", getAttrPathStr());
|
debug("using cached list of strings attribute '%s'", getAttrPathStr());
|
||||||
return *l;
|
return *l;
|
||||||
} else
|
} else
|
||||||
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
|
root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,7 +696,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
|
||||||
root->state.forceValue(v, noPos);
|
root->state.forceValue(v, noPos);
|
||||||
|
|
||||||
if (v.type() != nList)
|
if (v.type() != nList)
|
||||||
throw TypeError("'%s' is not a list", getAttrPathStr());
|
root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
std::vector<std::string> res;
|
std::vector<std::string> res;
|
||||||
|
|
||||||
|
@ -719,14 +719,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
||||||
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
||||||
return *attrs;
|
return *attrs;
|
||||||
} else
|
} else
|
||||||
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() != nAttrs)
|
if (v.type() != nAttrs)
|
||||||
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
std::vector<Symbol> attrs;
|
std::vector<Symbol> attrs;
|
||||||
for (auto & attr : *getValue().attrs)
|
for (auto & attr : *getValue().attrs)
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
|
|
||||||
namespace nix::eval_cache {
|
namespace nix::eval_cache {
|
||||||
|
|
||||||
MakeError(CachedEvalError, EvalError);
|
|
||||||
|
|
||||||
struct AttrDb;
|
struct AttrDb;
|
||||||
class AttrCursor;
|
class AttrCursor;
|
||||||
|
|
||||||
|
|
113
src/libexpr/eval-error.cc
Normal file
113
src/libexpr/eval-error.cc
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#include "eval-error.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "value.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
|
||||||
|
{
|
||||||
|
error.withExitStatus(exitStatus);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(PosIdx pos)
|
||||||
|
{
|
||||||
|
error.err.pos = error.state.positions[pos];
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(Value & value, PosIdx fallback)
|
||||||
|
{
|
||||||
|
return atPos(value.determinePos(fallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text)
|
||||||
|
{
|
||||||
|
error.err.traces.push_front(
|
||||||
|
Trace{.pos = error.state.positions[pos], .hint = hintfmt(std::string(text)), .frame = false});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrameTrace(PosIdx pos, const std::string_view text)
|
||||||
|
{
|
||||||
|
error.err.traces.push_front(
|
||||||
|
Trace{.pos = error.state.positions[pos], .hint = hintformat(std::string(text)), .frame = true});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withSuggestions(Suggestions & s)
|
||||||
|
{
|
||||||
|
error.err.suggestions = s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr)
|
||||||
|
{
|
||||||
|
// NOTE: This is abusing side-effects.
|
||||||
|
// TODO: check compatibility with nested debugger calls.
|
||||||
|
// TODO: What side-effects??
|
||||||
|
error.state.debugTraces.push_front(DebugTrace{
|
||||||
|
.pos = error.state.positions[expr.getPos()],
|
||||||
|
.expr = expr,
|
||||||
|
.env = env,
|
||||||
|
.hint = hintformat("Fake frame for debugging purposes"),
|
||||||
|
.isError = true});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
EvalErrorBuilder<T> & EvalErrorBuilder<T>::addTrace(PosIdx pos, hintformat hint, bool frame)
|
||||||
|
{
|
||||||
|
error.addTrace(error.state.positions[pos], hint, frame);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
template<typename... Args>
|
||||||
|
EvalErrorBuilder<T> &
|
||||||
|
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs)
|
||||||
|
{
|
||||||
|
|
||||||
|
addTrace(error.state.positions[pos], hintfmt(std::string(formatString), formatArgs...));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void EvalErrorBuilder<T>::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `EvalState` is the only class that can construct an `EvalErrorBuilder`,
|
||||||
|
// and it does so in dynamic storage. This is the final method called on
|
||||||
|
// any such instancve and must delete itself before throwing the underlying
|
||||||
|
// error.
|
||||||
|
auto error = std::move(this->error);
|
||||||
|
delete this;
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
template class EvalErrorBuilder<EvalError>;
|
||||||
|
template class EvalErrorBuilder<AssertionError>;
|
||||||
|
template class EvalErrorBuilder<ThrownError>;
|
||||||
|
template class EvalErrorBuilder<Abort>;
|
||||||
|
template class EvalErrorBuilder<TypeError>;
|
||||||
|
template class EvalErrorBuilder<UndefinedVarError>;
|
||||||
|
template class EvalErrorBuilder<MissingArgumentError>;
|
||||||
|
template class EvalErrorBuilder<InfiniteRecursionError>;
|
||||||
|
template class EvalErrorBuilder<CachedEvalError>;
|
||||||
|
template class EvalErrorBuilder<InvalidPathError>;
|
||||||
|
|
||||||
|
}
|
119
src/libexpr/eval-error.hh
Normal file
119
src/libexpr/eval-error.hh
Normal file
|
@ -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>),
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "print.hh"
|
#include "print.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "eval-error.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -115,10 +116,11 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
|
||||||
PosIdx pos = getPos();
|
PosIdx pos = getPos();
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nAttrs) {
|
if (v.type() != nAttrs) {
|
||||||
error("expected a set but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a set but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.withTrace(pos, errorCtx).debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +130,11 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (!v.isList()) {
|
if (!v.isList()) {
|
||||||
error("expected a list but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a list but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.withTrace(pos, errorCtx).debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -333,46 +333,6 @@ void initGC()
|
||||||
gcInitialised = true;
|
gcInitialised = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
|
|
||||||
{
|
|
||||||
info.errPos = state.positions[pos];
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
|
|
||||||
{
|
|
||||||
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
|
|
||||||
{
|
|
||||||
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
|
|
||||||
{
|
|
||||||
info.suggestions = s;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
|
|
||||||
{
|
|
||||||
// NOTE: This is abusing side-effects.
|
|
||||||
// TODO: check compatibility with nested debugger calls.
|
|
||||||
state.debugTraces.push_front(DebugTrace {
|
|
||||||
.pos = nullptr,
|
|
||||||
.expr = expr,
|
|
||||||
.env = env,
|
|
||||||
.hint = hintformat("Fake frame for debugging purposes"),
|
|
||||||
.isError = true
|
|
||||||
});
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
EvalState::EvalState(
|
EvalState::EvalState(
|
||||||
const SearchPath & _searchPath,
|
const SearchPath & _searchPath,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
@ -819,7 +779,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
|
||||||
? std::make_unique<DebugTraceStacker>(
|
? std::make_unique<DebugTraceStacker>(
|
||||||
*this,
|
*this,
|
||||||
DebugTrace {
|
DebugTrace {
|
||||||
.pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()],
|
.pos = error->info().pos ? error->info().pos : positions[expr.getPos()],
|
||||||
.expr = expr,
|
.expr = expr,
|
||||||
.env = env,
|
.env = env,
|
||||||
.hint = error->info().msg,
|
.hint = error->info().msg,
|
||||||
|
@ -936,7 +896,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
return j->value;
|
return j->value;
|
||||||
}
|
}
|
||||||
if (!fromWith->parentWith)
|
if (!fromWith->parentWith)
|
||||||
error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
|
error<UndefinedVarError>("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow();
|
||||||
for (size_t l = fromWith->prevWith; l; --l, env = env->up) ;
|
for (size_t l = fromWith->prevWith; l; --l, env = env->up) ;
|
||||||
fromWith = fromWith->parentWith;
|
fromWith = fromWith->parentWith;
|
||||||
}
|
}
|
||||||
|
@ -1162,7 +1122,7 @@ void EvalState::cacheFile(
|
||||||
// computation.
|
// computation.
|
||||||
if (mustBeTrivial &&
|
if (mustBeTrivial &&
|
||||||
!(dynamic_cast<ExprAttrs *>(e)))
|
!(dynamic_cast<ExprAttrs *>(e)))
|
||||||
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
|
error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
|
||||||
eval(e, v);
|
eval(e, v);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
|
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
|
||||||
|
@ -1186,10 +1146,11 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri
|
||||||
Value v;
|
Value v;
|
||||||
e->eval(*this, env, v);
|
e->eval(*this, env, v);
|
||||||
if (v.type() != nBool)
|
if (v.type() != nBool)
|
||||||
error("expected a Boolean but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a Boolean but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.withFrame(env, *e).debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).withFrame(env, *e).debugThrow();
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
@ -1203,10 +1164,11 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
|
||||||
try {
|
try {
|
||||||
e->eval(*this, env, v);
|
e->eval(*this, env, v);
|
||||||
if (v.type() != nAttrs)
|
if (v.type() != nAttrs)
|
||||||
error("expected a set but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a set but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.withFrame(env, *e).debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).withFrame(env, *e).debugThrow();
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
throw;
|
throw;
|
||||||
|
@ -1315,7 +1277,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
auto nameSym = state.symbols.create(nameVal.string.s);
|
auto nameSym = state.symbols.create(nameVal.string.s);
|
||||||
Bindings::iterator j = v.attrs->find(nameSym);
|
Bindings::iterator j = v.attrs->find(nameSym);
|
||||||
if (j != v.attrs->end())
|
if (j != v.attrs->end())
|
||||||
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
|
state.error<EvalError>("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow();
|
||||||
|
|
||||||
i.valueExpr->setName(nameSym);
|
i.valueExpr->setName(nameSym);
|
||||||
/* Keep sorted order so find can catch duplicates */
|
/* Keep sorted order so find can catch duplicates */
|
||||||
|
@ -1440,8 +1402,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
for (auto & attr : *vAttrs->attrs)
|
for (auto & attr : *vAttrs->attrs)
|
||||||
allAttrNames.insert(state.symbols[attr.name]);
|
allAttrNames.insert(state.symbols[attr.name]);
|
||||||
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
|
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
|
||||||
state.error("attribute '%1%' missing", state.symbols[name])
|
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
|
||||||
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
|
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vAttrs = j->value;
|
vAttrs = j->value;
|
||||||
|
@ -1514,7 +1476,7 @@ public:
|
||||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||||
{
|
{
|
||||||
if (callDepth > evalSettings.maxCallDepth)
|
if (callDepth > evalSettings.maxCallDepth)
|
||||||
error("stack overflow; max-call-depth exceeded").atPos(pos).template debugThrow<EvalError>();
|
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
|
||||||
CallDepth _level(callDepth);
|
CallDepth _level(callDepth);
|
||||||
|
|
||||||
auto trace = evalSettings.traceFunctionCalls
|
auto trace = evalSettings.traceFunctionCalls
|
||||||
|
@ -1572,13 +1534,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
auto j = args[0]->attrs->get(i.name);
|
auto j = args[0]->attrs->get(i.name);
|
||||||
if (!j) {
|
if (!j) {
|
||||||
if (!i.def) {
|
if (!i.def) {
|
||||||
error("function '%1%' called without required argument '%2%'",
|
error<TypeError>("function '%1%' called without required argument '%2%'",
|
||||||
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||||
symbols[i.name])
|
symbols[i.name])
|
||||||
.atPos(lambda.pos)
|
.atPos(lambda.pos)
|
||||||
.withTrace(pos, "from call site")
|
.withTrace(pos, "from call site")
|
||||||
.withFrame(*fun.lambda.env, lambda)
|
.withFrame(*fun.lambda.env, lambda)
|
||||||
.debugThrow<TypeError>();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1598,14 +1560,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
for (auto & formal : lambda.formals->formals)
|
for (auto & formal : lambda.formals->formals)
|
||||||
formalNames.insert(symbols[formal.name]);
|
formalNames.insert(symbols[formal.name]);
|
||||||
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
|
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
|
||||||
error("function '%1%' called with unexpected argument '%2%'",
|
error<TypeError>("function '%1%' called with unexpected argument '%2%'",
|
||||||
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||||
symbols[i.name])
|
symbols[i.name])
|
||||||
.atPos(lambda.pos)
|
.atPos(lambda.pos)
|
||||||
.withTrace(pos, "from call site")
|
.withTrace(pos, "from call site")
|
||||||
.withSuggestions(suggestions)
|
.withSuggestions(suggestions)
|
||||||
.withFrame(*fun.lambda.env, lambda)
|
.withFrame(*fun.lambda.env, lambda)
|
||||||
.debugThrow<TypeError>();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
abort(); // can't happen
|
abort(); // can't happen
|
||||||
}
|
}
|
||||||
|
@ -1737,11 +1699,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
error("attempt to call something which is not a function but %1%: %2%",
|
error<TypeError>(
|
||||||
|
"attempt to call something which is not a function but %1%: %2%",
|
||||||
showType(vCur),
|
showType(vCur),
|
||||||
ValuePrinter(*this, vCur, errorPrintOptions))
|
ValuePrinter(*this, vCur, errorPrintOptions))
|
||||||
.atPos(pos)
|
.atPos(pos)
|
||||||
.debugThrow<TypeError>();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
vRes = vCur;
|
vRes = vCur;
|
||||||
|
@ -1823,12 +1786,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
||||||
if (j != args.end()) {
|
if (j != args.end()) {
|
||||||
attrs.insert(*j);
|
attrs.insert(*j);
|
||||||
} else if (!i.def) {
|
} else if (!i.def) {
|
||||||
error(R"(cannot evaluate a function that has an argument without a value ('%1%')
|
error<MissingArgumentError>(R"(cannot evaluate a function that has an argument without a value ('%1%')
|
||||||
Nix attempted to evaluate a function as a top level expression; in
|
Nix attempted to evaluate a function as a top level expression; in
|
||||||
this case it must have its arguments supplied either by default
|
this case it must have its arguments supplied either by default
|
||||||
values, or passed explicitly with '--arg' or '--argstr'. See
|
values, or passed explicitly with '--arg' or '--argstr'. See
|
||||||
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
|
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
|
||||||
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
|
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1859,7 +1822,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
cond->show(state.symbols, out);
|
cond->show(state.symbols, out);
|
||||||
state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
|
state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
|
||||||
}
|
}
|
||||||
body->eval(state, env, v);
|
body->eval(state, env, v);
|
||||||
}
|
}
|
||||||
|
@ -2037,14 +2000,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
nf = n;
|
nf = n;
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
|
||||||
} else if (firstType == nFloat) {
|
} else if (firstType == nFloat) {
|
||||||
if (vTmp.type() == nInt) {
|
if (vTmp.type() == nInt) {
|
||||||
nf += vTmp.integer;
|
nf += vTmp.integer;
|
||||||
} else if (vTmp.type() == nFloat) {
|
} else if (vTmp.type() == nFloat) {
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
|
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
|
||||||
} else {
|
} else {
|
||||||
if (s.empty()) s.reserve(es->size());
|
if (s.empty()) s.reserve(es->size());
|
||||||
/* skip canonization of first path, which would only be not
|
/* skip canonization of first path, which would only be not
|
||||||
|
@ -2066,7 +2029,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
v.mkFloat(nf);
|
v.mkFloat(nf);
|
||||||
else if (firstType == nPath) {
|
else if (firstType == nPath) {
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
|
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
|
||||||
v.mkPath(CanonPath(canonPath(str())));
|
v.mkPath(CanonPath(canonPath(str())));
|
||||||
} else
|
} else
|
||||||
v.mkStringMove(c_str(), context);
|
v.mkStringMove(c_str(), context);
|
||||||
|
@ -2081,8 +2044,9 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
|
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
state.error("infinite recursion encountered")
|
state.error<InfiniteRecursionError>("infinite recursion encountered")
|
||||||
.debugThrow<InfiniteRecursionError>();
|
.atPos(v.determinePos(noPos))
|
||||||
|
.debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// always force this to be separate, otherwise forceValue may inline it and take
|
// always force this to be separate, otherwise forceValue may inline it and take
|
||||||
|
@ -2096,7 +2060,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(e);
|
std::rethrow_exception(e);
|
||||||
} catch (InfiniteRecursionError & e) {
|
} catch (InfiniteRecursionError & e) {
|
||||||
e.err.errPos = positions[pos];
|
e.atPos(positions[pos]);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2144,15 +2108,18 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
|
||||||
try {
|
try {
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nInt)
|
if (v.type() != nInt)
|
||||||
error("expected an integer but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected an integer but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
return v.integer;
|
return v.integer;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return v.integer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2163,10 +2130,11 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
|
||||||
if (v.type() == nInt)
|
if (v.type() == nInt)
|
||||||
return v.integer;
|
return v.integer;
|
||||||
else if (v.type() != nFloat)
|
else if (v.type() != nFloat)
|
||||||
error("expected a float but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a float but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
return v.fpoint;
|
return v.fpoint;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
@ -2180,15 +2148,18 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
|
||||||
try {
|
try {
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nBool)
|
if (v.type() != nBool)
|
||||||
error("expected a Boolean but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a Boolean but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
return v.boolean;
|
return v.boolean;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return v.boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2203,10 +2174,11 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro
|
||||||
try {
|
try {
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nFunction && !isFunctor(v))
|
if (v.type() != nFunction && !isFunctor(v))
|
||||||
error("expected a function but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a function but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
throw;
|
throw;
|
||||||
|
@ -2219,10 +2191,11 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
|
||||||
try {
|
try {
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nString)
|
if (v.type() != nString)
|
||||||
error("expected a string but found %1%: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"expected a string but found %1%: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
.debugThrow<TypeError>();
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
return v.string.s;
|
return v.string.s;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(positions[pos], errorCtx);
|
e.addTrace(positions[pos], errorCtx);
|
||||||
|
@ -2251,7 +2224,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos, errorCtx);
|
auto s = forceString(v, pos, errorCtx);
|
||||||
if (v.string.context) {
|
if (v.string.context) {
|
||||||
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -2316,11 +2289,13 @@ BackedStringView EvalState::coerceToString(
|
||||||
return std::move(*maybeString);
|
return std::move(*maybeString);
|
||||||
auto i = v.attrs->find(sOutPath);
|
auto i = v.attrs->find(sOutPath);
|
||||||
if (i == v.attrs->end()) {
|
if (i == v.attrs->end()) {
|
||||||
error("cannot coerce %1% to a string: %2%",
|
error<TypeError>(
|
||||||
showType(v),
|
"cannot coerce %1% to a string: %2%",
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
showType(v),
|
||||||
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
)
|
||||||
.withTrace(pos, errorCtx)
|
.withTrace(pos, errorCtx)
|
||||||
.debugThrow<TypeError>();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
return coerceToString(pos, *i->value, context, errorCtx,
|
return coerceToString(pos, *i->value, context, errorCtx,
|
||||||
coerceMore, copyToStore, canonicalizePath);
|
coerceMore, copyToStore, canonicalizePath);
|
||||||
|
@ -2328,7 +2303,7 @@ BackedStringView EvalState::coerceToString(
|
||||||
|
|
||||||
if (v.type() == nExternal) {
|
if (v.type() == nExternal) {
|
||||||
try {
|
try {
|
||||||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
|
return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(nullptr, errorCtx);
|
e.addTrace(nullptr, errorCtx);
|
||||||
throw;
|
throw;
|
||||||
|
@ -2364,18 +2339,19 @@ BackedStringView EvalState::coerceToString(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error("cannot coerce %1% to a string: %2%",
|
error<TypeError>("cannot coerce %1% to a string: %2%",
|
||||||
showType(v),
|
showType(v),
|
||||||
ValuePrinter(*this, v, errorPrintOptions))
|
ValuePrinter(*this, v, errorPrintOptions)
|
||||||
|
)
|
||||||
.withTrace(pos, errorCtx)
|
.withTrace(pos, errorCtx)
|
||||||
.debugThrow<TypeError>();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
|
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
|
||||||
{
|
{
|
||||||
if (nix::isDerivation(path.path.abs()))
|
if (nix::isDerivation(path.path.abs()))
|
||||||
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
|
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
|
||||||
|
|
||||||
auto i = srcToStore.find(path);
|
auto i = srcToStore.find(path);
|
||||||
|
|
||||||
|
@ -2400,7 +2376,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
||||||
if (path == "" || path[0] != '/')
|
if (path == "" || path[0] != '/')
|
||||||
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
error<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
|
||||||
return CanonPath(path);
|
return CanonPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2410,7 +2386,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon
|
||||||
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
||||||
if (auto storePath = store->maybeParseStorePath(path))
|
if (auto storePath = store->maybeParseStorePath(path))
|
||||||
return *storePath;
|
return *storePath;
|
||||||
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
error<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2420,18 +2396,18 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
|
||||||
auto s = forceString(v, context, pos, errorCtx);
|
auto s = forceString(v, context, pos, errorCtx);
|
||||||
auto csize = context.size();
|
auto csize = context.size();
|
||||||
if (csize != 1)
|
if (csize != 1)
|
||||||
error(
|
error<EvalError>(
|
||||||
"string '%s' has %d entries in its context. It should only have exactly one entry",
|
"string '%s' has %d entries in its context. It should only have exactly one entry",
|
||||||
s, csize)
|
s, csize)
|
||||||
.withTrace(pos, errorCtx).debugThrow<EvalError>();
|
.withTrace(pos, errorCtx).debugThrow();
|
||||||
auto derivedPath = std::visit(overloaded {
|
auto derivedPath = std::visit(overloaded {
|
||||||
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
|
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
|
||||||
return std::move(o);
|
return std::move(o);
|
||||||
},
|
},
|
||||||
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
|
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
|
||||||
error(
|
error<EvalError>(
|
||||||
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time",
|
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time",
|
||||||
s).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
s).withTrace(pos, errorCtx).debugThrow();
|
||||||
},
|
},
|
||||||
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
|
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
|
||||||
return std::move(b);
|
return std::move(b);
|
||||||
|
@ -2454,16 +2430,16 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
|
||||||
error message. */
|
error message. */
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const SingleDerivedPath::Opaque & o) {
|
[&](const SingleDerivedPath::Opaque & o) {
|
||||||
error(
|
error<EvalError>(
|
||||||
"path string '%s' has context with the different path '%s'",
|
"path string '%s' has context with the different path '%s'",
|
||||||
s, sExpected)
|
s, sExpected)
|
||||||
.withTrace(pos, errorCtx).debugThrow<EvalError>();
|
.withTrace(pos, errorCtx).debugThrow();
|
||||||
},
|
},
|
||||||
[&](const SingleDerivedPath::Built & b) {
|
[&](const SingleDerivedPath::Built & b) {
|
||||||
error(
|
error<EvalError>(
|
||||||
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
|
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
|
||||||
s, b.output, b.drvPath->to_string(*store), sExpected)
|
s, b.output, b.drvPath->to_string(*store), sExpected)
|
||||||
.withTrace(pos, errorCtx).debugThrow<EvalError>();
|
.withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
}, derivedPath.raw());
|
}, derivedPath.raw());
|
||||||
}
|
}
|
||||||
|
@ -2545,7 +2521,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
||||||
|
|
||||||
case nThunk: // Must not be left by forceValue
|
case nThunk: // Must not be left by forceValue
|
||||||
default:
|
default:
|
||||||
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2782,13 +2758,12 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
|
||||||
if (hasPrefix(path, "nix/"))
|
if (hasPrefix(path, "nix/"))
|
||||||
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
|
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
|
||||||
|
|
||||||
debugThrow(ThrownError({
|
error<ThrownError>(
|
||||||
.msg = hintfmt(evalSettings.pureEval
|
evalSettings.pureEval
|
||||||
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
||||||
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
||||||
path),
|
path
|
||||||
.errPos = positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}), 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2858,11 +2833,11 @@ Expr * EvalState::parse(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
state.error<TypeError>(
|
||||||
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this)
|
"cannot coerce %1% to a string: %2%", showType(), *this
|
||||||
});
|
).atPos(pos).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "attr-set.hh"
|
#include "attr-set.hh"
|
||||||
|
#include "eval-error.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
|
@ -150,45 +151,6 @@ struct DebugTrace {
|
||||||
bool isError;
|
bool isError;
|
||||||
};
|
};
|
||||||
|
|
||||||
void debugError(Error * e, Env & env, Expr & expr);
|
|
||||||
|
|
||||||
class ErrorBuilder
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
EvalState & state;
|
|
||||||
ErrorInfo info;
|
|
||||||
|
|
||||||
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
|
|
||||||
|
|
||||||
public:
|
|
||||||
template<typename... Args>
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
static ErrorBuilder * create(EvalState & s, const Args & ... args)
|
|
||||||
{
|
|
||||||
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
ErrorBuilder & atPos(PosIdx pos);
|
|
||||||
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
|
|
||||||
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
|
|
||||||
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
ErrorBuilder & withSuggestions(Suggestions & s);
|
|
||||||
|
|
||||||
[[nodiscard, gnu::noinline]]
|
|
||||||
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
|
|
||||||
|
|
||||||
template<class ErrorType>
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void debugThrow();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -262,39 +224,10 @@ public:
|
||||||
|
|
||||||
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
|
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
|
||||||
|
|
||||||
template<class E>
|
template<class T, typename... Args>
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void debugThrowLastTrace(E && error)
|
|
||||||
{
|
|
||||||
debugThrow(error, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class E>
|
|
||||||
[[gnu::noinline, gnu::noreturn]]
|
|
||||||
void debugThrow(E && error, const Env * env, const Expr * expr)
|
|
||||||
{
|
|
||||||
if (debugRepl && ((env && expr) || !debugTraces.empty())) {
|
|
||||||
if (!env || !expr) {
|
|
||||||
const DebugTrace & last = debugTraces.front();
|
|
||||||
env = &last.env;
|
|
||||||
expr = &last.expr;
|
|
||||||
}
|
|
||||||
runDebugRepl(&error, *env, *expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::move(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is dangerous, but gets in line with the idea that error creation and
|
|
||||||
// throwing should not allocate on the stack of hot functions.
|
|
||||||
// as long as errors are immediately thrown, it works.
|
|
||||||
ErrorBuilder * errorBuilder;
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
[[nodiscard, gnu::noinline]]
|
[[nodiscard, gnu::noinline]]
|
||||||
ErrorBuilder & error(const Args & ... args) {
|
EvalErrorBuilder<T> & error(const Args & ... args) {
|
||||||
errorBuilder = ErrorBuilder::create(*this, args...);
|
return *new EvalErrorBuilder<T>(*this, args...);
|
||||||
return *errorBuilder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -840,23 +773,8 @@ std::string showType(const Value & v);
|
||||||
*/
|
*/
|
||||||
SourcePath resolveExprPath(SourcePath path);
|
SourcePath resolveExprPath(SourcePath path);
|
||||||
|
|
||||||
struct InvalidPathError : EvalError
|
|
||||||
{
|
|
||||||
Path path;
|
|
||||||
InvalidPathError(const Path & path);
|
|
||||||
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
|
||||||
~InvalidPathError() throw () { };
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
||||||
|
|
||||||
template<class ErrorType>
|
|
||||||
void ErrorBuilder::debugThrow()
|
|
||||||
{
|
|
||||||
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
|
|
||||||
state.debugThrowLastTrace(ErrorType(info));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,8 +140,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
|
attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||||
state.symbols[attr.name], showType(*attr.value));
|
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||||
}
|
}
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
|
@ -288,15 +288,15 @@ static Flake getFlake(
|
||||||
std::vector<std::string> ss;
|
std::vector<std::string> ss;
|
||||||
for (auto elem : setting.value->listItems()) {
|
for (auto elem : setting.value->listItems()) {
|
||||||
if (elem->type() != nString)
|
if (elem->type() != nString)
|
||||||
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
|
state.error<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
|
||||||
state.symbols[setting.name], showType(*setting.value));
|
state.symbols[setting.name], showType(*setting.value)).debugThrow();
|
||||||
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
|
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
|
||||||
}
|
}
|
||||||
flake.config.settings.emplace(state.symbols[setting.name], ss);
|
flake.config.settings.emplace(state.symbols[setting.name], ss);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw TypeError("flake configuration setting '%s' is %s",
|
state.error<TypeError>("flake configuration setting '%s' is %s",
|
||||||
state.symbols[setting.name], showType(*setting.value));
|
state.symbols[setting.name], showType(*setting.value)).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,11 +857,11 @@ static void prim_flakeRefToString(
|
||||||
attrs.emplace(state.symbols[attr.name],
|
attrs.emplace(state.symbols[attr.name],
|
||||||
std::string(attr.value->str()));
|
std::string(attr.value->str()));
|
||||||
} else {
|
} else {
|
||||||
state.error(
|
state.error<EvalError>(
|
||||||
"flake reference attribute sets may only contain integers, Booleans, "
|
"flake reference attribute sets may only contain integers, Booleans, "
|
||||||
"and strings, but attribute '%s' is %s",
|
"and strings, but attribute '%s' is %s",
|
||||||
state.symbols[attr.name],
|
state.symbols[attr.name],
|
||||||
showType(*attr.value)).debugThrow<EvalError>();
|
showType(*attr.value)).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
||||||
|
|
|
@ -50,7 +50,7 @@ std::string DrvInfo::queryName() const
|
||||||
{
|
{
|
||||||
if (name == "" && attrs) {
|
if (name == "" && attrs) {
|
||||||
auto i = attrs->find(state->sName);
|
auto i = attrs->find(state->sName);
|
||||||
if (i == attrs->end()) throw TypeError("derivation name missing");
|
if (i == attrs->end()) state->error<TypeError>("derivation name missing").debugThrow();
|
||||||
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
|
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
|
@ -397,7 +397,8 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)");
|
else
|
||||||
|
state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "json-to-value.hh"
|
#include "json-to-value.hh"
|
||||||
|
#include "value.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
@ -159,7 +161,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
|
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
|
||||||
throw JSONParseError(ex.what());
|
throw JSONParseError("%s", ex.what());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "error.hh"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
MakeError(JSONParseError, EvalError);
|
class EvalState;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
MakeError(JSONParseError, Error);
|
||||||
|
|
||||||
void parseJSON(EvalState & state, const std::string_view & s, Value & v);
|
void parseJSON(EvalState & state, const std::string_view & s, Value & v);
|
||||||
|
|
||||||
|
|
|
@ -152,9 +152,9 @@ or { return OR_KW; }
|
||||||
try {
|
try {
|
||||||
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
||||||
} catch (const boost::bad_lexical_cast &) {
|
} catch (const boost::bad_lexical_cast &) {
|
||||||
throw ParseError({
|
throw ParseError(ErrorInfo{
|
||||||
.msg = hintfmt("invalid integer '%1%'", yytext),
|
.msg = hintfmt("invalid integer '%1%'", yytext),
|
||||||
.errPos = state->positions[CUR_POS],
|
.pos = state->positions[CUR_POS],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return INT;
|
return INT;
|
||||||
|
@ -162,9 +162,9 @@ or { return OR_KW; }
|
||||||
{FLOAT} { errno = 0;
|
{FLOAT} { errno = 0;
|
||||||
yylval->nf = strtod(yytext, 0);
|
yylval->nf = strtod(yytext, 0);
|
||||||
if (errno != 0)
|
if (errno != 0)
|
||||||
throw ParseError({
|
throw ParseError(ErrorInfo{
|
||||||
.msg = hintfmt("invalid float '%1%'", yytext),
|
.msg = hintfmt("invalid float '%1%'", yytext),
|
||||||
.errPos = state->positions[CUR_POS],
|
.pos = state->positions[CUR_POS],
|
||||||
});
|
});
|
||||||
return FLOAT;
|
return FLOAT;
|
||||||
}
|
}
|
||||||
|
@ -291,9 +291,9 @@ or { return OR_KW; }
|
||||||
|
|
||||||
<INPATH_SLASH>{ANY} |
|
<INPATH_SLASH>{ANY} |
|
||||||
<INPATH_SLASH><<EOF>> {
|
<INPATH_SLASH><<EOF>> {
|
||||||
throw ParseError({
|
throw ParseError(ErrorInfo{
|
||||||
.msg = hintfmt("path has a trailing slash"),
|
.msg = hintfmt("path has a trailing slash"),
|
||||||
.errPos = state->positions[CUR_POS],
|
.pos = state->positions[CUR_POS],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -294,10 +294,10 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||||
enclosing `with'. If there is no `with', then we can issue an
|
enclosing `with'. If there is no `with', then we can issue an
|
||||||
"undefined variable" error now. */
|
"undefined variable" error now. */
|
||||||
if (withLevel == -1)
|
if (withLevel == -1)
|
||||||
throw UndefinedVarError({
|
es.error<UndefinedVarError>(
|
||||||
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
|
"undefined variable '%1%'",
|
||||||
.errPos = es.positions[pos]
|
es.symbols[name]
|
||||||
});
|
).atPos(pos).debugThrow();
|
||||||
for (auto * e = env.get(); e && !fromWith; e = e->up)
|
for (auto * e = env.get(); e && !fromWith; e = e->up)
|
||||||
fromWith = e->isWith;
|
fromWith = e->isWith;
|
||||||
this->level = withLevel;
|
this->level = withLevel;
|
||||||
|
|
|
@ -9,29 +9,13 @@
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
#include "chunked-vector.hh"
|
#include "chunked-vector.hh"
|
||||||
#include "position.hh"
|
#include "position.hh"
|
||||||
|
#include "eval-error.hh"
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
#include "pos-table.hh"
|
#include "pos-table.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
MakeError(EvalError, Error);
|
|
||||||
MakeError(ParseError, Error);
|
|
||||||
MakeError(AssertionError, EvalError);
|
|
||||||
MakeError(ThrownError, AssertionError);
|
|
||||||
MakeError(Abort, EvalError);
|
|
||||||
MakeError(TypeError, EvalError);
|
|
||||||
MakeError(UndefinedVarError, Error);
|
|
||||||
MakeError(MissingArgumentError, EvalError);
|
|
||||||
MakeError(RestrictedPathError, Error);
|
|
||||||
|
|
||||||
class InfiniteRecursionError : public EvalError
|
|
||||||
{
|
|
||||||
friend class EvalState;
|
|
||||||
public:
|
|
||||||
using EvalError::EvalError;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Env;
|
struct Env;
|
||||||
struct Value;
|
struct Value;
|
||||||
class EvalState;
|
class EvalState;
|
||||||
|
|
|
@ -65,7 +65,7 @@ inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, co
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("attribute '%1%' already defined at %2%",
|
.msg = hintfmt("attribute '%1%' already defined at %2%",
|
||||||
showAttrPath(symbols, attrPath), positions[prevPos]),
|
showAttrPath(symbols, attrPath), positions[prevPos]),
|
||||||
.errPos = positions[pos]
|
.pos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx pre
|
||||||
{
|
{
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
|
.msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
|
||||||
.errPos = positions[pos]
|
.pos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,13 +154,13 @@ inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Sym
|
||||||
if (duplicate)
|
if (duplicate)
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
|
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
|
||||||
.errPos = positions[duplicate->second]
|
.pos = positions[duplicate->second]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (arg && formals->has(arg))
|
if (arg && formals->has(arg))
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]),
|
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]),
|
||||||
.errPos = positions[pos]
|
.pos = positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
return formals;
|
return formals;
|
||||||
|
|
|
@ -64,7 +64,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
|
||||||
{
|
{
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt(error),
|
.msg = hintfmt(error),
|
||||||
.errPos = state->positions[state->at(*loc)]
|
.pos = state->positions[state->at(*loc)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ expr_function
|
||||||
{ if (!$2->dynamicAttrs.empty())
|
{ if (!$2->dynamicAttrs.empty())
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("dynamic attributes not allowed in let"),
|
.msg = hintfmt("dynamic attributes not allowed in let"),
|
||||||
.errPos = state->positions[CUR_POS]
|
.pos = state->positions[CUR_POS]
|
||||||
});
|
});
|
||||||
$$ = new ExprLet($2, $4);
|
$$ = new ExprLet($2, $4);
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ expr_simple
|
||||||
if (noURLLiterals)
|
if (noURLLiterals)
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("URL literals are disabled"),
|
.msg = hintfmt("URL literals are disabled"),
|
||||||
.errPos = state->positions[CUR_POS]
|
.pos = state->positions[CUR_POS]
|
||||||
});
|
});
|
||||||
$$ = new ExprString(std::string($1));
|
$$ = new ExprString(std::string($1));
|
||||||
}
|
}
|
||||||
|
@ -339,7 +339,7 @@ attrs
|
||||||
} else
|
} else
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("dynamic attributes not allowed in inherit"),
|
.msg = hintfmt("dynamic attributes not allowed in inherit"),
|
||||||
.errPos = state->positions[state->at(@2)]
|
.pos = state->positions[state->at(@2)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
| { $$ = new AttrPath; }
|
| { $$ = new AttrPath; }
|
||||||
|
|
|
@ -37,10 +37,6 @@ namespace nix {
|
||||||
* Miscellaneous
|
* Miscellaneous
|
||||||
*************************************************************/
|
*************************************************************/
|
||||||
|
|
||||||
|
|
||||||
InvalidPathError::InvalidPathError(const Path & path) :
|
|
||||||
EvalError("path '%s' is not valid", path), path(path) {}
|
|
||||||
|
|
||||||
StringMap EvalState::realiseContext(const NixStringContext & context)
|
StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||||
{
|
{
|
||||||
std::vector<DerivedPath::Built> drvs;
|
std::vector<DerivedPath::Built> drvs;
|
||||||
|
@ -49,7 +45,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||||
for (auto & c : context) {
|
for (auto & c : context) {
|
||||||
auto ensureValid = [&](const StorePath & p) {
|
auto ensureValid = [&](const StorePath & p) {
|
||||||
if (!store->isValidPath(p))
|
if (!store->isValidPath(p))
|
||||||
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
|
error<InvalidPathError>(store->printStorePath(p)).debugThrow();
|
||||||
};
|
};
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const NixStringContextElem::Built & b) {
|
[&](const NixStringContextElem::Built & b) {
|
||||||
|
@ -76,9 +72,10 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||||
if (drvs.empty()) return {};
|
if (drvs.empty()) return {};
|
||||||
|
|
||||||
if (!evalSettings.enableImportFromDerivation)
|
if (!evalSettings.enableImportFromDerivation)
|
||||||
debugThrowLastTrace(Error(
|
error<EvalError>(
|
||||||
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
|
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
|
||||||
drvs.begin()->to_string(*store)));
|
drvs.begin()->to_string(*store)
|
||||||
|
).debugThrow();
|
||||||
|
|
||||||
/* Build/substitute the context. */
|
/* Build/substitute the context. */
|
||||||
std::vector<DerivedPath> buildReqs;
|
std::vector<DerivedPath> buildReqs;
|
||||||
|
@ -345,16 +342,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
|
||||||
|
|
||||||
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (!handle)
|
if (!handle)
|
||||||
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
|
state.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
|
||||||
|
|
||||||
dlerror();
|
dlerror();
|
||||||
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
||||||
if(!func) {
|
if(!func) {
|
||||||
char *message = dlerror();
|
char *message = dlerror();
|
||||||
if (message)
|
if (message)
|
||||||
state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
|
state.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow();
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
|
state.error<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
(func)(state, v);
|
(func)(state, v);
|
||||||
|
@ -370,7 +367,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
auto elems = args[0]->listElems();
|
auto elems = args[0]->listElems();
|
||||||
auto count = args[0]->listSize();
|
auto count = args[0]->listSize();
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
|
state.error<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow();
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto program = state.coerceToString(pos, *elems[0], context,
|
auto program = state.coerceToString(pos, *elems[0], context,
|
||||||
"while evaluating the first element of the argument passed to builtins.exec",
|
"while evaluating the first element of the argument passed to builtins.exec",
|
||||||
|
@ -385,7 +382,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
try {
|
try {
|
||||||
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
|
state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output = runProgram(program, true, commandArgs);
|
auto output = runProgram(program, true, commandArgs);
|
||||||
|
@ -587,7 +584,7 @@ struct CompareValues
|
||||||
if (v1->type() == nInt && v2->type() == nFloat)
|
if (v1->type() == nInt && v2->type() == nFloat)
|
||||||
return v1->integer < v2->fpoint;
|
return v1->integer < v2->fpoint;
|
||||||
if (v1->type() != v2->type())
|
if (v1->type() != v2->type())
|
||||||
state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>();
|
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
|
||||||
// Allow selecting a subset of enum values
|
// Allow selecting a subset of enum values
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||||
|
@ -612,7 +609,7 @@ struct CompareValues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
|
state.error<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow();
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
@ -639,7 +636,7 @@ static Bindings::iterator getAttr(
|
||||||
{
|
{
|
||||||
Bindings::iterator value = attrSet->find(attrSym);
|
Bindings::iterator value = attrSet->find(attrSym);
|
||||||
if (value == attrSet->end()) {
|
if (value == attrSet->end()) {
|
||||||
state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
|
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -752,7 +749,7 @@ static RegisterPrimOp primop_break({
|
||||||
auto error = Error(ErrorInfo {
|
auto error = Error(ErrorInfo {
|
||||||
.level = lvlInfo,
|
.level = lvlInfo,
|
||||||
.msg = hintfmt("breakpoint reached"),
|
.msg = hintfmt("breakpoint reached"),
|
||||||
.errPos = state.positions[pos],
|
.pos = state.positions[pos],
|
||||||
});
|
});
|
||||||
|
|
||||||
auto & dt = state.debugTraces.front();
|
auto & dt = state.debugTraces.front();
|
||||||
|
@ -763,7 +760,7 @@ static RegisterPrimOp primop_break({
|
||||||
throw Error(ErrorInfo{
|
throw Error(ErrorInfo{
|
||||||
.level = lvlInfo,
|
.level = lvlInfo,
|
||||||
.msg = hintfmt("quit the debugger"),
|
.msg = hintfmt("quit the debugger"),
|
||||||
.errPos = nullptr,
|
.pos = nullptr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -784,7 +781,7 @@ static RegisterPrimOp primop_abort({
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context,
|
auto s = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtins.abort").toOwned();
|
"while evaluating the error message passed to builtins.abort").toOwned();
|
||||||
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
|
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -803,7 +800,7 @@ static RegisterPrimOp primop_throw({
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context,
|
auto s = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtin.throw").toOwned();
|
"while evaluating the error message passed to builtin.throw").toOwned();
|
||||||
state.debugThrowLastTrace(ThrownError(s));
|
state.error<ThrownError>(s).debugThrow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1122,37 +1119,33 @@ drvName, Bindings * attrs, Value & v)
|
||||||
experimentalFeatureSettings.require(Xp::DynamicDerivations);
|
experimentalFeatureSettings.require(Xp::DynamicDerivations);
|
||||||
ingestionMethod = TextIngestionMethod {};
|
ingestionMethod = TextIngestionMethod {};
|
||||||
} else
|
} else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
"invalid value '%s' for 'outputHashMode' attribute", s
|
||||||
.errPos = state.positions[noPos]
|
).atPos(v).debugThrow();
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto handleOutputs = [&](const Strings & ss) {
|
auto handleOutputs = [&](const Strings & ss) {
|
||||||
outputs.clear();
|
outputs.clear();
|
||||||
for (auto & j : ss) {
|
for (auto & j : ss) {
|
||||||
if (outputs.find(j) != outputs.end())
|
if (outputs.find(j) != outputs.end())
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("duplicate derivation output '%1%'", j)
|
||||||
.msg = hintfmt("duplicate derivation output '%1%'", j),
|
.atPos(v)
|
||||||
.errPos = state.positions[noPos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
/* !!! Check whether j is a valid attribute
|
/* !!! Check whether j is a valid attribute
|
||||||
name. */
|
name. */
|
||||||
/* Derivations cannot be named ‘drv’, because
|
/* Derivations cannot be named ‘drv’, because
|
||||||
then we'd have an attribute ‘drvPath’ in
|
then we'd have an attribute ‘drvPath’ in
|
||||||
the resulting set. */
|
the resulting set. */
|
||||||
if (j == "drv")
|
if (j == "drv")
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("invalid derivation output name 'drv'")
|
||||||
.msg = hintfmt("invalid derivation output name 'drv'" ),
|
.atPos(v)
|
||||||
.errPos = state.positions[noPos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
outputs.insert(j);
|
outputs.insert(j);
|
||||||
}
|
}
|
||||||
if (outputs.empty())
|
if (outputs.empty())
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("derivation cannot have an empty set of outputs")
|
||||||
.msg = hintfmt("derivation cannot have an empty set of outputs"),
|
.atPos(v)
|
||||||
.errPos = state.positions[noPos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1275,16 +1268,14 @@ drvName, Bindings * attrs, Value & v)
|
||||||
|
|
||||||
/* Do we have all required attributes? */
|
/* Do we have all required attributes? */
|
||||||
if (drv.builder == "")
|
if (drv.builder == "")
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("required attribute 'builder' missing")
|
||||||
.msg = hintfmt("required attribute 'builder' missing"),
|
.atPos(v)
|
||||||
.errPos = state.positions[noPos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
|
|
||||||
if (drv.platform == "")
|
if (drv.platform == "")
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("required attribute 'system' missing")
|
||||||
.msg = hintfmt("required attribute 'system' missing"),
|
.atPos(v)
|
||||||
.errPos = state.positions[noPos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
|
|
||||||
/* Check whether the derivation name is valid. */
|
/* Check whether the derivation name is valid. */
|
||||||
if (isDerivation(drvName) &&
|
if (isDerivation(drvName) &&
|
||||||
|
@ -1292,10 +1283,10 @@ drvName, Bindings * attrs, Value & v)
|
||||||
outputs.size() == 1 &&
|
outputs.size() == 1 &&
|
||||||
*(outputs.begin()) == "out"))
|
*(outputs.begin()) == "out"))
|
||||||
{
|
{
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension),
|
"derivation names are allowed to end in '%s' only if they produce a single derivation file",
|
||||||
.errPos = state.positions[noPos]
|
drvExtension
|
||||||
}));
|
).atPos(v).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputHash) {
|
if (outputHash) {
|
||||||
|
@ -1304,10 +1295,9 @@ drvName, Bindings * attrs, Value & v)
|
||||||
Ignore `__contentAddressed` because fixed output derivations are
|
Ignore `__contentAddressed` because fixed output derivations are
|
||||||
already content addressed. */
|
already content addressed. */
|
||||||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||||
state.debugThrowLastTrace(Error({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
"multiple outputs are not supported in fixed-output derivations"
|
||||||
.errPos = state.positions[noPos]
|
).atPos(v).debugThrow();
|
||||||
}));
|
|
||||||
|
|
||||||
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||||
|
|
||||||
|
@ -1326,10 +1316,8 @@ drvName, Bindings * attrs, Value & v)
|
||||||
|
|
||||||
else if (contentAddressed || isImpure) {
|
else if (contentAddressed || isImpure) {
|
||||||
if (contentAddressed && isImpure)
|
if (contentAddressed && isImpure)
|
||||||
throw EvalError({
|
state.error<EvalError>("derivation cannot be both content-addressed and impure")
|
||||||
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
|
.atPos(v).debugThrow();
|
||||||
.errPos = state.positions[noPos]
|
|
||||||
});
|
|
||||||
|
|
||||||
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
||||||
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
|
||||||
|
@ -1370,10 +1358,10 @@ drvName, Bindings * attrs, Value & v)
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
auto h = get(hashModulo.hashes, i);
|
auto h = get(hashModulo.hashes, i);
|
||||||
if (!h)
|
if (!h)
|
||||||
throw AssertionError({
|
state.error<AssertionError>(
|
||||||
.msg = hintfmt("derivation produced no hash for output '%s'", i),
|
"derivation produced no hash for output '%s'",
|
||||||
.errPos = state.positions[noPos],
|
i
|
||||||
});
|
).atPos(v).debugThrow();
|
||||||
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
||||||
drv.env[i] = state.store->printStorePath(outPath);
|
drv.env[i] = state.store->printStorePath(outPath);
|
||||||
drv.outputs.insert_or_assign(
|
drv.outputs.insert_or_assign(
|
||||||
|
@ -1479,10 +1467,10 @@ static RegisterPrimOp primop_toPath({
|
||||||
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
if (evalSettings.pureEval)
|
if (evalSettings.pureEval)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
|
"'%s' is not allowed in pure evaluation mode",
|
||||||
.errPos = state.positions[pos]
|
"builtins.storePath"
|
||||||
}));
|
).atPos(pos).debugThrow();
|
||||||
|
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
|
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
|
||||||
|
@ -1492,10 +1480,8 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
if (!state.store->isStorePath(path.abs()))
|
if (!state.store->isStorePath(path.abs()))
|
||||||
path = CanonPath(canonPath(path.abs(), true));
|
path = CanonPath(canonPath(path.abs(), true));
|
||||||
if (!state.store->isInStore(path.abs()))
|
if (!state.store->isInStore(path.abs()))
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("path '%1%' is not in the Nix store", path)
|
||||||
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
.atPos(pos).debugThrow();
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
auto path2 = state.store->toStorePath(path.abs()).first;
|
auto path2 = state.store->toStorePath(path.abs()).first;
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(path2);
|
state.store->ensurePath(path2);
|
||||||
|
@ -1623,7 +1609,10 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
auto path = realisePath(state, pos, *args[0]);
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
auto s = path.readFile();
|
auto s = path.readFile();
|
||||||
if (s.find((char) 0) != std::string::npos)
|
if (s.find((char) 0) != std::string::npos)
|
||||||
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
|
state.error<EvalError>(
|
||||||
|
"the contents of the file '%1%' cannot be represented as a Nix string",
|
||||||
|
path
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
StorePathSet refs;
|
StorePathSet refs;
|
||||||
if (state.store->isInStore(path.path.abs())) {
|
if (state.store->isInStore(path.path.abs())) {
|
||||||
try {
|
try {
|
||||||
|
@ -1680,10 +1669,11 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
auto rewrites = state.realiseContext(context);
|
auto rewrites = state.realiseContext(context);
|
||||||
path = rewriteStrings(path, rewrites);
|
path = rewriteStrings(path, rewrites);
|
||||||
} catch (InvalidPathError & e) {
|
} catch (InvalidPathError & e) {
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
|
"cannot find '%1%', since path '%2%' is not valid",
|
||||||
.errPos = state.positions[pos]
|
path,
|
||||||
}));
|
e.path
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPath.elements.emplace_back(SearchPath::Elem {
|
searchPath.elements.emplace_back(SearchPath::Elem {
|
||||||
|
@ -1752,10 +1742,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
|
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
|
||||||
std::optional<HashType> ht = parseHashType(type);
|
std::optional<HashType> ht = parseHashType(type);
|
||||||
if (!ht)
|
if (!ht)
|
||||||
state.debugThrowLastTrace(Error({
|
state.error<EvalError>("unknown hash type '%1%'", type).atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("unknown hash type '%1%'", type),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
auto path = realisePath(state, pos, *args[1]);
|
auto path = realisePath(state, pos, *args[1]);
|
||||||
|
|
||||||
|
@ -2075,13 +2062,12 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
|
||||||
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
|
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
|
||||||
refs.insert(p->path);
|
refs.insert(p->path);
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt(
|
"files created by %1% may not reference derivations, but %2% references %3%",
|
||||||
"in 'toFile': the file named '%1%' must not contain a reference "
|
"builtins.toFile",
|
||||||
"to a derivation but contains (%2%)",
|
name,
|
||||||
name, c.to_string()),
|
c.to_string()
|
||||||
.errPos = state.positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto storePath = settings.readOnlyMode
|
auto storePath = settings.readOnlyMode
|
||||||
|
@ -2240,7 +2226,10 @@ static void addPath(
|
||||||
auto dstPath = fetchToStore(
|
auto dstPath = fetchToStore(
|
||||||
*state.store, state.rootPath(CanonPath(path)), name, method, &filter, state.repair);
|
*state.store, state.rootPath(CanonPath(path)), name, method, &filter, state.repair);
|
||||||
if (expectedHash && expectedStorePath != dstPath)
|
if (expectedHash && expectedStorePath != dstPath)
|
||||||
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
|
state.error<EvalError>(
|
||||||
|
"store path mismatch in (possibly filtered) path added from '%s'",
|
||||||
|
path
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
state.allowAndSetStorePathString(dstPath, v);
|
state.allowAndSetStorePathString(dstPath, v);
|
||||||
} else
|
} else
|
||||||
state.allowAndSetStorePathString(*expectedStorePath, v);
|
state.allowAndSetStorePathString(*expectedStorePath, v);
|
||||||
|
@ -2339,16 +2328,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
|
||||||
else if (n == "sha256")
|
else if (n == "sha256")
|
||||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), htSHA256);
|
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), htSHA256);
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
|
"unsupported argument '%1%' to 'addPath'",
|
||||||
.errPos = state.positions[attr.pos]
|
state.symbols[attr.name]
|
||||||
}));
|
).atPos(attr.pos).debugThrow();
|
||||||
}
|
}
|
||||||
if (!path)
|
if (!path)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
|
"missing required 'path' attribute in the first argument to builtins.path"
|
||||||
.errPos = state.positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}));
|
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
name = path->baseName();
|
name = path->baseName();
|
||||||
|
|
||||||
|
@ -2766,10 +2754,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!args[0]->isLambda())
|
if (!args[0]->isLambda())
|
||||||
state.debugThrowLastTrace(TypeError({
|
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("'functionArgs' requires a function"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!args[0]->lambda.fun->hasFormals()) {
|
if (!args[0]->lambda.fun->hasFormals()) {
|
||||||
v.mkAttrs(&state.emptyBindings);
|
v.mkAttrs(&state.emptyBindings);
|
||||||
|
@ -2939,10 +2924,10 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
|
||||||
{
|
{
|
||||||
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
||||||
if (n < 0 || (unsigned int) n >= list.listSize())
|
if (n < 0 || (unsigned int) n >= list.listSize())
|
||||||
state.debugThrowLastTrace(Error({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("list index %1% is out of bounds", n),
|
"list index %1% is out of bounds",
|
||||||
.errPos = state.positions[pos]
|
n
|
||||||
}));
|
).atPos(pos).debugThrow();
|
||||||
state.forceValue(*list.listElems()[n], pos);
|
state.forceValue(*list.listElems()[n], pos);
|
||||||
v = *list.listElems()[n];
|
v = *list.listElems()[n];
|
||||||
}
|
}
|
||||||
|
@ -2987,10 +2972,7 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
||||||
if (args[0]->listSize() == 0)
|
if (args[0]->listSize() == 0)
|
||||||
state.debugThrowLastTrace(Error({
|
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("'tail' called on an empty list"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
state.mkList(v, args[0]->listSize() - 1);
|
state.mkList(v, args[0]->listSize() - 1);
|
||||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||||
|
@ -3240,7 +3222,7 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
|
||||||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||||
|
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
|
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
|
||||||
|
|
||||||
// More strict than striclty (!) necessary, but acceptable
|
// More strict than striclty (!) necessary, but acceptable
|
||||||
// as evaluating map without accessing any values makes little sense.
|
// as evaluating map without accessing any values makes little sense.
|
||||||
|
@ -3557,10 +3539,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
|
||||||
|
|
||||||
NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
|
NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
|
||||||
if (f2 == 0)
|
if (f2 == 0)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("division by zero").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("division by zero"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||||
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
|
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
|
||||||
|
@ -3569,10 +3548,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
|
||||||
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
|
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
|
||||||
/* Avoid division overflow as it might raise SIGFPE. */
|
/* Avoid division overflow as it might raise SIGFPE. */
|
||||||
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("overflow in integer division"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
v.mkInt(i1 / i2);
|
v.mkInt(i1 / i2);
|
||||||
}
|
}
|
||||||
|
@ -3703,10 +3679,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
|
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
|
||||||
|
|
||||||
if (start < 0)
|
if (start < 0)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("negative start position in 'substring'"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
|
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
|
||||||
|
@ -3770,10 +3743,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
|
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
|
||||||
std::optional<HashType> ht = parseHashType(type);
|
std::optional<HashType> ht = parseHashType(type);
|
||||||
if (!ht)
|
if (!ht)
|
||||||
state.debugThrowLastTrace(Error({
|
state.error<EvalError>("unknown hash algorithm '%1%'", type).atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("unknown hash type '%1%'", type),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
|
|
||||||
NixStringContext context; // discarded
|
NixStringContext context; // discarded
|
||||||
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
|
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
|
||||||
|
@ -3843,15 +3813,13 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
} catch (std::regex_error & e) {
|
} catch (std::regex_error & e) {
|
||||||
if (e.code() == std::regex_constants::error_space) {
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
|
||||||
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
.atPos(pos)
|
||||||
.errPos = state.positions[pos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
} else
|
} else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("invalid regular expression '%s'", re)
|
||||||
.msg = hintfmt("invalid regular expression '%s'", re),
|
.atPos(pos)
|
||||||
.errPos = state.positions[pos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3947,15 +3915,13 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
} catch (std::regex_error & e) {
|
} catch (std::regex_error & e) {
|
||||||
if (e.code() == std::regex_constants::error_space) {
|
if (e.code() == std::regex_constants::error_space) {
|
||||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
|
||||||
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
.atPos(pos)
|
||||||
.errPos = state.positions[pos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
} else
|
} else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("invalid regular expression '%s'", re)
|
||||||
.msg = hintfmt("invalid regular expression '%s'", re),
|
.atPos(pos)
|
||||||
.errPos = state.positions[pos]
|
.debugThrow();
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4031,7 +3997,9 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
||||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
|
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
|
||||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
|
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
|
||||||
if (args[0]->listSize() != args[1]->listSize())
|
if (args[0]->listSize() != args[1]->listSize())
|
||||||
state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
|
state.error<EvalError>(
|
||||||
|
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
|
|
||||||
std::vector<std::string> from;
|
std::vector<std::string> from;
|
||||||
from.reserve(args[0]->listSize());
|
from.reserve(args[0]->listSize());
|
||||||
|
|
|
@ -98,30 +98,30 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
|
||||||
|
|
||||||
auto contextSize = context.size();
|
auto contextSize = context.size();
|
||||||
if (contextSize != 1) {
|
if (contextSize != 1) {
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("context of string '%s' must have exactly one element, but has %d", *s, contextSize),
|
"context of string '%s' must have exactly one element, but has %d",
|
||||||
.errPos = state.positions[pos]
|
*s,
|
||||||
});
|
contextSize
|
||||||
|
).atPos(pos).debugThrow();
|
||||||
}
|
}
|
||||||
NixStringContext context2 {
|
NixStringContext context2 {
|
||||||
(NixStringContextElem { std::visit(overloaded {
|
(NixStringContextElem { std::visit(overloaded {
|
||||||
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
|
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
|
||||||
if (!c.path.isDerivation()) {
|
if (!c.path.isDerivation()) {
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("path '%s' is not a derivation",
|
"path '%s' is not a derivation",
|
||||||
state.store->printStorePath(c.path)),
|
state.store->printStorePath(c.path)
|
||||||
.errPos = state.positions[pos],
|
).atPos(pos).debugThrow();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return NixStringContextElem::DrvDeep {
|
return NixStringContextElem::DrvDeep {
|
||||||
.drvPath = c.path,
|
.drvPath = c.path,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
|
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output),
|
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
|
||||||
.errPos = state.positions[pos],
|
c.output
|
||||||
});
|
).atPos(pos).debugThrow();
|
||||||
},
|
},
|
||||||
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
|
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
|
||||||
/* Reuse original item because we want this to be idempotent. */
|
/* Reuse original item because we want this to be idempotent. */
|
||||||
|
@ -261,10 +261,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
for (auto & i : *args[1]->attrs) {
|
for (auto & i : *args[1]->attrs) {
|
||||||
const auto & name = state.symbols[i.name];
|
const auto & name = state.symbols[i.name];
|
||||||
if (!state.store->isStorePath(name))
|
if (!state.store->isStorePath(name))
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("context key '%s' is not a store path", name),
|
"context key '%s' is not a store path",
|
||||||
.errPos = state.positions[i.pos]
|
name
|
||||||
});
|
).atPos(i.pos).debugThrow();
|
||||||
auto namePath = state.store->parseStorePath(name);
|
auto namePath = state.store->parseStorePath(name);
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(namePath);
|
state.store->ensurePath(namePath);
|
||||||
|
@ -281,10 +281,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
|
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
|
||||||
if (!isDerivation(name)) {
|
if (!isDerivation(name)) {
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
|
"tried to add all-outputs context of %s, which is not a derivation, to a string",
|
||||||
.errPos = state.positions[i.pos]
|
name
|
||||||
});
|
).atPos(i.pos).debugThrow();
|
||||||
}
|
}
|
||||||
context.emplace(NixStringContextElem::DrvDeep {
|
context.emplace(NixStringContextElem::DrvDeep {
|
||||||
.drvPath = namePath,
|
.drvPath = namePath,
|
||||||
|
@ -296,10 +296,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
|
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
|
||||||
if (iter->value->listSize() && !isDerivation(name)) {
|
if (iter->value->listSize() && !isDerivation(name)) {
|
||||||
throw EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
|
"tried to add derivation output context of %s, which is not a derivation, to a string",
|
||||||
.errPos = state.positions[i.pos]
|
name
|
||||||
});
|
).atPos(i.pos).debugThrow();
|
||||||
}
|
}
|
||||||
for (auto elem : iter->value->listItems()) {
|
for (auto elem : iter->value->listItems()) {
|
||||||
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
||||||
|
|
|
@ -26,7 +26,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
|
||||||
state.store->printStorePath(fromPath),
|
state.store->printStorePath(fromPath),
|
||||||
state.store->printStorePath(rewrittenPath),
|
state.store->printStorePath(rewrittenPath),
|
||||||
state.store->printStorePath(*toPathMaybe)),
|
state.store->printStorePath(*toPathMaybe)),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
if (!toPathMaybe)
|
if (!toPathMaybe)
|
||||||
throw Error({
|
throw Error({
|
||||||
|
@ -35,7 +35,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
|
||||||
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
|
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
|
||||||
state.store->printStorePath(fromPath),
|
state.store->printStorePath(fromPath),
|
||||||
state.store->printStorePath(rewrittenPath)),
|
state.store->printStorePath(rewrittenPath)),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
|
||||||
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
|
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
|
||||||
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
|
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
|
||||||
state.store->printStorePath(toPath)),
|
state.store->printStorePath(toPath)),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
|
||||||
"to the 'fetchClosure' arguments.\n\n"
|
"to the 'fetchClosure' arguments.\n\n"
|
||||||
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
|
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
|
||||||
state.store->printStorePath(fromPath)),
|
state.store->printStorePath(fromPath)),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId
|
||||||
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
|
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
|
||||||
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
|
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
|
||||||
state.store->printStorePath(fromPath)),
|
state.store->printStorePath(fromPath)),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,14 +153,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
else
|
else
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
|
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fromPath)
|
if (!fromPath)
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
bool inputAddressed = inputAddressedMaybe.value_or(false);
|
bool inputAddressed = inputAddressedMaybe.value_or(false);
|
||||||
|
@ -171,14 +171,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
.msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
|
.msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
|
||||||
"inputAddressed",
|
"inputAddressed",
|
||||||
"toPath"),
|
"toPath"),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fromStoreUrl)
|
if (!fromStoreUrl)
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
auto parsedURL = parseURL(*fromStoreUrl);
|
auto parsedURL = parseURL(*fromStoreUrl);
|
||||||
|
@ -188,13 +188,13 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
|
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
|
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!parsedURL.query.empty())
|
if (!parsedURL.query.empty())
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
|
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
|
||||||
.errPos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
auto fromStore = openStore(parsedURL.to_string());
|
auto fromStore = openStore(parsedURL.to_string());
|
||||||
|
|
|
@ -38,17 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
|
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
|
||||||
else
|
else
|
||||||
throw EvalError({
|
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
|
||||||
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
|
|
||||||
.errPos = state.positions[attr.pos]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.empty())
|
if (url.empty())
|
||||||
throw EvalError({
|
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("'url' argument required"),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
});
|
|
||||||
|
|
||||||
} else
|
} else
|
||||||
url = state.coerceToString(pos, *args[0], context,
|
url = state.coerceToString(pos, *args[0], context,
|
||||||
|
|
|
@ -123,16 +123,14 @@ static void fetchTree(
|
||||||
|
|
||||||
if (auto aType = args[0]->attrs->get(state.sType)) {
|
if (auto aType = args[0]->attrs->get(state.sType)) {
|
||||||
if (type)
|
if (type)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("unexpected attribute 'type'"),
|
"unexpected attribute 'type'"
|
||||||
.errPos = state.positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}));
|
|
||||||
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
||||||
} else if (!type)
|
} else if (!type)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
"attribute 'type' is missing in call to 'fetchTree'"
|
||||||
.errPos = state.positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}));
|
|
||||||
|
|
||||||
attrs.emplace("type", type.value());
|
attrs.emplace("type", type.value());
|
||||||
|
|
||||||
|
@ -153,16 +151,15 @@ static void fetchTree(
|
||||||
else if (attr.value->type() == nInt)
|
else if (attr.value->type() == nInt)
|
||||||
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
|
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||||
state.symbols[attr.name], showType(*attr.value)));
|
state.symbols[attr.name], showType(*attr.value)).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.allowNameArgument)
|
if (!params.allowNameArgument)
|
||||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
|
"attribute 'name' isn’t supported in call to 'fetchTree'"
|
||||||
.errPos = state.positions[pos]
|
).atPos(pos).debugThrow();
|
||||||
}));
|
|
||||||
|
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
} else {
|
} else {
|
||||||
|
@ -183,8 +180,9 @@ static void fetchTree(
|
||||||
if (!evalSettings.pureEval && !input.isDirect())
|
if (!evalSettings.pureEval && !input.isDirect())
|
||||||
input = lookupInRegistries(state.store, input).first;
|
input = lookupInRegistries(state.store, input).first;
|
||||||
|
|
||||||
if (evalSettings.pureEval && !input.isLocked())
|
if (evalSettings.pureEval && !input.isLocked()) {
|
||||||
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
|
state.error<EvalError>("in pure evaluation mode, 'fetchTree' requires a locked input").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
auto [tree, input2] = input.fetch(state.store);
|
||||||
|
|
||||||
|
@ -225,17 +223,13 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
|
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
|
||||||
else
|
else
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
|
||||||
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
|
.atPos(pos).debugThrow();
|
||||||
.errPos = state.positions[attr.pos]
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url)
|
if (!url)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.error<EvalError>(
|
||||||
.msg = hintfmt("'url' argument required"),
|
"'url' argument required").atPos(pos).debugThrow();
|
||||||
.errPos = state.positions[pos]
|
|
||||||
}));
|
|
||||||
} else
|
} else
|
||||||
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||||
|
|
||||||
|
@ -248,7 +242,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
name = baseNameOf(*url);
|
name = baseNameOf(*url);
|
||||||
|
|
||||||
if (evalSettings.pureEval && !expectedHash)
|
if (evalSettings.pureEval && !expectedHash)
|
||||||
state.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
|
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
|
||||||
|
|
||||||
// early exit if pinned and already in the store
|
// early exit if pinned and already in the store
|
||||||
if (expectedHash && expectedHash->type == htSHA256) {
|
if (expectedHash && expectedHash->type == htSHA256) {
|
||||||
|
@ -277,9 +271,15 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
auto hash = unpack
|
auto hash = unpack
|
||||||
? state.store->queryPathInfo(storePath)->narHash
|
? state.store->queryPathInfo(storePath)->narHash
|
||||||
: hashFile(htSHA256, state.store->toRealPath(storePath));
|
: hashFile(htSHA256, state.store->toRealPath(storePath));
|
||||||
if (hash != *expectedHash)
|
if (hash != *expectedHash) {
|
||||||
state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
|
state.error<EvalError>(
|
||||||
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
|
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
|
||||||
|
*url,
|
||||||
|
expectedHash->to_string(Base32, true),
|
||||||
|
hash.to_string(Base32, true)
|
||||||
|
).withExitStatus(102)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.allowAndSetStorePathString(storePath, v);
|
state.allowAndSetStorePathString(storePath, v);
|
||||||
|
|
|
@ -83,10 +83,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
try {
|
try {
|
||||||
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
|
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
|
||||||
} catch (std::exception & e) { // TODO: toml::syntax_error
|
} catch (std::exception & e) { // TODO: toml::syntax_error
|
||||||
throw EvalError({
|
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
|
||||||
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
|
|
||||||
.errPos = state.positions[pos]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
try {
|
try {
|
||||||
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
|
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({},
|
e.addTrace(state.positions[pos],
|
||||||
hintfmt("while evaluating list element at index %1%", i));
|
hintfmt("while evaluating list element at index %1%", i));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -99,13 +99,12 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
|
|
||||||
case nThunk:
|
case nThunk:
|
||||||
case nFunction:
|
case nFunction:
|
||||||
auto e = TypeError({
|
state.error<TypeError>(
|
||||||
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
|
"cannot convert %1% to JSON",
|
||||||
.errPos = state.positions[v.determinePos(pos)]
|
showType(v)
|
||||||
});
|
)
|
||||||
e.addTrace(state.positions[pos], hintfmt("message for the trace"));
|
.atPos(v.determinePos(pos))
|
||||||
state.debugThrowLastTrace(e);
|
.debugThrow();
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +118,8 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||||
NixStringContext & context, bool copyToStore) const
|
NixStringContext & context, bool copyToStore) const
|
||||||
{
|
{
|
||||||
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
|
state.error<TypeError>("cannot convert %1% to JSON", showType())
|
||||||
|
.debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ class ExternalValueBase
|
||||||
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
||||||
* error.
|
* error.
|
||||||
*/
|
*/
|
||||||
virtual std::string coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
|
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare to another value of the same type. Defaults to uncomparable,
|
* Compare to another value of the same type. Defaults to uncomparable,
|
||||||
|
|
|
@ -339,7 +339,7 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
return 1;
|
return 1;
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
logError(e.info());
|
logError(e.info());
|
||||||
return e.status;
|
return e.info().status;
|
||||||
} catch (std::bad_alloc & e) {
|
} catch (std::bad_alloc & e) {
|
||||||
printError(error + "out of memory");
|
printError(error + "out of memory");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -33,7 +33,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failed.size() == 1 && ex) {
|
if (failed.size() == 1 && ex) {
|
||||||
ex->status = worker.failingExitStatus();
|
ex->withExitStatus(worker.failingExitStatus());
|
||||||
throw std::move(*ex);
|
throw std::move(*ex);
|
||||||
} else if (!failed.empty()) {
|
} else if (!failed.empty()) {
|
||||||
if (ex) logError(ex->info());
|
if (ex) logError(ex->info());
|
||||||
|
@ -104,7 +104,7 @@ void Store::ensurePath(const StorePath & path)
|
||||||
|
|
||||||
if (goal->exitCode != Goal::ecSuccess) {
|
if (goal->exitCode != Goal::ecSuccess) {
|
||||||
if (goal->ex) {
|
if (goal->ex) {
|
||||||
goal->ex->status = worker.failingExitStatus();
|
goal->ex->withExitStatus(worker.failingExitStatus());
|
||||||
throw std::move(*goal->ex);
|
throw std::move(*goal->ex);
|
||||||
} else
|
} else
|
||||||
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
throw Error(worker.failingExitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
|
||||||
|
|
|
@ -119,7 +119,7 @@ struct TunnelLogger : public Logger
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 26) {
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 26) {
|
||||||
to << STDERR_ERROR << *ex;
|
to << STDERR_ERROR << *ex;
|
||||||
} else {
|
} else {
|
||||||
to << STDERR_ERROR << ex->what() << ex->status;
|
to << STDERR_ERROR << ex->what() << ex->info().status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||||
* try {
|
* try {
|
||||||
* e->eval(*this, env, v);
|
* e->eval(*this, env, v);
|
||||||
* if (v.type() != nAttrs)
|
* if (v.type() != nAttrs)
|
||||||
* throwTypeError("expected a set but found %1%", v);
|
* error<TypeError>("expected a set but found %1%", v);
|
||||||
* } catch (Error & e) {
|
* } catch (Error & e) {
|
||||||
* e.addTrace(pos, errorCtx);
|
* e.addTrace(pos, errorCtx);
|
||||||
* throw;
|
* throw;
|
||||||
|
@ -348,7 +348,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||||
* e->eval(*this, env, v);
|
* e->eval(*this, env, v);
|
||||||
* try {
|
* try {
|
||||||
* if (v.type() != nAttrs)
|
* if (v.type() != nAttrs)
|
||||||
* throwTypeError("expected a set but found %1%", v);
|
* error<TypeError>("expected a set but found %1%", v);
|
||||||
* } catch (Error & e) {
|
* } catch (Error & e) {
|
||||||
* e.addTrace(pos, errorCtx);
|
* e.addTrace(pos, errorCtx);
|
||||||
* throw;
|
* throw;
|
||||||
|
@ -410,7 +410,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
||||||
|
|
||||||
oss << einfo.msg << "\n";
|
oss << einfo.msg << "\n";
|
||||||
|
|
||||||
printPosMaybe(oss, "", einfo.errPos);
|
printPosMaybe(oss, "", einfo.pos);
|
||||||
|
|
||||||
auto suggestions = einfo.suggestions.trim();
|
auto suggestions = einfo.suggestions.trim();
|
||||||
if (!suggestions.suggestions.empty()) {
|
if (!suggestions.suggestions.empty()) {
|
||||||
|
|
|
@ -84,9 +84,14 @@ inline bool operator>=(const Trace& lhs, const Trace& rhs);
|
||||||
struct ErrorInfo {
|
struct ErrorInfo {
|
||||||
Verbosity level;
|
Verbosity level;
|
||||||
hintformat msg;
|
hintformat msg;
|
||||||
std::shared_ptr<Pos> errPos;
|
std::shared_ptr<Pos> pos;
|
||||||
std::list<Trace> traces;
|
std::list<Trace> traces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exit status.
|
||||||
|
*/
|
||||||
|
unsigned int status = 1;
|
||||||
|
|
||||||
Suggestions suggestions;
|
Suggestions suggestions;
|
||||||
|
|
||||||
static std::optional<std::string> programName;
|
static std::optional<std::string> programName;
|
||||||
|
@ -103,18 +108,21 @@ class BaseError : public std::exception
|
||||||
protected:
|
protected:
|
||||||
mutable ErrorInfo err;
|
mutable ErrorInfo err;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached formatted contents of `err.msg`.
|
||||||
|
*/
|
||||||
mutable std::optional<std::string> what_;
|
mutable std::optional<std::string> what_;
|
||||||
|
/**
|
||||||
|
* Format `err.msg` and set `what_` to the resulting value.
|
||||||
|
*/
|
||||||
const std::string & calcWhat() const;
|
const std::string & calcWhat() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
unsigned int status = 1; // exit status
|
|
||||||
|
|
||||||
BaseError(const BaseError &) = default;
|
BaseError(const BaseError &) = default;
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
BaseError(unsigned int status, const Args & ... args)
|
BaseError(unsigned int status, const Args & ... args)
|
||||||
: err { .level = lvlError, .msg = hintfmt(args...) }
|
: err { .level = lvlError, .msg = hintfmt(args...), .status = status }
|
||||||
, status(status)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
|
@ -149,6 +157,15 @@ public:
|
||||||
const std::string & msg() const { return calcWhat(); }
|
const std::string & msg() const { return calcWhat(); }
|
||||||
const ErrorInfo & info() const { calcWhat(); return err; }
|
const ErrorInfo & info() const { calcWhat(); return err; }
|
||||||
|
|
||||||
|
void withExitStatus(unsigned int status)
|
||||||
|
{
|
||||||
|
err.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void atPos(std::shared_ptr<Pos> pos) {
|
||||||
|
err.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
void pushTrace(Trace trace)
|
void pushTrace(Trace trace)
|
||||||
{
|
{
|
||||||
err.traces.push_front(trace);
|
err.traces.push_front(trace);
|
||||||
|
|
|
@ -196,7 +196,7 @@ struct JSONLogger : Logger {
|
||||||
json["level"] = ei.level;
|
json["level"] = ei.level;
|
||||||
json["msg"] = oss.str();
|
json["msg"] = oss.str();
|
||||||
json["raw_msg"] = ei.msg.str();
|
json["raw_msg"] = ei.msg.str();
|
||||||
to_json(json, ei.errPos);
|
to_json(json, ei.pos);
|
||||||
|
|
||||||
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
|
if (loggerSettings.showTrace.get() && !ei.traces.empty()) {
|
||||||
nlohmann::json traces = nlohmann::json::array();
|
nlohmann::json traces = nlohmann::json::array();
|
||||||
|
|
|
@ -932,8 +932,8 @@ static void opServe(Strings opFlags, Strings opArgs)
|
||||||
store->buildPaths(toDerivedPaths(paths));
|
store->buildPaths(toDerivedPaths(paths));
|
||||||
out << 0;
|
out << 0;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
assert(e.status);
|
assert(e.info().status);
|
||||||
out << e.status << e.msg();
|
out << e.info().status << e.msg();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]);
|
state->error<TypeError>("value at '%s' is not a string or an attribute set", state->positions[pos]).debugThrow();
|
||||||
};
|
};
|
||||||
|
|
||||||
recurse(*v, pos, *writeTo);
|
recurse(*v, pos, *writeTo);
|
||||||
|
|
|
@ -808,10 +808,10 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
|
||||||
auto templateDir = templateDirAttr->getString();
|
auto templateDir = templateDirAttr->getString();
|
||||||
|
|
||||||
if (!store->isInStore(templateDir))
|
if (!store->isInStore(templateDir))
|
||||||
throw TypeError(
|
evalState->error<TypeError>(
|
||||||
"'%s' was not found in the Nix store\n"
|
"'%s' was not found in the Nix store\n"
|
||||||
"If you've set '%s' to a string, try using a path instead.",
|
"If you've set '%s' to a string, try using a path instead.",
|
||||||
templateDir, templateDirAttr->getAttrPathStr());
|
templateDir, templateDirAttr->getAttrPathStr()).debugThrow();
|
||||||
|
|
||||||
std::vector<Path> changedFiles;
|
std::vector<Path> changedFiles;
|
||||||
std::vector<Path> conflictedFiles;
|
std::vector<Path> conflictedFiles;
|
||||||
|
@ -1281,7 +1281,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||||
{
|
{
|
||||||
auto aType = visitor.maybeGetAttr("type");
|
auto aType = visitor.maybeGetAttr("type");
|
||||||
if (!aType || aType->getString() != "app")
|
if (!aType || aType->getString() != "app")
|
||||||
throw EvalError("not an app definition");
|
state->error<EvalError>("not an app definition").debugThrow();
|
||||||
if (json) {
|
if (json) {
|
||||||
j.emplace("type", "app");
|
j.emplace("type", "app");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,3 +14,8 @@ error:
|
||||||
8|
|
8|
|
||||||
|
|
||||||
error: expected a string but found an integer: 1
|
error: expected a string but found an integer: 1
|
||||||
|
at /pwd/lang/eval-fail-attr-name-type.nix:7:17:
|
||||||
|
6| in
|
||||||
|
7| attrs.puppy.${key}
|
||||||
|
| ^
|
||||||
|
8|
|
||||||
|
|
|
@ -5,4 +5,4 @@ error:
|
||||||
| ^
|
| ^
|
||||||
2| key = "value"
|
2| key = "value"
|
||||||
|
|
||||||
error: while parsing a TOML string: Dates and times are not supported
|
error: while parsing TOML: Dates and times are not supported
|
||||||
|
|
|
@ -20,6 +20,11 @@ error:
|
||||||
3| true
|
3| true
|
||||||
|
|
||||||
… while evaluating list element at index 3
|
… while evaluating list element at index 3
|
||||||
|
at /pwd/lang/eval-fail-toJSON.nix:2:3:
|
||||||
|
1| builtins.toJSON {
|
||||||
|
2| a.b = [
|
||||||
|
| ^
|
||||||
|
3| true
|
||||||
|
|
||||||
… while evaluating attribute 'c'
|
… while evaluating attribute 'c'
|
||||||
at /pwd/lang/eval-fail-toJSON.nix:7:7:
|
at /pwd/lang/eval-fail-toJSON.nix:7:7:
|
||||||
|
|
|
@ -7,3 +7,8 @@ error:
|
||||||
6|
|
6|
|
||||||
|
|
||||||
error: expected a string but found a set: { }
|
error: expected a string but found a set: { }
|
||||||
|
at /pwd/lang/eval-fail-using-set-as-attr-name.nix:5:10:
|
||||||
|
4| in
|
||||||
|
5| attr.${key}
|
||||||
|
| ^
|
||||||
|
6|
|
||||||
|
|
|
@ -12,33 +12,33 @@ namespace nix {
|
||||||
|
|
||||||
TEST_F(ErrorTraceTest, TraceBuilder) {
|
TEST_F(ErrorTraceTest, TraceBuilder) {
|
||||||
ASSERT_THROW(
|
ASSERT_THROW(
|
||||||
state.error("Not much").debugThrow<EvalError>(),
|
state.error<EvalError>("puppy").debugThrow(),
|
||||||
EvalError
|
EvalError
|
||||||
);
|
);
|
||||||
|
|
||||||
ASSERT_THROW(
|
ASSERT_THROW(
|
||||||
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>(),
|
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow(),
|
||||||
EvalError
|
EvalError
|
||||||
);
|
);
|
||||||
|
|
||||||
ASSERT_THROW(
|
ASSERT_THROW(
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>();
|
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(state.positions[noPos], "Something", "");
|
e.addTrace(state.positions[noPos], "beans", "");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
ASSERT_EQ(PrintToString(e.info().msg),
|
ASSERT_EQ(PrintToString(e.info().msg),
|
||||||
PrintToString(hintfmt("Not much")));
|
PrintToString(hintfmt("puppy")));
|
||||||
auto trace = e.info().traces.rbegin();
|
auto trace = e.info().traces.rbegin();
|
||||||
ASSERT_EQ(e.info().traces.size(), 2);
|
ASSERT_EQ(e.info().traces.size(), 2);
|
||||||
ASSERT_EQ(PrintToString(trace->hint),
|
ASSERT_EQ(PrintToString(trace->hint),
|
||||||
PrintToString(hintfmt("No more")));
|
PrintToString(hintfmt("doggy")));
|
||||||
trace++;
|
trace++;
|
||||||
ASSERT_EQ(PrintToString(trace->hint),
|
ASSERT_EQ(PrintToString(trace->hint),
|
||||||
PrintToString(hintfmt("Something")));
|
PrintToString(hintfmt("beans")));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
, EvalError
|
, EvalError
|
||||||
|
@ -47,12 +47,12 @@ namespace nix {
|
||||||
|
|
||||||
TEST_F(ErrorTraceTest, NestedThrows) {
|
TEST_F(ErrorTraceTest, NestedThrows) {
|
||||||
try {
|
try {
|
||||||
state.error("Not much").withTrace(noPos, "No more").debugThrow<EvalError>();
|
state.error<EvalError>("puppy").withTrace(noPos, "doggy").debugThrow();
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
try {
|
try {
|
||||||
state.error("Not much more").debugThrow<EvalError>();
|
state.error<EvalError>("beans").debugThrow();
|
||||||
} catch (Error & e2) {
|
} catch (Error & e2) {
|
||||||
e.addTrace(state.positions[noPos], "Something", "");
|
e.addTrace(state.positions[noPos], "beans2", "");
|
||||||
//e2.addTrace(state.positions[noPos], "Something", "");
|
//e2.addTrace(state.positions[noPos], "Something", "");
|
||||||
ASSERT_TRUE(e.info().traces.size() == 2);
|
ASSERT_TRUE(e.info().traces.size() == 2);
|
||||||
ASSERT_TRUE(e2.info().traces.size() == 0);
|
ASSERT_TRUE(e2.info().traces.size() == 0);
|
||||||
|
|
Loading…
Reference in a new issue