aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/attr-path.cc16
-rw-r--r--src/libexpr/attr-path.hh4
-rw-r--r--src/libexpr/attr-set.cc6
-rw-r--r--src/libexpr/attr-set.hh50
-rw-r--r--src/libexpr/eval-cache.cc201
-rw-r--r--src/libexpr/eval-cache.hh11
-rw-r--r--src/libexpr/eval-inline.hh47
-rw-r--r--src/libexpr/eval.cc979
-rw-r--r--src/libexpr/eval.hh241
-rw-r--r--src/libexpr/fetchurl.nix8
-rw-r--r--src/libexpr/flake/call-flake.nix2
-rw-r--r--src/libexpr/flake/config.cc17
-rw-r--r--src/libexpr/flake/flake.cc172
-rw-r--r--src/libexpr/flake/flake.hh6
-rw-r--r--src/libexpr/flake/flakeref.cc17
-rw-r--r--src/libexpr/flake/flakeref.hh12
-rw-r--r--src/libexpr/flake/lockfile.cc50
-rw-r--r--src/libexpr/flake/lockfile.hh9
-rw-r--r--src/libexpr/function-trace.hh2
-rw-r--r--src/libexpr/get-drvs.cc73
-rw-r--r--src/libexpr/get-drvs.hh2
-rw-r--r--src/libexpr/lexer.l12
-rw-r--r--src/libexpr/nixexpr.cc451
-rw-r--r--src/libexpr/nixexpr.hh254
-rw-r--r--src/libexpr/parser.y197
-rw-r--r--src/libexpr/primops.cc1265
-rw-r--r--src/libexpr/primops.hh4
-rw-r--r--src/libexpr/primops/context.cc59
-rw-r--r--src/libexpr/primops/fetchClosure.cc41
-rw-r--r--src/libexpr/primops/fetchMercurial.cc20
-rw-r--r--src/libexpr/primops/fetchTree.cc96
-rw-r--r--src/libexpr/primops/fromTOML.cc6
-rw-r--r--src/libexpr/symbol-table.hh97
-rw-r--r--src/libexpr/tests/error_traces.cc94
-rw-r--r--src/libexpr/tests/json.cc68
-rw-r--r--src/libexpr/tests/libexprtests.hh137
-rw-r--r--src/libexpr/tests/local.mk15
-rw-r--r--src/libexpr/tests/primops.cc832
-rw-r--r--src/libexpr/tests/trivial.cc196
-rw-r--r--src/libexpr/value-to-json.cc62
-rw-r--r--src/libexpr/value-to-json.hh9
-rw-r--r--src/libexpr/value-to-xml.cc33
-rw-r--r--src/libexpr/value-to-xml.hh2
-rw-r--r--src/libexpr/value.hh28
44 files changed, 4065 insertions, 1838 deletions
diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc
index 32deecfae..7c0705091 100644
--- a/src/libexpr/attr-path.cc
+++ b/src/libexpr/attr-path.cc
@@ -41,13 +41,13 @@ std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
}
-std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string & attrPath,
+std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
Bindings & autoArgs, Value & vIn)
{
Strings tokens = parseAttrPath(attrPath);
Value * v = &vIn;
- Pos pos = noPos;
+ PosIdx pos = noPos;
for (auto & attr : tokens) {
@@ -77,13 +77,13 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
if (a == v->attrs->end()) {
std::set<std::string> attrNames;
for (auto & attr : *v->attrs)
- attrNames.insert(attr.name);
+ attrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(attrNames, attr);
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
}
v = &*a->value;
- pos = *a->pos;
+ pos = a->pos;
}
else {
@@ -106,7 +106,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
}
-Pos findPackageFilename(EvalState & state, Value & v, std::string what)
+std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value * v2;
try {
@@ -118,7 +118,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
- auto pos = state.forceString(*v2);
+ auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':');
if (colon == std::string::npos)
@@ -132,9 +132,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
throw ParseError("cannot parse line number '%s'", pos);
}
- Symbol file = state.symbols.create(filename);
-
- return { foFile, file, lineno, 0 };
+ return { std::move(filename), lineno };
}
diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh
index ff1135a06..117e0051b 100644
--- a/src/libexpr/attr-path.hh
+++ b/src/libexpr/attr-path.hh
@@ -10,14 +10,14 @@ namespace nix {
MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error);
-std::pair<Value *, Pos> findAlongAttrPath(
+std::pair<Value *, PosIdx> findAlongAttrPath(
EvalState & state,
const std::string & attrPath,
Bindings & autoArgs,
Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */
-Pos findPackageFilename(EvalState & state, Value & v, std::string what);
+std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc
index 52ac47e9b..877116f1f 100644
--- a/src/libexpr/attr-set.cc
+++ b/src/libexpr/attr-set.cc
@@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity)
/* Create a new attribute named 'name' on an existing attribute set stored
in 'vAttrs' and return the newly allocated Value which is associated with
this attribute. */
-Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
+Value * EvalState::allocAttr(Value & vAttrs, Symbol name)
{
Value * v = allocValue();
vAttrs.attrs->push_back(Attr(name, v));
@@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name)
}
-Value & BindingsBuilder::alloc(const Symbol & name, ptr<Pos> pos)
+Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
auto value = state.allocValue();
bindings->push_back(Attr(name, value, pos));
@@ -48,7 +48,7 @@ Value & BindingsBuilder::alloc(const Symbol & name, ptr<Pos> pos)
}
-Value & BindingsBuilder::alloc(std::string_view name, ptr<Pos> pos)
+Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
{
return alloc(state.symbols.create(name), pos);
}
diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh
index cad9743ea..dcc73b506 100644
--- a/src/libexpr/attr-set.hh
+++ b/src/libexpr/attr-set.hh
@@ -15,18 +15,27 @@ struct Value;
/* Map one attribute name to its value. */
struct Attr
{
+ /* the placement of `name` and `pos` in this struct is important.
+ both of them are uint32 wrappers, they are next to each other
+ to make sure that Attr has no padding on 64 bit machines. that
+ way we keep Attr size at two words with no wasted space. */
Symbol name;
+ PosIdx pos;
Value * value;
- ptr<Pos> pos;
- Attr(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
- : name(name), value(value), pos(pos) { };
- Attr() : pos(&noPos) { };
+ Attr(Symbol name, Value * value, PosIdx pos = noPos)
+ : name(name), pos(pos), value(value) { };
+ Attr() { };
bool operator < (const Attr & a) const
{
return name < a.name;
}
};
+static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
+ "performance of the evaluator is highly sensitive to the size of Attr. "
+ "avoid introducing any padding into Attr if at all possible, and do not "
+ "introduce new fields that need not be present for almost every instance.");
+
/* Bindings contains all the attributes of an attribute set. It is defined
by its size and its capacity, the capacity being the number of Attr
elements allocated after this structure, while the size corresponds to
@@ -35,13 +44,13 @@ class Bindings
{
public:
typedef uint32_t size_t;
- ptr<Pos> pos;
+ PosIdx pos;
private:
size_t size_, capacity_;
Attr attrs[0];
- Bindings(size_t capacity) : pos(&noPos), size_(0), capacity_(capacity) { }
+ Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
Bindings(const Bindings & bindings) = delete;
public:
@@ -57,7 +66,7 @@ public:
attrs[size_++] = attr;
}
- iterator find(const Symbol & name)
+ iterator find(Symbol name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
@@ -65,7 +74,7 @@ public:
return end();
}
- Attr * get(const Symbol & name)
+ Attr * get(Symbol name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
@@ -73,18 +82,6 @@ public:
return nullptr;
}
- Attr & need(const Symbol & name, const Pos & pos = noPos)
- {
- auto a = get(name);
- if (!a)
- throw Error({
- .msg = hintfmt("attribute '%s' missing", name),
- .errPos = pos
- });
-
- return *a;
- }
-
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }
@@ -98,14 +95,15 @@ public:
size_t capacity() { return capacity_; }
/* Returns the attributes in lexicographically sorted order. */
- std::vector<const Attr *> lexicographicOrder() const
+ std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<const Attr *> res;
res.reserve(size_);
for (size_t n = 0; n < size_; n++)
res.emplace_back(&attrs[n]);
- std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) {
- return (const std::string &) a->name < (const std::string &) b->name;
+ std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
+ std::string_view sa = symbols[a->name], sb = symbols[b->name];
+ return sa < sb;
});
return res;
}
@@ -130,7 +128,7 @@ public:
: bindings(bindings), state(state)
{ }
- void insert(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
+ void insert(Symbol name, Value * value, PosIdx pos = noPos)
{
insert(Attr(name, value, pos));
}
@@ -145,9 +143,9 @@ public:
bindings->push_back(attr);
}
- Value & alloc(const Symbol & name, ptr<Pos> pos = ptr(&noPos));
+ Value & alloc(Symbol name, PosIdx pos = noPos);
- Value & alloc(std::string_view name, ptr<Pos> pos = ptr(&noPos));
+ Value & alloc(std::string_view name, PosIdx pos = noPos);
Bindings * finish()
{
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 7d3fd01a4..afe575fee 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -35,13 +35,19 @@ struct AttrDb
std::unique_ptr<Sync<State>> _state;
- AttrDb(const Store & cfg, const Hash & fingerprint)
+ SymbolTable & symbols;
+
+ AttrDb(
+ const Store & cfg,
+ const Hash & fingerprint,
+ SymbolTable & symbols)
: cfg(cfg)
, _state(std::make_unique<Sync<State>>())
+ , symbols(symbols)
{
auto state(_state->lock());
- Path cacheDir = getCacheDir() + "/nix/eval-cache-v2";
+ Path cacheDir = getCacheDir() + "/nix/eval-cache-v4";
createDirs(cacheDir);
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
@@ -100,7 +106,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::FullAttrs)
(0, false).exec();
@@ -110,7 +116,7 @@ struct AttrDb
for (auto & attr : attrs)
state->insertAttribute.use()
(rowId)
- (attr)
+ (symbols[attr])
(AttrType::Placeholder)
(0, false).exec();
@@ -135,14 +141,14 @@ struct AttrDb
}
state->insertAttributeWithContext.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::String)
(s)
(ctx).exec();
} else {
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::String)
(s).exec();
}
@@ -161,7 +167,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::Bool)
(b ? 1 : 0).exec();
@@ -169,6 +175,42 @@ struct AttrDb
});
}
+ AttrId setInt(
+ AttrKey key,
+ int n)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (symbols[key.second])
+ (AttrType::Int)
+ (n).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setListOfStrings(
+ AttrKey key,
+ const std::vector<std::string> & l)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (symbols[key.second])
+ (AttrType::ListOfStrings)
+ (concatStringsSep("\t", l)).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
AttrId setPlaceholder(AttrKey key)
{
return doSQLite([&]()
@@ -177,7 +219,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::Placeholder)
(0, false).exec();
@@ -193,7 +235,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::Missing)
(0, false).exec();
@@ -209,7 +251,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::Misc)
(0, false).exec();
@@ -225,7 +267,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
- (key.second)
+ (symbols[key.second])
(AttrType::Failed)
(0, false).exec();
@@ -233,16 +275,14 @@ struct AttrDb
});
}
- std::optional<std::pair<AttrId, AttrValue>> getAttr(
- AttrKey key,
- SymbolTable & symbols)
+ std::optional<std::pair<AttrId, AttrValue>> getAttr(AttrKey key)
{
auto state(_state->lock());
- auto queryAttribute(state->queryAttribute.use()(key.first)(key.second));
+ auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
if (!queryAttribute.next()) return {};
- auto rowId = (AttrType) queryAttribute.getInt(0);
+ auto rowId = (AttrId) queryAttribute.getInt(0);
auto type = (AttrType) queryAttribute.getInt(1);
switch (type) {
@@ -253,7 +293,7 @@ struct AttrDb
std::vector<Symbol> attrs;
auto queryAttributes(state->queryAttributes.use()(rowId));
while (queryAttributes.next())
- attrs.push_back(symbols.create(queryAttributes.getStr(0)));
+ attrs.emplace_back(symbols.create(queryAttributes.getStr(0)));
return {{rowId, attrs}};
}
case AttrType::String: {
@@ -265,6 +305,10 @@ struct AttrDb
}
case AttrType::Bool:
return {{rowId, queryAttribute.getInt(2) != 0}};
+ case AttrType::Int:
+ return {{rowId, int_t{queryAttribute.getInt(2)}}};
+ case AttrType::ListOfStrings:
+ return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
case AttrType::Missing:
return {{rowId, missing_t()}};
case AttrType::Misc:
@@ -277,10 +321,13 @@ struct AttrDb
}
};
-static std::shared_ptr<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint)
+static std::shared_ptr<AttrDb> makeAttrDb(
+ const Store & cfg,
+ const Hash & fingerprint,
+ SymbolTable & symbols)
{
try {
- return std::make_shared<AttrDb>(cfg, fingerprint);
+ return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
} catch (SQLiteError &) {
ignoreException();
return nullptr;
@@ -291,7 +338,7 @@ EvalCache::EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state,
RootLoader rootLoader)
- : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr)
+ : db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr)
, state(state)
, rootLoader(rootLoader)
{
@@ -327,8 +374,7 @@ AttrKey AttrCursor::getKey()
if (!parent)
return {0, root->state.sEpsilon};
if (!parent->first->cachedValue) {
- parent->first->cachedValue = root->db->getAttr(
- parent->first->getKey(), root->state.symbols);
+ parent->first->cachedValue = root->db->getAttr(parent->first->getKey());
assert(parent->first->cachedValue);
}
return {parent->first->cachedValue->first, parent->second};
@@ -339,7 +385,7 @@ Value & AttrCursor::getValue()
if (!_value) {
if (parent) {
auto & vParent = parent->first->getValue();
- root->state.forceAttrs(vParent, noPos);
+ root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(parent->second);
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
@@ -369,17 +415,17 @@ std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
std::string AttrCursor::getAttrPathStr() const
{
- return concatStringsSep(".", getAttrPath());
+ return concatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
}
std::string AttrCursor::getAttrPathStr(Symbol name) const
{
- return concatStringsSep(".", getAttrPath(name));
+ return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
}
Value & AttrCursor::forceValue()
{
- debug("evaluating uncached attribute %s", getAttrPathStr());
+ debug("evaluating uncached attribute '%s'", getAttrPathStr());
auto & v = getValue();
@@ -400,6 +446,8 @@ Value & AttrCursor::forceValue()
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}};
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
+ else if (v.type() == nInt)
+ cachedValue = {root->db->setInt(getKey(), v.integer), int_t{v.integer}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
@@ -414,31 +462,31 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
auto attrNames = getAttrs();
std::set<std::string> strAttrNames;
for (auto & name : attrNames)
- strAttrNames.insert(std::string(name));
+ strAttrNames.insert(root->state.symbols[name]);
- return Suggestions::bestMatches(strAttrNames, name);
+ return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]);
}
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
{
if (root->db) {
if (!cachedValue)
- cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ cachedValue = root->db->getAttr(getKey());
if (cachedValue) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
for (auto & attr : *attrs)
if (attr == name)
- return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), name));
+ return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr));
return nullptr;
} else if (std::get_if<placeholder_t>(&cachedValue->second)) {
- auto attr = root->db->getAttr({cachedValue->first, name}, root->state.symbols);
+ auto attr = root->db->getAttr({cachedValue->first, name});
if (attr) {
if (std::get_if<missing_t>(&attr->second))
return nullptr;
else if (std::get_if<failed_t>(&attr->second)) {
if (forceErrors)
- debug("reevaluating failed cached attribute '%s'");
+ debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
else
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name));
} else
@@ -459,11 +507,6 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
- for (auto & attr : *v.attrs) {
- if (root->db)
- root->db->setPlaceholder({cachedValue->first, attr.name});
- }
-
auto attr = v.attrs->get(name);
if (!attr) {
@@ -522,20 +565,20 @@ std::string AttrCursor::getString()
{
if (root->db) {
if (!cachedValue)
- cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
debug("using cached string attribute '%s'", getAttrPathStr());
return s->first;
} else
- throw TypeError("'%s' is not a string", getAttrPathStr());
+ root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nString && v.type() != nPath)
- throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
+ root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path;
}
@@ -544,7 +587,7 @@ string_t AttrCursor::getStringWithContext()
{
if (root->db) {
if (!cachedValue)
- cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true;
@@ -559,7 +602,7 @@ string_t AttrCursor::getStringWithContext()
return *s;
}
} else
- throw TypeError("'%s' is not a string", getAttrPathStr());
+ root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
}
}
@@ -570,55 +613,111 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath)
return {v.path, {}};
else
- throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
+ root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
}
bool AttrCursor::getBool()
{
if (root->db) {
if (!cachedValue)
- cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto b = std::get_if<bool>(&cachedValue->second)) {
debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b;
} else
- throw TypeError("'%s' is not a Boolean", getAttrPathStr());
+ root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nBool)
- throw TypeError("'%s' is not a Boolean", getAttrPathStr());
+ root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
return v.boolean;
}
+NixInt AttrCursor::getInt()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey());
+ if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
+ if (auto i = std::get_if<int_t>(&cachedValue->second)) {
+ debug("using cached integer attribute '%s'", getAttrPathStr());
+ return i->x;
+ } else
+ throw TypeError("'%s' is not an integer", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type() != nInt)
+ throw TypeError("'%s' is not an integer", getAttrPathStr());
+
+ return v.integer;
+}
+
+std::vector<std::string> AttrCursor::getListOfStrings()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey());
+ if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
+ if (auto l = std::get_if<std::vector<std::string>>(&cachedValue->second)) {
+ debug("using cached list of strings attribute '%s'", getAttrPathStr());
+ return *l;
+ } else
+ throw TypeError("'%s' is not a list of strings", getAttrPathStr());
+ }
+ }
+
+ debug("evaluating uncached attribute '%s'", getAttrPathStr());
+
+ auto & v = getValue();
+ root->state.forceValue(v, noPos);
+
+ if (v.type() != nList)
+ throw TypeError("'%s' is not a list", getAttrPathStr());
+
+ std::vector<std::string> res;
+
+ for (auto & elem : v.listItems())
+ res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
+
+ if (root->db)
+ cachedValue = {root->db->setListOfStrings(getKey(), res), res};
+
+ return res;
+}
+
std::vector<Symbol> AttrCursor::getAttrs()
{
if (root->db) {
if (!cachedValue)
- cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs;
} else
- throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+ root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nAttrs)
- throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+ root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)
attrs.push_back(attr.name);
- std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
- return (const std::string &) a < (const std::string &) b;
+ std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) {
+ std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b];
+ return sa < sb;
});
if (root->db)
diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh
index b0709ebc2..c93e55b93 100644
--- a/src/libexpr/eval-cache.hh
+++ b/src/libexpr/eval-cache.hh
@@ -44,12 +44,15 @@ enum AttrType {
Misc = 4,
Failed = 5,
Bool = 6,
+ ListOfStrings = 7,
+ Int = 8,
};
struct placeholder_t {};
struct missing_t {};
struct misc_t {};
struct failed_t {};
+struct int_t { NixInt x; };
typedef uint64_t AttrId;
typedef std::pair<AttrId, Symbol> AttrKey;
typedef std::pair<std::string, NixStringContext> string_t;
@@ -61,7 +64,9 @@ typedef std::variant<
missing_t,
misc_t,
failed_t,
- bool
+ bool,
+ int_t,
+ std::vector<std::string>
> AttrValue;
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
@@ -114,6 +119,10 @@ public:
bool getBool();
+ NixInt getInt();
+
+ std::vector<std::string> getListOfStrings();
+
std::vector<Symbol> getAttrs();
bool isDerivation();
diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh
index 08a419923..f0da688db 100644
--- a/src/libexpr/eval-inline.hh
+++ b/src/libexpr/eval-inline.hh
@@ -2,28 +2,8 @@
#include "eval.hh"
-#define LocalNoInline(f) static f __attribute__((noinline)); f
-#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f
-
namespace nix {
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
-{
- throw EvalError({
- .msg = hintfmt(s),
- .errPos = pos
- });
-}
-
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
-{
- throw TypeError({
- .msg = hintfmt(s, showType(v)),
- .errPos = pos
- });
-}
-
-
/* Note: Various places expect the allocated memory to be zeroed. */
[[gnu::always_inline]]
inline void * allocBytes(size_t n)
@@ -99,7 +79,7 @@ Env & EvalState::allocEnv(size_t size)
[[gnu::always_inline]]
-void EvalState::forceValue(Value & v, const Pos & pos)
+void EvalState::forceValue(Value & v, const PosIdx pos)
{
forceValue(v, [&]() { return pos; });
}
@@ -123,33 +103,36 @@ void EvalState::forceValue(Value & v, Callable getPos)
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isBlackhole())
- throwEvalError(getPos(), "infinite recursion encountered");
+ error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
}
[[gnu::always_inline]]
-inline void EvalState::forceAttrs(Value & v, const Pos & pos)
+inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceAttrs(v, [&]() { return pos; });
+ forceAttrs(v, [&]() { return pos; }, errorCtx);
}
template <typename Callable>
[[gnu::always_inline]]
-inline void EvalState::forceAttrs(Value & v, Callable getPos)
+inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
{
- forceValue(v, getPos);
- if (v.type() != nAttrs)
- throwTypeError(getPos(), "value is %1% while a set was expected", v);
+ forceValue(v, noPos);
+ if (v.type() != nAttrs) {
+ PosIdx pos = getPos();
+ error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
+ }
}
[[gnu::always_inline]]
-inline void EvalState::forceList(Value & v, const Pos & pos)
+inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (!v.isList())
- throwTypeError(pos, "value is %1% while a list was expected", v);
+ forceValue(v, noPos);
+ if (!v.isList()) {
+ error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
+ }
}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index b87e06ef5..72c2b104f 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -7,7 +7,6 @@
#include "globals.hh"
#include "eval-inline.hh"
#include "filetransfer.hh"
-#include "json.hh"
#include "function-trace.hh"
#include <algorithm>
@@ -18,8 +17,10 @@
#include <sys/resource.h>
#include <iostream>
#include <fstream>
+#include <functional>
#include <sys/resource.h>
+#include <nlohmann/json.hpp>
#if HAVE_BOEHMGC
@@ -34,8 +35,9 @@
#endif
-namespace nix {
+using json = nlohmann::json;
+namespace nix {
static char * allocString(size_t size)
{
@@ -43,7 +45,7 @@ static char * allocString(size_t size)
#if HAVE_BOEHMGC
t = (char *) GC_MALLOC_ATOMIC(size);
#else
- t = malloc(size);
+ t = (char *) malloc(size);
#endif
if (!t) throw std::bad_alloc();
return t;
@@ -65,26 +67,19 @@ static char * dupString(const char * s)
// When there's no need to write to the string, we can optimize away empty
// string allocations.
-// This function handles makeImmutableStringWithLen(null, 0) by returning the
-// empty string.
-static const char * makeImmutableStringWithLen(const char * s, size_t size)
+// This function handles makeImmutableString(std::string_view()) by returning
+// the empty string.
+static const char * makeImmutableString(std::string_view s)
{
- char * t;
+ const size_t size = s.size();
if (size == 0)
return "";
-#if HAVE_BOEHMGC
- t = GC_STRNDUP(s, size);
-#else
- t = strndup(s, size);
-#endif
- if (!t) throw std::bad_alloc();
+ auto t = allocString(size + 1);
+ memcpy(t, s.data(), size);
+ t[size] = '\0';
return t;
}
-static inline const char * makeImmutableString(std::string_view s) {
- return makeImmutableStringWithLen(s.data(), s.size());
-}
-
RootValue allocRootValue(Value * v)
{
@@ -96,7 +91,8 @@ RootValue allocRootValue(Value * v)
}
-void Value::print(std::ostream & str, std::set<const void *> * seen) const
+void Value::print(const SymbolTable & symbols, std::ostream & str,
+ std::set<const void *> * seen) const
{
checkInterrupt();
@@ -129,9 +125,9 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
str << "«repeated»";
else {
str << "{ ";
- for (auto & i : attrs->lexicographicOrder()) {
- str << i->name << " = ";
- i->value->print(str, seen);
+ for (auto & i : attrs->lexicographicOrder(symbols)) {
+ str << symbols[i->name] << " = ";
+ i->value->print(symbols, str, seen);
str << "; ";
}
str << "}";
@@ -146,7 +142,10 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
else {
str << "[ ";
for (auto v2 : listItems()) {
- v2->print(str, seen);
+ if (v2)
+ v2->print(symbols, str, seen);
+ else
+ str << "(nullptr)";
str << " ";
}
str << "]";
@@ -177,17 +176,23 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
}
-void Value::print(std::ostream & str, bool showRepeated) const
+void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const
{
std::set<const void *> seen;
- print(str, showRepeated ? nullptr : &seen);
+ print(symbols, str, showRepeated ? nullptr : &seen);
}
+// Pretty print types for assertion errors
+std::ostream & operator << (std::ostream & os, const ValueType t) {
+ os << showType(t);
+ return os;
+}
-std::ostream & operator << (std::ostream & str, const Value & v)
+std::string printValue(const EvalState & state, const Value & v)
{
- v.print(str, false);
- return str;
+ std::ostringstream out;
+ v.print(state.symbols, out);
+ return out.str();
}
@@ -236,10 +241,10 @@ std::string showType(const Value & v)
}
}
-Pos Value::determinePos(const Pos & pos) const
+PosIdx Value::determinePos(const PosIdx pos) const
{
switch (internalType) {
- case tAttrs: return *attrs->pos;
+ case tAttrs: return attrs->pos;
case tLambda: return lambda.fun->pos;
case tApp: return app.left->determinePos(pos);
default: return pos;
@@ -308,12 +313,12 @@ static BoehmGCStackAllocator boehmGCStackAllocator;
static Symbol getName(const AttrName & name, EvalState & state, Env & env)
{
- if (name.symbol.set()) {
+ if (name.symbol) {
return name.symbol;
} else {
Value nameValue;
name.expr->eval(state, env, nameValue);
- state.forceStringNoCtx(nameValue);
+ state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
return state.symbols.create(nameValue.string.s);
}
}
@@ -394,7 +399,8 @@ static Strings parseNixPath(const std::string & s)
}
if (*p == ':') {
- if (isUri(std::string(start2, s.end()))) {
+ auto prefix = std::string(start2, s.end());
+ if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) {
++p;
while (p != s.end() && *p != ':') ++p;
}
@@ -408,6 +414,44 @@ static Strings parseNixPath(const std::string & s)
return res;
}
+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(
const Strings & _searchPath,
@@ -449,20 +493,22 @@ EvalState::EvalState(
, sKey(symbols.create("key"))
, sPath(symbols.create("path"))
, sPrefix(symbols.create("prefix"))
+ , sOutputSpecified(symbols.create("outputSpecified"))
, repair(NoRepair)
, emptyBindings(0)
, store(store)
, buildStore(buildStore ? buildStore : store)
+ , debugRepl(nullptr)
+ , debugStop(false)
+ , debugQuit(false)
+ , trylevel(0)
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
-#else
- , valueAllocCache(std::make_shared<void *>(nullptr))
- , env1AllocCache(std::make_shared<void *>(nullptr))
#endif
, baseEnv(allocEnv(128))
- , staticBaseEnv(false, 0)
+ , staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
{
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@@ -520,7 +566,7 @@ void EvalState::allowPath(const StorePath & storePath)
allowedPaths->insert(store->toRealPath(storePath));
}
-void EvalState::allowAndSetStorePathString(const StorePath &storePath, Value & v)
+void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value & v)
{
allowPath(storePath);
@@ -628,7 +674,7 @@ Value * EvalState::addConstant(const std::string & name, Value & v)
void EvalState::addConstant(const std::string & name, Value * v)
{
- staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
+ staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
@@ -638,25 +684,7 @@ void EvalState::addConstant(const std::string & name, Value * v)
Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp)
{
- auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
- Symbol sym = symbols.create(name2);
-
- /* Hack to make constants lazy: turn them into a application of
- the primop to a dummy value. */
- if (arity == 0) {
- auto vPrimOp = allocValue();
- vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym });
- Value v;
- v.mkApp(vPrimOp, vPrimOp);
- return addConstant(name, v);
- }
-
- Value * v = allocValue();
- v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
- staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
- baseEnv.values[baseEnvDispl++] = v;
- baseEnv.values[0]->attrs->push_back(Attr(sym, v));
- return v;
+ return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
}
@@ -667,21 +695,21 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
if (primOp.arity == 0) {
primOp.arity = 1;
auto vPrimOp = allocValue();
- vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
+ vPrimOp->mkPrimOp(new PrimOp(primOp));
Value v;
v.mkApp(vPrimOp, vPrimOp);
return addConstant(primOp.name, v);
}
- Symbol envName = primOp.name;
+ auto envName = symbols.create(primOp.name);
if (hasPrefix(primOp.name, "__"))
- primOp.name = symbols.create(std::string(primOp.name, 2));
+ primOp.name = primOp.name.substr(2);
Value * v = allocValue();
- v->mkPrimOp(new PrimOp(std::move(primOp)));
- staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
+ v->mkPrimOp(new PrimOp(primOp));
+ staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
- baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
+ baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
return v;
}
@@ -698,7 +726,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
auto v2 = &v;
if (v2->primOp->doc)
return Doc {
- .pos = noPos,
+ .pos = {},
.name = v2->primOp->name,
.arity = v2->primOp->arity,
.args = v2->primOp->args,
@@ -709,121 +737,168 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
}
-/* Every "format" object (even temporary) takes up a few hundred bytes
- of stack space, which is a real killer in the recursive
- evaluator. So here are some helper functions for throwing
- exceptions. */
-
-LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2))
+// just for the current level of StaticEnv, not the whole chain.
+void printStaticEnvBindings(const SymbolTable & st, const StaticEnv & se)
{
- throw EvalError(s, s2);
+ std::cout << ANSI_MAGENTA;
+ for (auto & i : se.vars)
+ std::cout << st[i.first] << " ";
+ std::cout << ANSI_NORMAL;
+ std::cout << std::endl;
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
+// just for the current level of Env, not the whole chain.
+void printWithBindings(const SymbolTable & st, const Env & env)
{
- throw EvalError(ErrorInfo {
- .msg = hintfmt(s, s2),
- .errPos = pos,
- .suggestions = suggestions,
- });
+ if (env.type == Env::HasWithAttrs) {
+ std::cout << "with: ";
+ std::cout << ANSI_MAGENTA;
+ Bindings::iterator j = env.values[0]->attrs->begin();
+ while (j != env.values[0]->attrs->end()) {
+ std::cout << st[j->name] << " ";
+ ++j;
+ }
+ std::cout << ANSI_NORMAL;
+ std::cout << std::endl;
+ }
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2))
+void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl)
{
- throw EvalError(ErrorInfo {
- .msg = hintfmt(s, s2),
- .errPos = pos
- });
-}
+ std::cout << "Env level " << lvl << std::endl;
-LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3))
-{
- throw EvalError(s, s2, s3);
-}
+ if (se.up && env.up) {
+ std::cout << "static: ";
+ printStaticEnvBindings(st, se);
+ printWithBindings(st, env);
+ std::cout << std::endl;
+ printEnvBindings(st, *se.up, *env.up, ++lvl);
+ } else {
+ std::cout << ANSI_MAGENTA;
+ // for the top level, don't print the double underscore ones;
+ // they are in builtins.
+ for (auto & i : se.vars)
+ if (!hasPrefix(st[i.first], "__"))
+ std::cout << st[i.first] << " ";
+ std::cout << ANSI_NORMAL;
+ std::cout << std::endl;
+ printWithBindings(st, env); // probably nothing there for the top level.
+ std::cout << std::endl;
-LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3))
-{
- throw EvalError({
- .msg = hintfmt(s, s2, s3),
- .errPos = pos
- });
+ }
}
-LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
+void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env)
{
- // p1 is where the error occurred; p2 is a position mentioned in the message.
- throw EvalError({
- .msg = hintfmt(s, sym, p2),
- .errPos = p1
- });
+ // just print the names for now
+ auto se = es.getStaticEnv(expr);
+ if (se)
+ printEnvBindings(es.symbols, *se, env, 0);
}
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
+void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, ValMap & vm)
{
- throw TypeError({
- .msg = hintfmt(s),
- .errPos = pos
- });
+ // add bindings for the next level up first, so that the bindings for this level
+ // override the higher levels.
+ // The top level bindings (builtins) are skipped since they are added for us by initEnv()
+ if (env.up && se.up) {
+ mapStaticEnvBindings(st, *se.up, *env.up, vm);
+
+ if (env.type == Env::HasWithAttrs) {
+ // add 'with' bindings.
+ Bindings::iterator j = env.values[0]->attrs->begin();
+ while (j != env.values[0]->attrs->end()) {
+ vm[st[j->name]] = j->value;
+ ++j;
+ }
+ } else {
+ // iterate through staticenv bindings and add them.
+ for (auto & i : se.vars)
+ vm[st[i.first]] = env.values[i.second];
+ }
+ }
}
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
+std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env)
{
- throw TypeError({
- .msg = hintfmt(s, fun.showNamePos(), s2),
- .errPos = pos
- });
+ auto vm = std::make_unique<ValMap>();
+ mapStaticEnvBindings(st, se, env, *vm);
+ return vm;
}
-LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2))
+void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & expr)
{
- throw TypeError(ErrorInfo {
- .msg = hintfmt(s, fun.showNamePos(), s2),
- .errPos = pos,
- .suggestions = suggestions,
- });
-}
+ // double check we've got the debugRepl function pointer.
+ if (!debugRepl)
+ return;
+ auto dts =
+ error && expr.getPos()
+ ? std::make_unique<DebugTraceStacker>(
+ *this,
+ DebugTrace {
+ .pos = error->info().errPos ? error->info().errPos : static_cast<std::shared_ptr<AbstractPos>>(positions[expr.getPos()]),
+ .expr = expr,
+ .env = env,
+ .hint = error->info().msg,
+ .isError = true
+ })
+ : nullptr;
+
+ if (error)
+ {
+ printError("%s\n\n", error->what());
-LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
-{
- throw TypeError(s, showType(v));
-}
+ if (trylevel > 0 && error->info().level != lvlInfo)
+ printError("This exception occurred in a 'tryEval' call. Use " ANSI_GREEN "--ignore-try" ANSI_NORMAL " to skip these.\n");
-LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1))
-{
- throw AssertionError({
- .msg = hintfmt(s, s1),
- .errPos = pos
- });
+ printError(ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL);
+ }
+
+ auto se = getStaticEnv(expr);
+ if (se) {
+ auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
+ (debugRepl)(ref<EvalState>(shared_from_this()), *vm);
+ }
}
-LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1))
+void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{
- throw UndefinedVarError({
- .msg = hintfmt(s, s1),
- .errPos = pos
- });
+ e.addTrace(nullptr, s, s2);
}
-LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1))
+void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
{
- throw MissingArgumentError({
- .msg = hintfmt(s, s1),
- .errPos = pos
- });
+ e.addTrace(positions[pos], hintfmt(s, s2), frame);
}
-LocalNoInline(void addErrorTrace(Error & e, const char * s, const std::string & s2))
+static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
+ EvalState & state,
+ Expr & expr,
+ Env & env,
+ std::shared_ptr<AbstractPos> && pos,
+ const char * s,
+ const std::string & s2)
{
- e.addTrace(std::nullopt, s, s2);
+ return std::make_unique<DebugTraceStacker>(state,
+ DebugTrace {
+ .pos = std::move(pos),
+ .expr = expr,
+ .env = env,
+ .hint = hintfmt(s, s2),
+ .isError = false
+ });
}
-LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2))
+DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
+ : evalState(evalState)
+ , trace(std::move(t))
{
- e.addTrace(pos, s, s2);
+ evalState.debugTraces.push_front(trace);
+ if (evalState.debugStop && evalState.debugRepl)
+ evalState.runDebugRepl(nullptr, trace.env, trace.expr);
}
-
void Value::mkString(std::string_view s)
{
mkString(makeImmutableString(s));
@@ -871,22 +946,21 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
if (env->type == Env::HasWithExpr) {
if (noEval) return 0;
Value * v = allocValue();
- evalAttrs(*env->up, (Expr *) env->values[0], *v);
+ evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, "<borked>");
env->values[0] = v;
env->type = Env::HasWithAttrs;
}
Bindings::iterator j = env->values[0]->attrs->find(var.name);
if (j != env->values[0]->attrs->end()) {
- if (countCalls) attrSelects[*j->pos]++;
+ if (countCalls) attrSelects[j->pos]++;
return j->value;
}
if (!env->prevWith)
- throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name);
+ error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
for (size_t l = env->prevWith; l; --l, env = env->up) ;
}
}
-
void EvalState::mkList(Value & v, size_t size)
{
v.mkList(size);
@@ -911,13 +985,14 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
}
-void EvalState::mkPos(Value & v, ptr<Pos> pos)
+void EvalState::mkPos(Value & v, PosIdx p)
{
- if (pos->file.set()) {
+ auto pos = positions[p];
+ if (auto path = std::get_if<Path>(&pos.origin)) {
auto attrs = buildBindings(3);
- attrs.alloc(sFile).mkString(pos->file);
- attrs.alloc(sLine).mkInt(pos->line);
- attrs.alloc(sColumn).mkInt(pos->column);
+ attrs.alloc(sFile).mkString(*path);
+ attrs.alloc(sLine).mkInt(pos.line);
+ attrs.alloc(sColumn).mkInt(pos.column);
v.mkAttrs(attrs);
} else
v.mkNull();
@@ -1018,11 +1093,20 @@ void EvalState::cacheFile(
fileParseCache[resolvedPath] = e;
try {
+ auto dts = debugRepl
+ ? makeDebugTraceStacker(
+ *this,
+ *e,
+ this->baseEnv,
+ e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
+ "while evaluating the file '%1%':", resolvedPath)
+ : nullptr;
+
// Enforce that 'flake.nix' is a direct attrset, not a
// computation.
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
- throw EvalError("file '%s' must be an attribute set", path);
+ error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
@@ -1040,31 +1124,31 @@ void EvalState::eval(Expr * e, Value & v)
}
-inline bool EvalState::evalBool(Env & env, Expr * e)
-{
- Value v;
- e->eval(*this, env, v);
- if (v.type() != nBool)
- throwTypeError("value is %1% while a Boolean was expected", v);
- return v.boolean;
-}
-
-
-inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
+inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
{
- Value v;
- e->eval(*this, env, v);
- if (v.type() != nBool)
- throwTypeError(pos, "value is %1% while a Boolean was expected", v);
- return v.boolean;
+ try {
+ Value v;
+ e->eval(*this, env, v);
+ if (v.type() != nBool)
+ error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
+ return v.boolean;
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
-inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
+inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
{
- e->eval(*this, env, v);
- if (v.type() != nAttrs)
- throwTypeError("value is %1% while a set was expected", v);
+ try {
+ e->eval(*this, env, v);
+ if (v.type() != nAttrs)
+ error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
@@ -1124,7 +1208,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
} else
vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
env2.values[displ++] = vAttr;
- v.attrs->push_back(Attr(i.first, vAttr, ptr(&i.second.pos)));
+ v.attrs->push_back(Attr(i.first, vAttr, i.second.pos));
}
/* If the rec contains an attribute called `__overrides', then
@@ -1137,7 +1221,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Hence we need __overrides.) */
if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
- state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); });
+ state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs)
newBnds->push_back(i);
@@ -1156,7 +1240,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
else
for (auto & i : attrs)
- v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), ptr(&i.second.pos)));
+ v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), i.second.pos));
/* Dynamic attrs apply *after* rec and __overrides. */
for (auto & i : dynamicAttrs) {
@@ -1165,19 +1249,19 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
state.forceValue(nameVal, i.pos);
if (nameVal.type() == nNull)
continue;
- state.forceStringNoCtx(nameVal);
- Symbol nameSym = state.symbols.create(nameVal.string.s);
+ state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
+ auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
- throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos);
+ state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
- v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), ptr(&i.pos)));
+ v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
v.attrs->sort(); // FIXME: inefficient
}
- v.attrs->pos = ptr(&pos);
+ v.attrs->pos = pos;
}
@@ -1222,10 +1306,12 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
for (auto & i : attrPath) {
if (!first) out << '.'; else first = false;
try {
- out << getName(i, state, env);
+ out << state.symbols[getName(i, state, env)];
} catch (Error & e) {
- assert(!i.symbol.set());
- out << "\"${" << *i.expr << "}\"";
+ assert(!i.symbol);
+ out << "\"${";
+ i.expr->show(state.symbols, out);
+ out << "}\"";
}
}
return out.str();
@@ -1235,17 +1321,26 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{
Value vTmp;
- ptr<Pos> pos2(&noPos);
+ PosIdx pos2;
Value * vAttrs = &vTmp;
e->eval(state, env, vTmp);
try {
+ auto dts = state.debugRepl
+ ? makeDebugTraceStacker(
+ state,
+ *this,
+ env,
+ state.positions[pos2],
+ "while evaluating the attribute '%1%'",
+ showAttrPath(state, env, attrPath))
+ : nullptr;
for (auto & i : attrPath) {
state.nrLookups++;
Bindings::iterator j;
- Symbol name = getName(i, state, env);
+ auto name = getName(i, state, env);
if (def) {
state.forceValue(*vAttrs, pos);
if (vAttrs->type() != nAttrs ||
@@ -1255,28 +1350,31 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
return;
}
} else {
- state.forceAttrs(*vAttrs, pos);
+ state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs)
- allAttrNames.insert(attr.name);
- throwEvalError(
- pos,
- Suggestions::bestMatches(allAttrNames, name),
- "attribute '%1%' missing", name);
+ allAttrNames.insert(state.symbols[attr.name]);
+ auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
+ state.error("attribute '%1%' missing", state.symbols[name])
+ .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
}
}
vAttrs = j->value;
pos2 = j->pos;
- if (state.countCalls) state.attrSelects[*pos2]++;
+ if (state.countCalls) state.attrSelects[pos2]++;
}
- state.forceValue(*vAttrs, (*pos2 != noPos ? *pos2 : this->pos ) );
+ state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) );
} catch (Error & e) {
- if (*pos2 != noPos && pos2->file != state.sDerivationNix)
- addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'",
- showAttrPath(state, env, attrPath));
+ if (pos2) {
+ auto pos2r = state.positions[pos2];
+ auto origin = std::get_if<Path>(&pos2r.origin);
+ if (!(origin && *origin == state.derivationNixPath))
+ state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
+ showAttrPath(state, env, attrPath));
+ }
throw;
}
@@ -1294,7 +1392,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrPath) {
state.forceValue(*vAttrs, noPos);
Bindings::iterator j;
- Symbol name = getName(i, state, env);
+ auto name = getName(i, state, env);
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
@@ -1315,9 +1413,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
}
-void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
+void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
- auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
+ auto trace = evalSettings.traceFunctionCalls
+ ? std::make_unique<FunctionCallTrace>(positions[pos])
+ : nullptr;
forceValue(fun, pos);
@@ -1342,7 +1442,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
ExprLambda & lambda(*vCur.lambda.fun);
auto size =
- (lambda.arg.empty() ? 0 : 1) +
+ (!lambda.arg ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = vCur.lambda.env;
@@ -1351,11 +1451,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals())
env2.values[displ++] = args[0];
-
else {
- forceAttrs(*args[0], pos);
+ try {
+ forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
+ } catch (Error & e) {
+ if (pos) e.addTrace(positions[pos], "from call site");
+ throw;
+ }
- if (!lambda.arg.empty())
+ if (lambda.arg)
env2.values[displ++] = args[0];
/* For each formal argument, get the actual argument. If
@@ -1365,8 +1469,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name);
if (!j) {
- if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
- lambda, i.name);
+ if (!i.def) {
+ error("function '%1%' called without required argument '%2%'",
+ (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
+ symbols[i.name])
+ .atPos(lambda.pos)
+ .withTrace(pos, "from call site")
+ .withFrame(*fun.lambda.env, lambda)
+ .debugThrow<TypeError>();
+ }
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
@@ -1383,13 +1494,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.formals->has(i.name)) {
std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals)
- formalNames.insert(formal.name);
- throwTypeError(
- pos,
- Suggestions::bestMatches(formalNames, i.name),
- "%1% called with unexpected argument '%2%'",
- lambda,
- i.name);
+ formalNames.insert(symbols[formal.name]);
+ auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
+ error("function '%1%' called with unexpected argument '%2%'",
+ (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
+ symbols[i.name])
+ .atPos(lambda.pos)
+ .withTrace(pos, "from call site")
+ .withSuggestions(suggestions)
+ .withFrame(*fun.lambda.env, lambda)
+ .debugThrow<TypeError>();
}
abort(); // can't happen
}
@@ -1400,14 +1514,27 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Evaluate the body. */
try {
+ auto dts = debugRepl
+ ? makeDebugTraceStacker(
+ *this, *lambda.body, env2, positions[lambda.pos],
+ "while calling %s",
+ lambda.name
+ ? concatStrings("'", symbols[lambda.name], "'")
+ : "anonymous lambda")
+ : nullptr;
+
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
- addErrorTrace(e, lambda.pos, "while evaluating %s",
- (lambda.name.set()
- ? "'" + (const std::string &) lambda.name + "'"
- : "anonymous lambda"));
- addErrorTrace(e, pos, "from call site%s", "");
+ addErrorTrace(
+ e,
+ lambda.pos,
+ "while calling %s",
+ lambda.name
+ ? concatStrings("'", symbols[lambda.name], "'")
+ : "anonymous lambda",
+ true);
+ if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
}
throw;
}
@@ -1426,9 +1553,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return;
} else {
/* We have all the arguments, so call the primop. */
+ auto name = vCur.primOp->name;
+
nrPrimOpCalls++;
- if (countCalls) primOpCalls[vCur.primOp->name]++;
- vCur.primOp->fun(*this, pos, args, vCur);
+ if (countCalls) primOpCalls[name]++;
+
+ try {
+ vCur.primOp->fun(*this, noPos, args, vCur);
+ } catch (Error & e) {
+ addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
+ throw;
+ }
nrArgs -= argsLeft;
args += argsLeft;
@@ -1463,9 +1598,20 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i];
+ auto name = primOp->primOp->name;
nrPrimOpCalls++;
- if (countCalls) primOpCalls[primOp->primOp->name]++;
- primOp->primOp->fun(*this, pos, vArgs, vCur);
+ if (countCalls) primOpCalls[name]++;
+
+ try {
+ // TODO:
+ // 1. Unify this and above code. Heavily redundant.
+ // 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
+ // so the debugger allows to inspect the wrong parameters passed to the builtin.
+ primOp->primOp->fun(*this, noPos, vArgs, vCur);
+ } catch (Error & e) {
+ addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
+ throw;
+ }
nrArgs -= argsLeft;
args += argsLeft;
@@ -1478,14 +1624,18 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
heap-allocate a copy and use that instead. */
Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur;
- /* !!! Should we use the attr pos here? */
- callFunction(*functor->value, 2, args2, vCur, pos);
+ try {
+ callFunction(*functor->value, 2, args2, vCur, functor->pos);
+ } catch (Error & e) {
+ e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
+ throw;
+ }
nrArgs--;
args++;
}
else
- throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur);
+ error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow<TypeError>();
}
vRes = vCur;
@@ -1549,13 +1699,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) {
attrs.insert(*j);
} else if (!i.def) {
- throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%')
-
+ error(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
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
-https://nixos.org/manual/nix/stable/#ss-functions.)", 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>();
}
}
}
@@ -1578,16 +1727,17 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v)
{
- (state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v);
+ // We cheat in the parser, and pass the position of the condition as the position of the if itself.
+ (state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
}
void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
- if (!state.evalBool(env, cond, pos)) {
+ if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out;
- cond->show(out);
- throwAssertionError(pos, "assertion '%1%' failed", out.str());
+ cond->show(state.symbols, out);
+ state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
}
body->eval(state, env, v);
}
@@ -1595,7 +1745,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{
- v.mkBool(!state.evalBool(env, e));
+ v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: !
}
@@ -1603,7 +1753,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
{
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
- v.mkBool(state.eqValues(v1, v2));
+ v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
}
@@ -1611,33 +1761,33 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
{
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
- v.mkBool(!state.eqValues(v1, v2));
+ v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
}
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{
- v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos));
+ v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
}
void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
{
- v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
+ v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
}
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{
- v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos));
+ v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
}
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{
Value v1, v2;
- state.evalAttrs(env, e1, v1);
- state.evalAttrs(env, e2, v2);
+ state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
+ state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.nrOpUpdates++;
@@ -1676,18 +1826,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2);
Value * lists[2] = { &v1, &v2 };
- state.concatLists(v, 2, lists, pos);
+ state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate");
}
-void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos)
+void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
{
nrListConcats++;
Value * nonEmpty = 0;
size_t len = 0;
for (size_t n = 0; n < nrLists; ++n) {
- forceList(*lists[n], pos);
+ forceList(*lists[n], pos, errorCtx);
auto l = lists[n]->listSize();
len += l;
if (l) nonEmpty = lists[n];
@@ -1764,20 +1914,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n;
nf += vTmp.fpoint;
} else
- throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
+ state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
- throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
+ state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
- auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first);
+ auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
sSize += part->size();
s.emplace_back(std::move(part));
}
@@ -1791,7 +1941,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf);
else if (firstType == nPath) {
if (!context.empty())
- throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
+ state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(canonPath(str()));
} else
v.mkStringMove(c_str(), context);
@@ -1800,7 +1950,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
void ExprPos::eval(EvalState & state, Env & env, Value & v)
{
- state.mkPos(v, ptr(&pos));
+ state.mkPos(v, pos);
}
@@ -1818,9 +1968,15 @@ void EvalState::forceValueDeep(Value & v)
if (v.type() == nAttrs) {
for (auto & i : *v.attrs)
try {
+ // If the value is a thunk, we're evaling. Otherwise no trace necessary.
+ auto dts = debugRepl && i.value->isThunk()
+ ? makeDebugTraceStacker(*this, *i.value->thunk.expr, *i.value->thunk.env, positions[i.pos],
+ "while evaluating the attribute '%1%'", symbols[i.name])
+ : nullptr;
+
recurse(*i.value);
} catch (Error & e) {
- addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name);
+ addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]);
throw;
}
}
@@ -1835,32 +1991,47 @@ void EvalState::forceValueDeep(Value & v)
}
-NixInt EvalState::forceInt(Value & v, const Pos & pos)
+NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (v.type() != nInt)
- throwTypeError(pos, "value is %1% while an integer was expected", v);
- return v.integer;
+ try {
+ forceValue(v, pos);
+ if (v.type() != nInt)
+ error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
+ return v.integer;
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
-NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
+NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (v.type() == nInt)
- return v.integer;
- else if (v.type() != nFloat)
- throwTypeError(pos, "value is %1% while a float was expected", v);
- return v.fpoint;
+ try {
+ forceValue(v, pos);
+ if (v.type() == nInt)
+ return v.integer;
+ else if (v.type() != nFloat)
+ error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
+ return v.fpoint;
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
-bool EvalState::forceBool(Value & v, const Pos & pos)
+bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (v.type() != nBool)
- throwTypeError(pos, "value is %1% while a Boolean was expected", v);
- return v.boolean;
+ try {
+ forceValue(v, pos);
+ if (v.type() != nBool)
+ error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
+ return v.boolean;
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
@@ -1870,24 +2041,30 @@ bool EvalState::isFunctor(Value & fun)
}
-void EvalState::forceFunction(Value & v, const Pos & pos)
+void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (v.type() != nFunction && !isFunctor(v))
- throwTypeError(pos, "value is %1% while a function was expected", v);
+ try {
+ forceValue(v, pos);
+ if (v.type() != nFunction && !isFunctor(v))
+ error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
}
-std::string_view EvalState::forceString(Value & v, const Pos & pos)
+std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- forceValue(v, pos);
- if (v.type() != nString) {
- if (pos)
- throwTypeError(pos, "value is %1% while a string was expected", v);
- else
- throwTypeError("value is %1% while a string was expected", v);
+ try {
+ forceValue(v, pos);
+ if (v.type() != nString)
+ error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
+ return v.string.s;
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
}
- return v.string.s;
}
@@ -1931,24 +2108,19 @@ NixStringContext Value::getContext(const Store & store)
}
-std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos)
+std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
{
- auto s = forceString(v, pos);
+ auto s = forceString(v, pos, errorCtx);
copyContext(v, context);
return s;
}
-std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos)
+std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
{
- auto s = forceString(v, pos);
+ auto s = forceString(v, pos, errorCtx);
if (v.string.context) {
- if (pos)
- throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
- v.string.s, v.string.context[0]);
- else
- throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
- v.string.s, v.string.context[0]);
+ 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>();
}
return s;
}
@@ -1959,27 +2131,28 @@ bool EvalState::isDerivation(Value & v)
if (v.type() != nAttrs) return false;
Bindings::iterator i = v.attrs->find(sType);
if (i == v.attrs->end()) return false;
- forceValue(*i->value, *i->pos);
+ forceValue(*i->value, i->pos);
if (i->value->type() != nString) return false;
return strcmp(i->value->string.s, "derivation") == 0;
}
-std::optional<std::string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
+std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value & v,
PathSet & context, bool coerceMore, bool copyToStore)
{
auto i = v.attrs->find(sToString);
if (i != v.attrs->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
- return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned();
+ return coerceToString(pos, v1, context, coerceMore, copyToStore,
+ "while evaluating the result of the `toString` attribute").toOwned();
}
return {};
}
-BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
- bool coerceMore, bool copyToStore, bool canonicalizePath)
+BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
+ bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
{
forceValue(v, pos);
@@ -2002,15 +2175,15 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
- if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string");
- return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
+ if (i == v.attrs->end())
+ error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
+ return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
}
if (v.type() == nExternal)
- return v.external->coerceToString(pos, context, coerceMore, copyToStore);
+ return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
if (coerceMore) {
-
/* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1";
@@ -2022,7 +2195,13 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
if (v.isList()) {
std::string result;
for (auto [n, v2] : enumerate(v.listItems())) {
- result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
+ try {
+ result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
+ "while evaluating one element of the list");
+ } catch (Error & e) {
+ e.addTrace(positions[pos], errorCtx);
+ throw;
+ }
if (n < v.listSize() - 1
/* !!! not quite correct */
&& (!v2->isList() || v2->listSize() != 0))
@@ -2032,14 +2211,14 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
}
}
- throwTypeError(pos, "cannot coerce %1% to a string", v);
+ error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
{
if (nix::isDerivation(path))
- throwEvalError("file names are not allowed to end in '%1%'", drvExtension);
+ error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
Path dstPath;
auto i = srcToStore.find(path);
@@ -2060,28 +2239,25 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
}
-Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
+Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
- auto path = coerceToString(pos, v, context, false, false).toOwned();
+ auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (path == "" || path[0] != '/')
- throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
+ error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path;
}
-StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context)
+StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
- auto path = coerceToString(pos, v, context, false, false).toOwned();
+ auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
return *storePath;
- throw EvalError({
- .msg = hintfmt("path '%1%' is not in the Nix store", path),
- .errPos = pos
- });
+ error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
-bool EvalState::eqValues(Value & v1, Value & v2)
+bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{
forceValue(v1, noPos);
forceValue(v2, noPos);
@@ -2101,7 +2277,6 @@ bool EvalState::eqValues(Value & v1, Value & v2)
if (v1.type() != v2.type()) return false;
switch (v1.type()) {
-
case nInt:
return v1.integer == v2.integer;
@@ -2120,7 +2295,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
case nList:
if (v1.listSize() != v2.listSize()) return false;
for (size_t n = 0; n < v1.listSize(); ++n)
- if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false;
+ if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false;
return true;
case nAttrs: {
@@ -2130,7 +2305,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
Bindings::iterator i = v1.attrs->find(sOutPath);
Bindings::iterator j = v2.attrs->find(sOutPath);
if (i != v1.attrs->end() && j != v2.attrs->end())
- return eqValues(*i->value, *j->value);
+ return eqValues(*i->value, *j->value, pos, errorCtx);
}
if (v1.attrs->size() != v2.attrs->size()) return false;
@@ -2138,7 +2313,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
/* Otherwise, compare the attributes one by one. */
Bindings::iterator i, j;
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
- if (i->name != j->name || !eqValues(*i->value, *j->value))
+ if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
return false;
return true;
@@ -2155,7 +2330,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return v1.fpoint == v2.fpoint;
default:
- throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2));
+ error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
}
@@ -2181,108 +2356,111 @@ void EvalState::printStats()
std::fstream fs;
if (outPath != "-")
fs.open(outPath, std::fstream::out);
- JSONObject topObj(outPath == "-" ? std::cerr : fs, true);
- topObj.attr("cpuTime",cpuTime);
- {
- auto envs = topObj.object("envs");
- envs.attr("number", nrEnvs);
- envs.attr("elements", nrValuesInEnvs);
- envs.attr("bytes", bEnvs);
- }
- {
- auto lists = topObj.object("list");
- lists.attr("elements", nrListElems);
- lists.attr("bytes", bLists);
- lists.attr("concats", nrListConcats);
- }
- {
- auto values = topObj.object("values");
- values.attr("number", nrValues);
- values.attr("bytes", bValues);
- }
- {
- auto syms = topObj.object("symbols");
- syms.attr("number", symbols.size());
- syms.attr("bytes", symbols.totalSize());
- }
- {
- auto sets = topObj.object("sets");
- sets.attr("number", nrAttrsets);
- sets.attr("bytes", bAttrsets);
- sets.attr("elements", nrAttrsInAttrsets);
- }
- {
- auto sizes = topObj.object("sizes");
- sizes.attr("Env", sizeof(Env));
- sizes.attr("Value", sizeof(Value));
- sizes.attr("Bindings", sizeof(Bindings));
- sizes.attr("Attr", sizeof(Attr));
- }
- topObj.attr("nrOpUpdates", nrOpUpdates);
- topObj.attr("nrOpUpdateValuesCopied", nrOpUpdateValuesCopied);
- topObj.attr("nrThunks", nrThunks);
- topObj.attr("nrAvoided", nrAvoided);
- topObj.attr("nrLookups", nrLookups);
- topObj.attr("nrPrimOpCalls", nrPrimOpCalls);
- topObj.attr("nrFunctionCalls", nrFunctionCalls);
+ json topObj = json::object();
+ topObj["cpuTime"] = cpuTime;
+ topObj["envs"] = {
+ {"number", nrEnvs},
+ {"elements", nrValuesInEnvs},
+ {"bytes", bEnvs},
+ };
+ topObj["list"] = {
+ {"elements", nrListElems},
+ {"bytes", bLists},
+ {"concats", nrListConcats},
+ };
+ topObj["values"] = {
+ {"number", nrValues},
+ {"bytes", bValues},
+ };
+ topObj["symbols"] = {
+ {"number", symbols.size()},
+ {"bytes", symbols.totalSize()},
+ };
+ topObj["sets"] = {
+ {"number", nrAttrsets},
+ {"bytes", bAttrsets},
+ {"elements", nrAttrsInAttrsets},
+ };
+ topObj["sizes"] = {
+ {"Env", sizeof(Env)},
+ {"Value", sizeof(Value)},
+ {"Bindings", sizeof(Bindings)},
+ {"Attr", sizeof(Attr)},
+ };
+ topObj["nrOpUpdates"] = nrOpUpdates;
+ topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
+ topObj["nrThunks"] = nrThunks;
+ topObj["nrAvoided"] = nrAvoided;
+ topObj["nrLookups"] = nrLookups;
+ topObj["nrPrimOpCalls"] = nrPrimOpCalls;
+ topObj["nrFunctionCalls"] = nrFunctionCalls;
#if HAVE_BOEHMGC
- {
- auto gc = topObj.object("gc");
- gc.attr("heapSize", heapSize);
- gc.attr("totalBytes", totalBytes);
- }
+ topObj["gc"] = {
+ {"heapSize", heapSize},
+ {"totalBytes", totalBytes},
+ };
#endif
if (countCalls) {
+ topObj["primops"] = primOpCalls;
{
- auto obj = topObj.object("primops");
- for (auto & i : primOpCalls)
- obj.attr(i.first, i.second);
- }
- {
- auto list = topObj.list("functions");
- for (auto & i : functionCalls) {
- auto obj = list.object();
- if (i.first->name.set())
- obj.attr("name", (const std::string &) i.first->name);
+ auto& list = topObj["functions"];
+ list = json::array();
+ for (auto & [fun, count] : functionCalls) {
+ json obj = json::object();
+ if (fun->name)
+ obj["name"] = (std::string_view) symbols[fun->name];
else
- obj.attr("name", nullptr);
- if (i.first->pos) {
- obj.attr("file", (const std::string &) i.first->pos.file);
- obj.attr("line", i.first->pos.line);
- obj.attr("column", i.first->pos.column);
+ obj["name"] = nullptr;
+ if (auto pos = positions[fun->pos]) {
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
+ obj["line"] = pos.line;
+ obj["column"] = pos.column;
}
- obj.attr("count", i.second);
+ obj["count"] = count;
+ list.push_back(obj);
}
}
{
- auto list = topObj.list("attributes");
+ auto list = topObj["attributes"];
+ list = json::array();
for (auto & i : attrSelects) {
- auto obj = list.object();
- if (i.first) {
- obj.attr("file", (const std::string &) i.first.file);
- obj.attr("line", i.first.line);
- obj.attr("column", i.first.column);
+ json obj = json::object();
+ if (auto pos = positions[i.first]) {
+ if (auto path = std::get_if<Path>(&pos.origin))
+ obj["file"] = *path;
+ obj["line"] = pos.line;
+ obj["column"] = pos.column;
}
- obj.attr("count", i.second);
+ obj["count"] = i.second;
+ list.push_back(obj);
}
}
}
if (getEnv("NIX_SHOW_SYMBOLS").value_or("0") != "0") {
- auto list = topObj.list("symbols");
- symbols.dump([&](const std::string & s) { list.elem(s); });
+ // XXX: overrides earlier assignment
+ topObj["symbols"] = json::array();
+ auto &list = topObj["symbols"];
+ symbols.dump([&](const std::string & s) { list.emplace_back(s); });
+ }
+ if (outPath == "-") {
+ std::cerr << topObj.dump(2) << std::endl;
+ } else {
+ fs << topObj.dump(2) << std::endl;
}
}
}
-std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
+std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
{
- throw TypeError({
- .msg = hintfmt("cannot coerce %1% to a string", showType()),
- .errPos = pos
+ auto e = TypeError({
+ .msg = hintfmt("cannot coerce %1% to a string", showType())
});
+ e.addTrace(pos, errorCtx);
+ throw e;
}
@@ -2325,6 +2503,23 @@ Strings EvalSettings::getDefaultNixPath()
return res;
}
+bool EvalSettings::isPseudoUrl(std::string_view s)
+{
+ if (s.compare(0, 8, "channel:") == 0) return true;
+ size_t pos = s.find("://");
+ if (pos == std::string::npos) return false;
+ std::string scheme(s, 0, pos);
+ return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git" || scheme == "s3" || scheme == "ssh";
+}
+
+std::string EvalSettings::resolvePseudoUrl(std::string_view url)
+{
+ if (hasPrefix(url, "channel:"))
+ return "https://nixos.org/channels/" + std::string(url.substr(8)) + "/nixexprs.tar.xz";
+ else
+ return std::string(url);
+}
+
EvalSettings evalSettings;
static GlobalConfig::Register rEvalSettings(&evalSettings);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 7ed376e8d..9b3d160ea 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -13,7 +13,6 @@
#include <unordered_map>
#include <mutex>
-
namespace nix {
@@ -23,18 +22,22 @@ class StorePath;
enum RepairFlag : bool;
-typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
-
+typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
struct PrimOp
{
PrimOpFun fun;
size_t arity;
- Symbol name;
+ std::string name;
std::vector<std::string> args;
const char * doc = nullptr;
};
+#if HAVE_BOEHMGC
+ typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap;
+#else
+ typedef std::map<std::string, Value *> ValMap;
+#endif
struct Env
{
@@ -44,6 +47,10 @@ struct Env
Value * values[0];
};
+void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env);
+void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl = 0);
+
+std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
void copyContext(const Value & v, PathSet & context);
@@ -53,7 +60,8 @@ void copyContext(const Value & v, PathSet & context);
typedef std::map<Path, StorePath> SrcToStore;
-std::ostream & operator << (std::ostream & str, const Value & v);
+std::string printValue(const EvalState & state, const Value & v);
+std::ostream & operator << (std::ostream & os, const ValueType t);
typedef std::pair<std::string, std::string> SearchPathElem;
@@ -68,11 +76,60 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache();
+struct DebugTrace {
+ std::shared_ptr<AbstractPos> pos;
+ const Expr & expr;
+ const Env & env;
+ hintformat hint;
+ 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...) });
+ }
-class EvalState
+ [[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>
{
public:
SymbolTable symbols;
+ PosTable positions;
+
+ static inline std::string derivationNixPath = "//builtin/derivation.nix";
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
@@ -82,7 +139,8 @@ public:
sOutputHash, sOutputHashAlgo, sOutputHashMode,
sRecurseForDerivations,
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
- sPrefix;
+ sPrefix,
+ sOutputSpecified;
Symbol sDerivationNix;
/* If set, force copying files to the Nix store even if they
@@ -104,12 +162,62 @@ public:
RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;
+ /* Debugger */
+ void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
+ bool debugStop;
+ bool debugQuit;
+ int trylevel;
+ std::list<DebugTrace> debugTraces;
+ std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
+ const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
+ {
+ auto i = exprEnvs.find(&expr);
+ if (i != exprEnvs.end())
+ return i->second;
+ else
+ return std::shared_ptr<const StaticEnv>();;
+ }
+
+ void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
+
+ template<class E>
+ [[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);
+ }
+
+ ErrorBuilder * errorBuilder;
+
+ template<typename... Args>
+ [[nodiscard, gnu::noinline]]
+ ErrorBuilder & error(const Args & ... args) {
+ errorBuilder = ErrorBuilder::create(*this, args...);
+ return *errorBuilder;
+ }
+
private:
SrcToStore srcToStore;
/* A cache from path names to parse trees. */
#if HAVE_BOEHMGC
- typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *> > > FileParseCache;
+ typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *>>> FileParseCache;
#else
typedef std::map<Path, Expr *> FileParseCache;
#endif
@@ -117,7 +225,7 @@ private:
/* A cache from path names to values. */
#if HAVE_BOEHMGC
- typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value> > > FileEvalCache;
+ typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value>>> FileEvalCache;
#else
typedef std::map<Path, Value> FileEvalCache;
#endif
@@ -180,10 +288,10 @@ public:
/* Parse a Nix expression from the specified file. */
Expr * parseExprFromFile(const Path & path);
- Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
+ Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
/* Parse a Nix expression from the specified string. */
- Expr * parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv);
+ Expr * parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const Path & basePath);
Expr * parseStdin();
@@ -193,7 +301,7 @@ public:
trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
- /* Like `cacheFile`, but with an already parsed expression. */
+ /* Like `evalFile`, but with an already parsed expression. */
void cacheFile(
const Path & path,
const Path & resolvedPath,
@@ -205,7 +313,7 @@ public:
/* Look up a file in the search path. */
Path findFile(const std::string_view path);
- Path findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos = noPos);
+ Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/* If the specified search path element is a URI, download it. */
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
@@ -217,14 +325,14 @@ public:
/* Evaluation the expression, then verify that it has the expected
type. */
inline bool evalBool(Env & env, Expr * e);
- inline bool evalBool(Env & env, Expr * e, const Pos & pos);
- inline void evalAttrs(Env & env, Expr * e, Value & v);
+ inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
+ inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
/* If `v' is a thunk, enter it and overwrite `v' with the result
of the evaluation of the thunk. If `v' is a delayed function
application, call the function and overwrite `v' with the
result. Otherwise, this is a no-op. */
- inline void forceValue(Value & v, const Pos & pos);
+ inline void forceValue(Value & v, const PosIdx pos);
template <typename Callable>
inline void forceValue(Value & v, Callable getPos);
@@ -234,45 +342,52 @@ public:
void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */
- NixInt forceInt(Value & v, const Pos & pos);
- NixFloat forceFloat(Value & v, const Pos & pos);
- bool forceBool(Value & v, const Pos & pos);
+ NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
+ NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
+ bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
- void forceAttrs(Value & v, const Pos & pos);
+ void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
template <typename Callable>
- inline void forceAttrs(Value & v, Callable getPos);
+ inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
+
+ inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
+ void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
+ std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
+ std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
+ std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
- inline void forceList(Value & v, const Pos & pos);
- void forceFunction(Value & v, const Pos & pos); // either lambda or primop
- std::string_view forceString(Value & v, const Pos & pos = noPos);
- std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos);
- std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos);
+ [[gnu::noinline]]
+ void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
+ [[gnu::noinline]]
+ void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const;
+public:
/* Return true iff the value `v' denotes a derivation (i.e. a
set with attribute `type = "derivation"'). */
bool isDerivation(Value & v);
- std::optional<std::string> tryAttrsToString(const Pos & pos, Value & v,
+ std::optional<std::string> tryAttrsToString(const PosIdx pos, Value & v,
PathSet & context, bool coerceMore = false, bool copyToStore = true);
/* String coercion. Converts strings, paths and derivations to a
string. If `coerceMore' is set, also converts nulls, integers,
booleans and lists to a string. If `copyToStore' is set,
referenced paths are copied to the Nix store as a side effect. */
- BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context,
+ BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true,
- bool canonicalizePath = true);
+ bool canonicalizePath = true,
+ std::string_view errorCtx = "");
std::string copyPathToStore(PathSet & context, const Path & path);
/* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */
- Path coerceToPath(const Pos & pos, Value & v, PathSet & context);
+ Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
/* Like coerceToPath, but the result must be a store path. */
- StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context);
+ StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
public:
@@ -281,7 +396,7 @@ public:
Env & baseEnv;
/* The same, but used during parsing to resolve variables. */
- StaticEnv staticBaseEnv; // !!! should be private
+ std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
private:
@@ -305,7 +420,7 @@ public:
struct Doc
{
Pos pos;
- std::optional<Symbol> name;
+ std::optional<std::string> name;
size_t arity;
std::vector<std::string> args;
const char * doc;
@@ -321,21 +436,25 @@ private:
friend struct ExprAttrs;
friend struct ExprLet;
- Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
- const PathView basePath, StaticEnv & staticEnv);
+ Expr * parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv);
public:
/* Do a deep equality test between two values. That is, list
elements and attributes are compared recursively. */
- bool eqValues(Value & v1, Value & v2);
+ bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
bool isFunctor(Value & fun);
// FIXME: use std::span
- void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos);
+ void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
- void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos)
+ void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
@@ -349,7 +468,7 @@ public:
inline Value * allocValue();
inline Env & allocEnv(size_t size);
- Value * allocAttr(Value & vAttrs, const Symbol & name);
+ Value * allocAttr(Value & vAttrs, Symbol name);
Value * allocAttr(Value & vAttrs, std::string_view name);
Bindings * allocBindings(size_t capacity);
@@ -361,9 +480,9 @@ public:
void mkList(Value & v, size_t length);
void mkThunk_(Value & v, Expr * expr);
- void mkPos(Value & v, ptr<Pos> pos);
+ void mkPos(Value & v, PosIdx pos);
- void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos);
+ void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/* Print statistics. */
void printStats();
@@ -391,7 +510,7 @@ private:
bool countCalls;
- typedef std::map<Symbol, size_t> PrimOpCalls;
+ typedef std::map<std::string, size_t> PrimOpCalls;
PrimOpCalls primOpCalls;
typedef std::map<ExprLambda *, size_t> FunctionCalls;
@@ -399,7 +518,7 @@ private:
void incrFunctionCall(ExprLambda * fun);
- typedef std::map<Pos, size_t> AttrSelects;
+ typedef std::map<PosIdx, size_t> AttrSelects;
AttrSelects attrSelects;
friend struct ExprOpUpdate;
@@ -410,13 +529,23 @@ private:
friend struct ExprFloat;
friend struct ExprPath;
friend struct ExprSelect;
- friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
- friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v);
- friend void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v);
+ friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v);
+ friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v);
+ friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend struct Value;
};
+struct DebugTraceStacker {
+ DebugTraceStacker(EvalState & evalState, DebugTrace t);
+ ~DebugTraceStacker()
+ {
+ // assert(evalState.debugTraces.front() == trace);
+ evalState.debugTraces.pop_front();
+ }
+ EvalState & evalState;
+ DebugTrace trace;
+};
/* Return a string representing the type of the value `v'. */
std::string_view showType(ValueType type);
@@ -444,6 +573,10 @@ struct EvalSettings : Config
static Strings getDefaultNixPath();
+ static bool isPseudoUrl(std::string_view s);
+
+ static std::string resolvePseudoUrl(std::string_view url);
+
Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation",
"Whether builtin functions that allow executing native code should be enabled."};
@@ -501,12 +634,28 @@ struct EvalSettings : Config
Setting<bool> useEvalCache{this, true, "eval-cache",
"Whether to use the flake evaluation cache."};
+
+ Setting<bool> ignoreExceptionsDuringTry{this, false, "ignore-try",
+ R"(
+ If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in
+ debug mode (using the --debugger flag). By default the debugger will pause on all exceptions.
+ )"};
+
+ Setting<bool> traceVerbose{this, false, "trace-verbose",
+ "Whether `builtins.traceVerbose` should trace its first argument when evaluated."};
};
extern EvalSettings evalSettings;
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));
+}
+
}
#include "eval-inline.hh"
diff --git a/src/libexpr/fetchurl.nix b/src/libexpr/fetchurl.nix
index 02531103b..9d1b61d7f 100644
--- a/src/libexpr/fetchurl.nix
+++ b/src/libexpr/fetchurl.nix
@@ -12,13 +12,13 @@
, executable ? false
, unpack ? false
, name ? baseNameOf (toString url)
+, impure ? false
}:
-derivation {
+derivation ({
builder = "builtin:fetchurl";
# New-style output content requirements.
- inherit outputHashAlgo outputHash;
outputHashMode = if unpack || executable then "recursive" else "flat";
inherit name url executable unpack;
@@ -38,4 +38,6 @@ derivation {
# To make "nix-prefetch-url" work.
urls = [ url ];
-}
+} // (if impure
+ then { __impure = true; }
+ else { inherit outputHashAlgo outputHash; }))
diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix
index 932ac5e90..8061db3df 100644
--- a/src/libexpr/flake/call-flake.nix
+++ b/src/libexpr/flake/call-flake.nix
@@ -43,7 +43,7 @@ let
outputs = flake.outputs (inputs // { self = result; });
- result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
+ result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; };
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index a811e59a1..89ddbde7e 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -31,7 +31,7 @@ static void writeTrustedList(const TrustedList & trustedList)
void ConfigFile::apply()
{
- std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix", "flake-registry"};
+ std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry"};
for (auto & [name, value] : settings) {
@@ -50,15 +50,13 @@ void ConfigFile::apply()
else
assert(false);
- if (!whitelist.count(baseName)) {
- auto trustedList = readTrustedList();
-
+ if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) {
bool trusted = false;
- if (nix::fetchSettings.acceptFlakeConfig){
- trusted = true;
- } else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
+ auto trustedList = readTrustedList();
+ auto tlname = get(trustedList, name);
+ if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
trusted = *saved;
- warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
+ printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS);
} else {
// FIXME: filter ANSI escapes, newlines, \r, etc.
if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') {
@@ -69,9 +67,8 @@ void ConfigFile::apply()
writeTrustedList(trustedList);
}
}
-
if (!trusted) {
- warn("ignoring untrusted flake configuration setting '%s'", name);
+ warn("ignoring untrusted flake configuration setting '%s'.\nPass '%s' to trust it", name, "--accept-flake-config");
continue;
}
}
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 22257c6b3..fc4be5678 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -72,7 +72,7 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
return {std::move(tree), resolvedRef, lockedRef};
}
-static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
+static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
{
if (value.isThunk() && value.isTrivial())
state.forceValue(value, pos);
@@ -80,20 +80,20 @@ static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
static void expectType(EvalState & state, ValueType type,
- Value & value, const Pos & pos)
+ Value & value, const PosIdx pos)
{
forceTrivialValue(state, value, pos);
if (value.type() != type)
throw Error("expected %s but got %s at %s",
- showType(type), showType(value.type()), pos);
+ showType(type), showType(value.type()), state.positions[pos]);
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
- EvalState & state, Value * value, const Pos & pos,
+ EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath);
static FlakeInput parseFlakeInput(EvalState & state,
- const std::string & inputName, Value * value, const Pos & pos,
+ const std::string & inputName, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath)
{
expectType(state, nAttrs, *value, pos);
@@ -111,37 +111,39 @@ static FlakeInput parseFlakeInput(EvalState & state,
for (nix::Attr attr : *(value->attrs)) {
try {
if (attr.name == sUrl) {
- expectType(state, nString, *attr.value, *attr.pos);
+ expectType(state, nString, *attr.value, attr.pos);
url = attr.value->string.s;
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
- expectType(state, nBool, *attr.value, *attr.pos);
+ expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) {
- input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir, lockRootPath);
+ input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
} else if (attr.name == sFollows) {
- expectType(state, nString, *attr.value, *attr.pos);
+ expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->string.s));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
switch (attr.value->type()) {
case nString:
- attrs.emplace(attr.name, attr.value->string.s);
+ attrs.emplace(state.symbols[attr.name], attr.value->string.s);
break;
case nBool:
- attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean });
+ attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
break;
case nInt:
- attrs.emplace(attr.name, (long unsigned int)attr.value->integer);
+ attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
break;
default:
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
- attr.name, showType(*attr.value));
+ state.symbols[attr.name], showType(*attr.value));
}
}
} catch (Error & e) {
- e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name));
+ e.addTrace(
+ state.positions[attr.pos],
+ hintfmt("while evaluating flake attribute '%s'", state.symbols[attr.name]));
throw;
}
}
@@ -150,13 +152,13 @@ static FlakeInput parseFlakeInput(EvalState & state,
try {
input.ref = FlakeRef::fromAttrs(attrs);
} catch (Error & e) {
- e.addTrace(pos, hintfmt("in flake input"));
+ e.addTrace(state.positions[pos], hintfmt("while evaluating flake input"));
throw;
}
else {
attrs.erase("url");
if (!attrs.empty())
- throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
+ throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]);
if (url)
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
}
@@ -168,7 +170,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
- EvalState & state, Value * value, const Pos & pos,
+ EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath)
{
std::map<FlakeId, FlakeInput> inputs;
@@ -176,11 +178,11 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
- inputs.emplace(inputAttr.name,
+ inputs.emplace(state.symbols[inputAttr.name],
parseFlakeInput(state,
- inputAttr.name,
+ state.symbols[inputAttr.name],
inputAttr.value,
- *inputAttr.pos,
+ inputAttr.pos,
baseDir,
lockRootPath));
}
@@ -218,28 +220,28 @@ static Flake getFlake(
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
- expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
+ expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1));
if (auto description = vInfo.attrs->get(state.sDescription)) {
- expectType(state, nString, *description->value, *description->pos);
+ expectType(state, nString, *description->value, description->pos);
flake.description = description->value->string.s;
}
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
- flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath);
+ flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath);
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
- expectType(state, nFunction, *outputs->value, *outputs->pos);
+ expectType(state, nFunction, *outputs->value, outputs->pos);
if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
- flake.inputs.emplace(formal.name, FlakeInput {
- .ref = parseFlakeRef(formal.name)
+ flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
+ .ref = parseFlakeRef(state.symbols[formal.name])
});
}
}
@@ -250,35 +252,41 @@ static Flake getFlake(
auto sNixConfig = state.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
- expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos);
+ expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
for (auto & setting : *nixConfig->value->attrs) {
- forceTrivialValue(state, *setting.value, *setting.pos);
+ forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
- flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))});
+ flake.config.settings.emplace(
+ state.symbols[setting.name],
+ std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) {
PathSet emptyContext = {};
flake.config.settings.emplace(
- setting.name,
- state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
+ state.symbols[setting.name],
+ state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
}
else if (setting.value->type() == nInt)
- flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)});
+ flake.config.settings.emplace(
+ state.symbols[setting.name],
+ state.forceInt(*setting.value, setting.pos, ""));
else if (setting.value->type() == nBool)
- flake.config.settings.insert({setting.name, Explicit<bool> { state.forceBool(*setting.value, *setting.pos) }});
+ flake.config.settings.emplace(
+ state.symbols[setting.name],
+ Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
- setting.name, showType(*setting.value));
- ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos));
+ state.symbols[setting.name], showType(*setting.value));
+ ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
}
- flake.config.settings.insert({setting.name, ss});
+ flake.config.settings.emplace(state.symbols[setting.name], ss);
}
else
throw TypeError("flake configuration setting '%s' is %s",
- setting.name, showType(*setting.value));
+ state.symbols[setting.name], showType(*setting.value));
}
}
@@ -288,7 +296,7 @@ static Flake getFlake(
attr.name != sOutputs &&
attr.name != sNixConfig)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
- lockedRef, attr.name, *attr.pos);
+ lockedRef, state.symbols[attr.name], state.positions[attr.pos]);
}
return flake;
@@ -333,7 +341,6 @@ LockedFlake lockFlake(
debug("old lock file: %s", oldLockFile);
- // FIXME: check whether all overrides are used.
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> overridesUsed, updatesUsed;
@@ -346,7 +353,7 @@ LockedFlake lockFlake(
std::function<void(
const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
+ ref<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath,
@@ -355,9 +362,15 @@ LockedFlake lockFlake(
computeLocks;
computeLocks = [&](
+ /* The inputs of this node, either from flake.nix or
+ flake.lock. */
const FlakeInputs & flakeInputs,
- std::shared_ptr<Node> node,
+ /* The node whose locks are to be updated.*/
+ ref<Node> node,
+ /* The path to this node in the lock file graph. */
const InputPath & inputPathPrefix,
+ /* The old node, if any, from which locks can be
+ copied. */
std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath,
const Path & parentPath,
@@ -376,6 +389,18 @@ LockedFlake lockFlake(
}
}
+ /* Check whether this input has overrides for a
+ non-existent input. */
+ for (auto [inputPath, inputOverride] : overrides) {
+ auto inputPath2(inputPath);
+ auto follow = inputPath2.back();
+ inputPath2.pop_back();
+ if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
+ warn(
+ "input '%s' has an override for a non-existent input '%s'",
+ printInputPath(inputPathPrefix), follow);
+ }
+
/* Go over the flake inputs, resolve/fetch them if
necessary (i.e. if they're new or the flakeref changed
from what's in the lock file). */
@@ -433,7 +458,7 @@ LockedFlake lockFlake(
/* Copy the input from the old lock since its flakeref
didn't change and there is no override from a
higher level flake. */
- auto childNode = std::make_shared<LockedNode>(
+ auto childNode = make_ref<LockedNode>(
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
node->inputs.insert_or_assign(id, childNode);
@@ -462,14 +487,14 @@ LockedFlake lockFlake(
.isFlake = (*lockedNode)->isFlake,
});
} else if (auto follows = std::get_if<1>(&i.second)) {
- if (! trustLock) {
+ if (!trustLock) {
// It is possible that the flake has changed,
- // so we must confirm all the follows that are in the lockfile are also in the flake.
+ // so we must confirm all the follows that are in the lock file are also in the flake.
auto overridePath(inputPath);
overridePath.push_back(i.first);
auto o = overrides.find(overridePath);
// If the override disappeared, we have to refetch the flake,
- // since some of the inputs may not be present in the lockfile.
+ // since some of the inputs may not be present in the lock file.
if (o == overrides.end()) {
mustRefetch = true;
// There's no point populating the rest of the fake inputs,
@@ -502,8 +527,17 @@ LockedFlake lockFlake(
this input. */
debug("creating new input '%s'", inputPathS);
- if (!lockFlags.allowMutable && !input.ref->input.isLocked())
- throw Error("cannot update flake input '%s' in pure mode", inputPathS);
+ if (!lockFlags.allowUnlocked && !input.ref->input.isLocked())
+ throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS);
+
+ /* Note: in case of an --override-input, we use
+ the *original* ref (input2.ref) for the
+ "original" field, rather than the
+ override. This ensures that the override isn't
+ nuked the next time we update the lock
+ file. That is, overrides are sticky unless you
+ use --no-write-lock-file. */
+ auto ref = input2.ref ? *input2.ref : *input.ref;
if (input.isFlake) {
Path localPath = parentPath;
@@ -516,15 +550,7 @@ LockedFlake lockFlake(
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
- /* Note: in case of an --override-input, we use
- the *original* ref (input2.ref) for the
- "original" field, rather than the
- override. This ensures that the override isn't
- nuked the next time we update the lock
- file. That is, overrides are sticky unless you
- use --no-write-lock-file. */
- auto childNode = std::make_shared<LockedNode>(
- inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref);
+ auto childNode = make_ref<LockedNode>(inputFlake.lockedRef, ref);
node->inputs.insert_or_assign(id, childNode);
@@ -544,15 +570,19 @@ LockedFlake lockFlake(
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
- inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
- oldLock ? lockRootPath : inputPath, localPath, false);
+ inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(),
+ oldLock ? lockRootPath : inputPath,
+ localPath,
+ false);
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);
- node->inputs.insert_or_assign(id,
- std::make_shared<LockedNode>(lockedRef, *input.ref, false));
+
+ auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
+
+ node->inputs.insert_or_assign(id, childNode);
}
}
@@ -567,8 +597,13 @@ LockedFlake lockFlake(
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks(
- flake.inputs, newLockFile.root, {},
- lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false);
+ flake.inputs,
+ newLockFile.root,
+ {},
+ lockFlags.recreateLockFile ? nullptr : oldLockFile.root.get_ptr(),
+ {},
+ parentPath,
+ false);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
@@ -591,9 +626,9 @@ LockedFlake lockFlake(
if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) {
- if (!newLockFile.isImmutable()) {
+ if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty)
- warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
+ warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
@@ -704,19 +739,20 @@ void callFlake(EvalState & state,
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
-static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
+ std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked())
- throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
+ throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
callFlake(state,
lockFlake(state, flakeRef,
LockFlags {
.updateLockFile = false,
+ .writeLockFile = false,
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
- .allowMutable = !evalSettings.pureEval,
+ .allowUnlocked = !evalSettings.pureEval,
}),
v);
}
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index 524b18af1..10301d8aa 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -108,11 +108,11 @@ struct LockFlags
bool applyNixConfig = false;
- /* Whether mutable flake references (i.e. those without a Git
+ /* Whether unlocked flake references (i.e. those without a Git
revision or similar) without a corresponding lock are
- allowed. Mutable flake references with a lock are always
+ allowed. Unlocked flake references with a lock are always
allowed. */
- bool allowMutable = true;
+ bool allowUnlocked = true;
/* Whether to commit changes to flake.lock. */
bool commitLockFile = false;
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index c1eae413f..eede493f8 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -176,7 +176,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
- FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
+ FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
fragment);
}
@@ -189,7 +189,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url);
auto query = decodeQuery(match[2]);
- path = canonPath(path + "/" + get(query, "dir").value_or(""));
+ path = canonPath(path + "/" + getOr(query, "dir", ""));
}
fetchers::Attrs attrs;
@@ -208,7 +208,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
input.parent = baseDir;
return std::make_pair(
- FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")),
+ FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
fragment);
}
}
@@ -238,4 +238,15 @@ std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)};
}
+std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
+ const std::string & url,
+ const std::optional<Path> & baseDir,
+ bool allowMissing,
+ bool isFlake)
+{
+ auto [prefix, outputsSpec] = parseOutputsSpec(url);
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
+ return {std::move(flakeRef), fragment, outputsSpec};
+}
+
}
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index 1fddfd9a0..a36d852a8 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -3,6 +3,7 @@
#include "types.hh"
#include "hash.hh"
#include "fetchers.hh"
+#include "path-with-outputs.hh"
#include <variant>
@@ -27,14 +28,14 @@ typedef std::string FlakeId;
* object that fetcher generates (usually via
* FlakeRef::fromAttrs(attrs) or parseFlakeRef(url) calls).
*
- * The actual fetch not have been performed yet (i.e. a FlakeRef may
+ * The actual fetch may not have been performed yet (i.e. a FlakeRef may
* be lazy), but the fetcher can be invoked at any time via the
* FlakeRef to ensure the store is populated with this input.
*/
struct FlakeRef
{
- /* fetcher-specific representation of the input, sufficient to
+ /* Fetcher-specific representation of the input, sufficient to
perform the fetch operation. */
fetchers::Input input;
@@ -79,4 +80,11 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
const std::string & url, const std::optional<Path> & baseDir = {});
+std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
+ const std::string & url,
+ const std::optional<Path> & baseDir = {},
+ bool allowMissing = false,
+ bool isFlake = true);
+
+
}
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index 60b52d578..a3ed90e1f 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -31,12 +31,12 @@ FlakeRef getFlakeRef(
}
LockedNode::LockedNode(const nlohmann::json & json)
- : lockedRef(getFlakeRef(json, "locked", "info"))
+ : lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info"
, originalRef(getFlakeRef(json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isLocked())
- throw Error("lockfile contains mutable lock '%s'",
+ throw Error("lock file contains mutable lock '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
@@ -49,15 +49,15 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{
auto pos = root;
- if (!pos) return {};
-
for (auto & elem : path) {
if (auto i = get(pos->inputs, elem)) {
if (auto node = std::get_if<0>(&*i))
pos = *node;
else if (auto follows = std::get_if<1>(&*i)) {
- pos = findInput(*follows);
- if (!pos) return {};
+ if (auto p = findInput(*follows))
+ pos = ref(p);
+ else
+ return {};
}
} else
return {};
@@ -72,7 +72,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
if (version < 5 || version > 7)
throw Error("lock file '%s' has unsupported version %d", path, version);
- std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap;
+ std::map<std::string, ref<Node>> nodeMap;
std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs;
@@ -93,12 +93,12 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
auto jsonNode2 = nodes.find(inputKey);
if (jsonNode2 == nodes.end())
throw Error("lock file references missing node '%s'", inputKey);
- auto input = std::make_shared<LockedNode>(*jsonNode2);
+ auto input = make_ref<LockedNode>(*jsonNode2);
k = nodeMap.insert_or_assign(inputKey, input).first;
getInputs(*input, *jsonNode2);
}
- if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second))
- node.inputs.insert_or_assign(i.key(), child);
+ if (auto child = k->second.dynamic_pointer_cast<LockedNode>())
+ node.inputs.insert_or_assign(i.key(), ref(child));
else
// FIXME: replace by follows node
throw Error("lock file contains cycle to root node");
@@ -122,9 +122,9 @@ nlohmann::json LockFile::toJSON() const
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
std::unordered_set<std::string> keys;
- std::function<std::string(const std::string & key, std::shared_ptr<const Node> node)> dumpNode;
+ std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
- dumpNode = [&](std::string key, std::shared_ptr<const Node> node) -> std::string
+ dumpNode = [&](std::string key, ref<const Node> node) -> std::string
{
auto k = nodeKeys.find(node);
if (k != nodeKeys.end())
@@ -159,10 +159,11 @@ nlohmann::json LockFile::toJSON() const
n["inputs"] = std::move(inputs);
}
- if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
+ if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) {
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
- if (!lockedNode->isFlake) n["flake"] = false;
+ if (!lockedNode->isFlake)
+ n["flake"] = false;
}
nodes[key] = std::move(n);
@@ -201,13 +202,13 @@ void LockFile::write(const Path & path) const
writeFile(path, fmt("%s\n", *this));
}
-bool LockFile::isImmutable() const
+std::optional<FlakeRef> LockFile::isUnlocked() const
{
- std::unordered_set<std::shared_ptr<const Node>> nodes;
+ std::set<ref<const Node>> nodes;
- std::function<void(std::shared_ptr<const Node> node)> visit;
+ std::function<void(ref<const Node> node)> visit;
- visit = [&](std::shared_ptr<const Node> node)
+ visit = [&](ref<const Node> node)
{
if (!nodes.insert(node).second) return;
for (auto & i : node->inputs)
@@ -219,11 +220,12 @@ bool LockFile::isImmutable() const
for (auto & i : nodes) {
if (i == root) continue;
- auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
- if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false;
+ auto node = i.dynamic_pointer_cast<const LockedNode>();
+ if (node && !node->lockedRef.input.isLocked())
+ return node->lockedRef;
}
- return true;
+ return {};
}
bool LockFile::operator ==(const LockFile & other) const
@@ -247,12 +249,12 @@ InputPath parseInputPath(std::string_view s)
std::map<InputPath, Node::Edge> LockFile::getAllInputs() const
{
- std::unordered_set<std::shared_ptr<Node>> done;
+ std::set<ref<Node>> done;
std::map<InputPath, Node::Edge> res;
- std::function<void(const InputPath & prefix, std::shared_ptr<Node> node)> recurse;
+ std::function<void(const InputPath & prefix, ref<Node> node)> recurse;
- recurse = [&](const InputPath & prefix, std::shared_ptr<Node> node)
+ recurse = [&](const InputPath & prefix, ref<Node> node)
{
if (!done.insert(node).second) return;
diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh
index 96f1edc76..02e9bdfbc 100644
--- a/src/libexpr/flake/lockfile.hh
+++ b/src/libexpr/flake/lockfile.hh
@@ -20,7 +20,7 @@ struct LockedNode;
type LockedNode. */
struct Node : std::enable_shared_from_this<Node>
{
- typedef std::variant<std::shared_ptr<LockedNode>, InputPath> Edge;
+ typedef std::variant<ref<LockedNode>, InputPath> Edge;
std::map<FlakeId, Edge> inputs;
@@ -47,11 +47,13 @@ struct LockedNode : Node
struct LockFile
{
- std::shared_ptr<Node> root = std::make_shared<Node>();
+ ref<Node> root = make_ref<Node>();
LockFile() {};
LockFile(const nlohmann::json & json, const Path & path);
+ typedef std::map<ref<const Node>, std::string> KeyMap;
+
nlohmann::json toJSON() const;
std::string to_string() const;
@@ -60,7 +62,8 @@ struct LockFile
void write(const Path & path) const;
- bool isImmutable() const;
+ /* Check whether this lock file has any unlocked inputs. */
+ std::optional<FlakeRef> isUnlocked() const;
bool operator ==(const LockFile & other) const;
diff --git a/src/libexpr/function-trace.hh b/src/libexpr/function-trace.hh
index 472f2045e..e9a2526bd 100644
--- a/src/libexpr/function-trace.hh
+++ b/src/libexpr/function-trace.hh
@@ -8,7 +8,7 @@ namespace nix {
struct FunctionCallTrace
{
- const Pos & pos;
+ const Pos pos;
FunctionCallTrace(const Pos & pos);
~FunctionCallTrace();
};
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index bb7e77b61..1602fbffb 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -34,7 +34,7 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
outputName =
selectedOutputs.empty()
- ? get(drv.env, "outputName").value_or("out")
+ ? getOr(drv.env, "outputName", "out")
: *selectedOutputs.begin();
auto i = drv.outputs.find(outputName);
@@ -51,7 +51,7 @@ std::string DrvInfo::queryName() const
if (name == "" && attrs) {
auto i = attrs->find(state->sName);
if (i == attrs->end()) throw TypeError("derivation name missing");
- name = state->forceStringNoCtx(*i->value);
+ name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
return name;
}
@@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
{
if (system == "" && attrs) {
auto i = attrs->find(state->sSystem);
- system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos);
+ system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
}
return system;
}
@@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
if (i == attrs->end())
drvPath = {std::nullopt};
else
- drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)};
+ drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
}
return drvPath.value_or(std::nullopt);
}
@@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
Bindings::iterator i = attrs->find(state->sOutPath);
PathSet context;
if (i != attrs->end())
- outPath = state->coerceToStorePath(*i->pos, *i->value, context);
+ outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
}
if (!outPath)
throw UnimplementedError("CA derivations are not yet supported");
@@ -109,46 +109,59 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Get the ‘outputs’ list. */
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
- state->forceList(*i->value, *i->pos);
+ state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
/* For each output... */
for (auto elem : i->value->listItems()) {
- std::string output(state->forceStringNoCtx(*elem, *i->pos));
+ std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
if (withPaths) {
/* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error?
- state->forceAttrs(*out->value, *i->pos);
+ state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* And evaluate its ‘outPath’ attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context;
- outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
+ outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else
outputs.emplace(output, std::nullopt);
}
} else
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
}
+
if (!onlyOutputsToInstall || !attrs)
return outputs;
- /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
- const Value * outTI = queryMeta("outputsToInstall");
- if (!outTI) return outputs;
- const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
- /* ^ this shows during `nix-env -i` right under the bad derivation */
- if (!outTI->isList()) throw errMsg;
- Outputs result;
- for (auto elem : outTI->listItems()) {
- if (elem->type() != nString) throw errMsg;
- auto out = outputs.find(elem->string.s);
- if (out == outputs.end()) throw errMsg;
+ Bindings::iterator i;
+ if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
+ Outputs result;
+ auto out = outputs.find(queryOutputName());
+ if (out == outputs.end())
+ throw Error("derivation does not have output '%s'", queryOutputName());
result.insert(*out);
+ return result;
+ }
+
+ else {
+ /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
+ const Value * outTI = queryMeta("outputsToInstall");
+ if (!outTI) return outputs;
+ auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
+ /* ^ this shows during `nix-env -i` right under the bad derivation */
+ if (!outTI->isList()) throw errMsg;
+ Outputs result;
+ for (auto elem : outTI->listItems()) {
+ if (elem->type() != nString) throw errMsg;
+ auto out = outputs.find(elem->string.s);
+ if (out == outputs.end()) throw errMsg;
+ result.insert(*out);
+ }
+ return result;
}
- return result;
}
@@ -156,7 +169,7 @@ std::string DrvInfo::queryOutputName() const
{
if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->sOutputName);
- outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : "";
+ outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
}
return outputName;
}
@@ -168,7 +181,7 @@ Bindings * DrvInfo::getMeta()
if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0;
- state->forceAttrs(*a->value, *a->pos);
+ state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
return meta;
}
@@ -179,7 +192,7 @@ StringSet DrvInfo::queryMetaNames()
StringSet res;
if (!getMeta()) return res;
for (auto & i : *meta)
- res.insert(i.name);
+ res.emplace(state->symbols[i.name]);
return res;
}
@@ -269,7 +282,7 @@ void DrvInfo::setMeta(const std::string & name, Value * v)
{
getMeta();
auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0));
- Symbol sym = state->symbols.create(name);
+ auto sym = state->symbols.create(name);
if (meta)
for (auto i : *meta)
if (i.name != sym)
@@ -356,11 +369,11 @@ static void getDerivations(EvalState & state, Value & vIn,
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
- for (auto & i : v.attrs->lexicographicOrder()) {
- debug("evaluating attribute '%1%'", i->name);
- if (!std::regex_match(std::string(i->name), attrRegex))
+ for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
+ debug("evaluating attribute '%1%'", state.symbols[i->name]);
+ if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
continue;
- std::string pathPrefix2 = addToPath(pathPrefix, i->name);
+ std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
if (combineChannels)
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
@@ -369,7 +382,7 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
- if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos))
+ if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
}
}
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 7cc1abef2..bbd2d3c47 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -73,7 +73,7 @@ public:
#if HAVE_BOEHMGC
-typedef std::list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
+typedef std::list<DrvInfo, traceable_allocator<DrvInfo>> DrvInfos;
#else
typedef std::list<DrvInfo> DrvInfos;
#endif
diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l
index d574121b0..462b3b602 100644
--- a/src/libexpr/lexer.l
+++ b/src/libexpr/lexer.l
@@ -28,9 +28,9 @@ using namespace nix;
namespace nix {
-static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
+static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
{
- return Pos(data->origin, data->file, loc.first_line, loc.first_column);
+ return data->state.positions.add(data->origin, loc.first_line, loc.first_column);
}
#define CUR_POS makeCurPos(*yylloc, data)
@@ -155,7 +155,7 @@ or { return OR_KW; }
} catch (const boost::bad_lexical_cast &) {
throw ParseError({
.msg = hintfmt("invalid integer '%1%'", yytext),
- .errPos = CUR_POS,
+ .errPos = data->state.positions[CUR_POS],
});
}
return INT;
@@ -165,7 +165,7 @@ or { return OR_KW; }
if (errno != 0)
throw ParseError({
.msg = hintfmt("invalid float '%1%'", yytext),
- .errPos = CUR_POS,
+ .errPos = data->state.positions[CUR_POS],
});
return FLOAT;
}
@@ -198,7 +198,7 @@ or { return OR_KW; }
(...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered.
This is technically invalid, but we leave the problem to the
parser who fails with exact location. */
- return STR;
+ return EOF;
}
\'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
@@ -294,7 +294,7 @@ or { return OR_KW; }
<INPATH_SLASH><<EOF>> {
throw ParseError({
.msg = hintfmt("path has a trailing slash"),
- .errPos = CUR_POS,
+ .errPos = data->state.positions[CUR_POS],
});
}
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index a2def65a6..eb6f062b4 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -1,21 +1,67 @@
#include "nixexpr.hh"
#include "derivations.hh"
+#include "eval.hh"
+#include "symbol-table.hh"
#include "util.hh"
#include <cstdlib>
-
namespace nix {
+struct PosAdapter : AbstractPos
+{
+ Pos::Origin origin;
-/* Displaying abstract syntax trees. */
+ PosAdapter(Pos::Origin origin)
+ : origin(std::move(origin))
+ {
+ }
-std::ostream & operator << (std::ostream & str, const Expr & e)
+ std::optional<std::string> getSource() const override
+ {
+ return std::visit(overloaded {
+ [](const Pos::none_tag &) -> std::optional<std::string> {
+ return std::nullopt;
+ },
+ [](const Pos::Stdin & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Pos::String & s) -> std::optional<std::string> {
+ // Get rid of the null terminators added by the parser.
+ return std::string(s.source->c_str());
+ },
+ [](const Path & path) -> std::optional<std::string> {
+ try {
+ return readFile(path);
+ } catch (Error &) {
+ return std::nullopt;
+ }
+ }
+ }, origin);
+ }
+
+ void print(std::ostream & out) const override
+ {
+ std::visit(overloaded {
+ [&](const Pos::none_tag &) { out << "«none»"; },
+ [&](const Pos::Stdin &) { out << "«stdin»"; },
+ [&](const Pos::String & s) { out << "«string»"; },
+ [&](const Path & path) { out << path; }
+ }, origin);
+ }
+};
+
+Pos::operator std::shared_ptr<AbstractPos>() const
{
- e.show(str);
- return str;
+ auto pos = std::make_shared<PosAdapter>(origin);
+ pos->line = line;
+ pos->column = column;
+ return pos;
}
+/* Displaying abstract syntax trees. */
+
static void showString(std::ostream & str, std::string_view s)
{
str << '"';
@@ -28,8 +74,10 @@ static void showString(std::ostream & str, std::string_view s)
str << '"';
}
-static void showId(std::ostream & str, std::string_view s)
+std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
{
+ std::string_view s = symbol;
+
if (s.empty())
str << "\"\"";
else if (s == "if") // FIXME: handle other keywords
@@ -38,7 +86,7 @@ static void showId(std::ostream & str, std::string_view s)
char c = s[0];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
showString(str, s);
- return;
+ return str;
}
for (auto c : s)
if (!((c >= 'a' && c <= 'z') ||
@@ -46,89 +94,104 @@ static void showId(std::ostream & str, std::string_view s)
(c >= '0' && c <= '9') ||
c == '_' || c == '\'' || c == '-')) {
showString(str, s);
- return;
+ return str;
}
str << s;
}
-}
-
-std::ostream & operator << (std::ostream & str, const Symbol & sym)
-{
- showId(str, *sym.s);
return str;
}
-void Expr::show(std::ostream & str) const
+void Expr::show(const SymbolTable & symbols, std::ostream & str) const
{
abort();
}
-void ExprInt::show(std::ostream & str) const
+void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
{
str << n;
}
-void ExprFloat::show(std::ostream & str) const
+void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
{
str << nf;
}
-void ExprString::show(std::ostream & str) const
+void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
{
showString(str, s);
}
-void ExprPath::show(std::ostream & str) const
+void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
{
str << s;
}
-void ExprVar::show(std::ostream & str) const
+void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << name;
+ str << symbols[name];
}
-void ExprSelect::show(std::ostream & str) const
+void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "(" << *e << ")." << showAttrPath(attrPath);
- if (def) str << " or (" << *def << ")";
+ str << "(";
+ e->show(symbols, str);
+ str << ")." << showAttrPath(symbols, attrPath);
+ if (def) {
+ str << " or (";
+ def->show(symbols, str);
+ str << ")";
+ }
}
-void ExprOpHasAttr::show(std::ostream & str) const
+void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")";
+ str << "((";
+ e->show(symbols, str);
+ str << ") ? " << showAttrPath(symbols, attrPath) << ")";
}
-void ExprAttrs::show(std::ostream & str) const
+void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const
{
if (recursive) str << "rec ";
str << "{ ";
typedef const decltype(attrs)::value_type * Attr;
std::vector<Attr> sorted;
for (auto & i : attrs) sorted.push_back(&i);
- std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) {
- return (const std::string &) a->first < (const std::string &) b->first;
- });
+ std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
+ std::string_view sa = symbols[a->first], sb = symbols[b->first];
+ return sa < sb;
+ });
for (auto & i : sorted) {
if (i->second.inherited)
- str << "inherit " << i->first << " " << "; ";
- else
- str << i->first << " = " << *i->second.e << "; ";
+ str << "inherit " << symbols[i->first] << " " << "; ";
+ else {
+ str << symbols[i->first] << " = ";
+ i->second.e->show(symbols, str);
+ str << "; ";
+ }
+ }
+ for (auto & i : dynamicAttrs) {
+ str << "\"${";
+ i.nameExpr->show(symbols, str);
+ str << "}\" = ";
+ i.valueExpr->show(symbols, str);
+ str << "; ";
}
- for (auto & i : dynamicAttrs)
- str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
str << "}";
}
-void ExprList::show(std::ostream & str) const
+void ExprList::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "[ ";
- for (auto & i : elems)
- str << "(" << *i << ") ";
+ for (auto & i : elems) {
+ str << "(";
+ i->show(symbols, str);
+ str << ") ";
+ }
str << "]";
}
-void ExprLambda::show(std::ostream & str) const
+void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "(";
if (hasFormals()) {
@@ -136,74 +199,100 @@ void ExprLambda::show(std::ostream & str) const
bool first = true;
for (auto & i : formals->formals) {
if (first) first = false; else str << ", ";
- str << i.name;
- if (i.def) str << " ? " << *i.def;
+ str << symbols[i.name];
+ if (i.def) {
+ str << " ? ";
+ i.def->show(symbols, str);
+ }
}
if (formals->ellipsis) {
if (!first) str << ", ";
str << "...";
}
str << " }";
- if (!arg.empty()) str << " @ ";
+ if (arg) str << " @ ";
}
- if (!arg.empty()) str << arg;
- str << ": " << *body << ")";
+ if (arg) str << symbols[arg];
+ str << ": ";
+ body->show(symbols, str);
+ str << ")";
}
-void ExprCall::show(std::ostream & str) const
+void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << '(' << *fun;
+ str << '(';
+ fun->show(symbols, str);
for (auto e : args) {
str << ' ';
- str << *e;
+ e->show(symbols, str);
}
str << ')';
}
-void ExprLet::show(std::ostream & str) const
+void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "(let ";
for (auto & i : attrs->attrs)
if (i.second.inherited) {
- str << "inherit " << i.first << "; ";
+ str << "inherit " << symbols[i.first] << "; ";
}
- else
- str << i.first << " = " << *i.second.e << "; ";
- str << "in " << *body << ")";
+ else {
+ str << symbols[i.first] << " = ";
+ i.second.e->show(symbols, str);
+ str << "; ";
+ }
+ str << "in ";
+ body->show(symbols, str);
+ str << ")";
}
-void ExprWith::show(std::ostream & str) const
+void ExprWith::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "(with " << *attrs << "; " << *body << ")";
+ str << "(with ";
+ attrs->show(symbols, str);
+ str << "; ";
+ body->show(symbols, str);
+ str << ")";
}
-void ExprIf::show(std::ostream & str) const
+void ExprIf::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "(if " << *cond << " then " << *then << " else " << *else_ << ")";
+ str << "(if ";
+ cond->show(symbols, str);
+ str << " then ";
+ then->show(symbols, str);
+ str << " else ";
+ else_->show(symbols, str);
+ str << ")";
}
-void ExprAssert::show(std::ostream & str) const
+void ExprAssert::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "assert " << *cond << "; " << *body;
+ str << "assert ";
+ cond->show(symbols, str);
+ str << "; ";
+ body->show(symbols, str);
}
-void ExprOpNot::show(std::ostream & str) const
+void ExprOpNot::show(const SymbolTable & symbols, std::ostream & str) const
{
- str << "(! " << *e << ")";
+ str << "(! ";
+ e->show(symbols, str);
+ str << ")";
}
-void ExprConcatStrings::show(std::ostream & str) const
+void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) const
{
bool first = true;
str << "(";
for (auto & i : *es) {
if (first) first = false; else str << " + ";
- str << *i.second;
+ i.second->show(symbols, str);
}
str << ")";
}
-void ExprPos::show(std::ostream & str) const
+void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "__curPos";
}
@@ -211,78 +300,75 @@ void ExprPos::show(std::ostream & str) const
std::ostream & operator << (std::ostream & str, const Pos & pos)
{
- if (!pos)
+ if (auto pos2 = (std::shared_ptr<AbstractPos>) pos) {
+ str << *pos2;
+ } else
str << "undefined position";
- else
- {
- auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%");
- switch (pos.origin) {
- case foFile:
- f % (const std::string &) pos.file;
- break;
- case foStdin:
- case foString:
- f % "(string)";
- break;
- default:
- throw Error("unhandled Pos origin!");
- }
- str << (f % pos.line % pos.column).str();
- }
return str;
}
-std::string showAttrPath(const AttrPath & attrPath)
+std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
{
std::ostringstream out;
bool first = true;
for (auto & i : attrPath) {
if (!first) out << '.'; else first = false;
- if (i.symbol.set())
- out << i.symbol;
- else
- out << "\"${" << *i.expr << "}\"";
+ if (i.symbol)
+ out << symbols[i.symbol];
+ else {
+ out << "\"${";
+ i.expr->show(symbols, out);
+ out << "}\"";
+ }
}
return out.str();
}
-Pos noPos;
-
-
/* Computing levels/displacements for variables. */
-void Expr::bindVars(const StaticEnv & env)
+void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
abort();
}
-void ExprInt::bindVars(const StaticEnv & env)
+void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
}
-void ExprFloat::bindVars(const StaticEnv & env)
+void ExprFloat::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
}
-void ExprString::bindVars(const StaticEnv & env)
+void ExprString::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
}
-void ExprPath::bindVars(const StaticEnv & env)
+void ExprPath::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
}
-void ExprVar::bindVars(const StaticEnv & env)
+void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
/* Check whether the variable appears in the environment. If so,
set its level and displacement. */
const StaticEnv * curEnv;
Level level;
int withLevel = -1;
- for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) {
+ for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) {
if (curEnv->isWith) {
if (withLevel == -1) withLevel = level;
} else {
@@ -301,176 +387,222 @@ void ExprVar::bindVars(const StaticEnv & env)
"undefined variable" error now. */
if (withLevel == -1)
throw UndefinedVarError({
- .msg = hintfmt("undefined variable '%1%'", name),
- .errPos = pos
+ .msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
+ .errPos = es.positions[pos]
});
fromWith = true;
this->level = withLevel;
}
-void ExprSelect::bindVars(const StaticEnv & env)
+void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- e->bindVars(env);
- if (def) def->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ e->bindVars(es, env);
+ if (def) def->bindVars(es, env);
for (auto & i : attrPath)
- if (!i.symbol.set())
- i.expr->bindVars(env);
+ if (!i.symbol)
+ i.expr->bindVars(es, env);
}
-void ExprOpHasAttr::bindVars(const StaticEnv & env)
+void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- e->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ e->bindVars(es, env);
for (auto & i : attrPath)
- if (!i.symbol.set())
- i.expr->bindVars(env);
+ if (!i.symbol)
+ i.expr->bindVars(es, env);
}
-void ExprAttrs::bindVars(const StaticEnv & env)
+void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- const StaticEnv * dynamicEnv = &env;
- StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
if (recursive) {
- dynamicEnv = &newEnv;
+ auto newEnv = std::make_shared<StaticEnv>(false, env.get(), recursive ? attrs.size() : 0);
Displacement displ = 0;
for (auto & i : attrs)
- newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
+ newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs is in sorted order.
for (auto & i : attrs)
- i.second.e->bindVars(i.second.inherited ? env : newEnv);
- }
+ i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
- else
+ for (auto & i : dynamicAttrs) {
+ i.nameExpr->bindVars(es, newEnv);
+ i.valueExpr->bindVars(es, newEnv);
+ }
+ }
+ else {
for (auto & i : attrs)
- i.second.e->bindVars(env);
+ i.second.e->bindVars(es, env);
- for (auto & i : dynamicAttrs) {
- i.nameExpr->bindVars(*dynamicEnv);
- i.valueExpr->bindVars(*dynamicEnv);
+ for (auto & i : dynamicAttrs) {
+ i.nameExpr->bindVars(es, env);
+ i.valueExpr->bindVars(es, env);
+ }
}
}
-void ExprList::bindVars(const StaticEnv & env)
+void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
for (auto & i : elems)
- i->bindVars(env);
+ i->bindVars(es, env);
}
-void ExprLambda::bindVars(const StaticEnv & env)
+void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- StaticEnv newEnv(
- false, &env,
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ auto newEnv = std::make_shared<StaticEnv>(
+ false, env.get(),
(hasFormals() ? formals->formals.size() : 0) +
- (arg.empty() ? 0 : 1));
+ (!arg ? 0 : 1));
Displacement displ = 0;
- if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++);
+ if (arg) newEnv->vars.emplace_back(arg, displ++);
if (hasFormals()) {
for (auto & i : formals->formals)
- newEnv.vars.emplace_back(i.name, displ++);
+ newEnv->vars.emplace_back(i.name, displ++);
- newEnv.sort();
+ newEnv->sort();
for (auto & i : formals->formals)
- if (i.def) i.def->bindVars(newEnv);
+ if (i.def) i.def->bindVars(es, newEnv);
}
- body->bindVars(newEnv);
+ body->bindVars(es, newEnv);
}
-void ExprCall::bindVars(const StaticEnv & env)
+void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- fun->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ fun->bindVars(es, env);
for (auto e : args)
- e->bindVars(env);
+ e->bindVars(es, env);
}
-void ExprLet::bindVars(const StaticEnv & env)
+void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- StaticEnv newEnv(false, &env, attrs->attrs.size());
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ auto newEnv = std::make_shared<StaticEnv>(false, env.get(), attrs->attrs.size());
Displacement displ = 0;
for (auto & i : attrs->attrs)
- newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
+ newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs->attrs is in sorted order.
for (auto & i : attrs->attrs)
- i.second.e->bindVars(i.second.inherited ? env : newEnv);
+ i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
- body->bindVars(newEnv);
+ body->bindVars(es, newEnv);
}
-void ExprWith::bindVars(const StaticEnv & env)
+void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
/* Does this `with' have an enclosing `with'? If so, record its
level so that `lookupVar' can look up variables in the previous
`with' if this one doesn't contain the desired attribute. */
const StaticEnv * curEnv;
Level level;
prevWith = 0;
- for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++)
+ for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++)
if (curEnv->isWith) {
prevWith = level;
break;
}
- attrs->bindVars(env);
- StaticEnv newEnv(true, &env);
- body->bindVars(newEnv);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ attrs->bindVars(es, env);
+ auto newEnv = std::make_shared<StaticEnv>(true, env.get());
+ body->bindVars(es, newEnv);
}
-void ExprIf::bindVars(const StaticEnv & env)
+void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- cond->bindVars(env);
- then->bindVars(env);
- else_->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ cond->bindVars(es, env);
+ then->bindVars(es, env);
+ else_->bindVars(es, env);
}
-void ExprAssert::bindVars(const StaticEnv & env)
+void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- cond->bindVars(env);
- body->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ cond->bindVars(es, env);
+ body->bindVars(es, env);
}
-void ExprOpNot::bindVars(const StaticEnv & env)
+void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- e->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ e->bindVars(es, env);
}
-void ExprConcatStrings::bindVars(const StaticEnv & env)
+void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
- for (auto & i : *es)
- i.second->bindVars(env);
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
+
+ for (auto & i : *this->es)
+ i.second->bindVars(es, env);
}
-void ExprPos::bindVars(const StaticEnv & env)
+void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
+ if (es.debugRepl)
+ es.exprEnvs.insert(std::make_pair(this, env));
}
/* Storing function names. */
-void Expr::setName(Symbol & name)
+void Expr::setName(Symbol name)
{
}
-void ExprLambda::setName(Symbol & name)
+void ExprLambda::setName(Symbol name)
{
this->name = name;
body->setName(name);
}
-std::string ExprLambda::showNamePos() const
+std::string ExprLambda::showNamePos(const EvalState & state) const
{
- return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos);
+ std::string id(name
+ ? concatStrings("'", state.symbols[name], "'")
+ : "anonymous function");
+ return fmt("%1% at %2%", id, state.positions[pos]);
}
@@ -480,8 +612,7 @@ std::string ExprLambda::showNamePos() const
size_t SymbolTable::totalSize() const
{
size_t n = 0;
- for (auto & i : store)
- n += i.size();
+ dump([&] (const std::string & s) { n += s.size(); });
return n;
}
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 4dbe31510..ffe67f97d 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -1,9 +1,12 @@
#pragma once
+#include <map>
+#include <vector>
+
#include "value.hh"
#include "symbol-table.hh"
#include "error.hh"
-
+#include "chunked-vector.hh"
namespace nix {
@@ -18,37 +21,107 @@ MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
-
/* Position objects. */
-
struct Pos
{
- Symbol file;
uint32_t line;
- FileOrigin origin:2;
- uint32_t column:30;
- Pos() : line(0), origin(foString), column(0) { };
- Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column)
- : file(file), line(line), origin(origin), column(column) { };
- operator bool() const
+ uint32_t column;
+
+ struct none_tag { };
+ struct Stdin { ref<std::string> source; };
+ struct String { ref<std::string> source; };
+
+ typedef std::variant<none_tag, Stdin, String, Path> Origin;
+
+ Origin origin;
+
+ explicit operator bool() const { return line > 0; }
+
+ operator std::shared_ptr<AbstractPos>() const;
+};
+
+class PosIdx {
+ friend class PosTable;
+
+private:
+ uint32_t id;
+
+ explicit PosIdx(uint32_t id): id(id) {}
+
+public:
+ PosIdx() : id(0) {}
+
+ explicit operator bool() const { return id > 0; }
+
+ bool operator <(const PosIdx other) const { return id < other.id; }
+
+ bool operator ==(const PosIdx other) const { return id == other.id; }
+
+ bool operator !=(const PosIdx other) const { return id != other.id; }
+};
+
+class PosTable
+{
+public:
+ class Origin {
+ friend PosTable;
+ private:
+ // must always be invalid by default, add() replaces this with the actual value.
+ // subsequent add() calls use this index as a token to quickly check whether the
+ // current origins.back() can be reused or not.
+ mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
+
+ // Used for searching in PosTable::[].
+ explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {}
+
+ public:
+ const Pos::Origin origin;
+
+ Origin(Pos::Origin origin): origin(origin) {}
+ };
+
+ struct Offset {
+ uint32_t line, column;
+ };
+
+private:
+ std::vector<Origin> origins;
+ ChunkedVector<Offset, 8192> offsets;
+
+public:
+ PosTable(): offsets(1024)
+ {
+ origins.reserve(1024);
+ }
+
+ PosIdx add(const Origin & origin, uint32_t line, uint32_t column)
{
- return line != 0;
+ const auto idx = offsets.add({line, column}).second;
+ if (origins.empty() || origins.back().idx != origin.idx) {
+ origin.idx = idx;
+ origins.push_back(origin);
+ }
+ return PosIdx(idx + 1);
}
- bool operator < (const Pos & p2) const
+ Pos operator[](PosIdx p) const
{
- if (!line) return p2.line;
- if (!p2.line) return false;
- int d = ((const std::string &) file).compare((const std::string &) p2.file);
- if (d < 0) return true;
- if (d > 0) return false;
- if (line < p2.line) return true;
- if (line > p2.line) return false;
- return column < p2.column;
+ if (p.id == 0 || p.id > offsets.size())
+ return {};
+ const auto idx = p.id - 1;
+ /* we want the last key <= idx, so we'll take prev(first key > idx).
+ this is guaranteed to never rewind origin.begin because the first
+ key is always 0. */
+ const auto pastOrigin = std::upper_bound(
+ origins.begin(), origins.end(), Origin(idx),
+ [] (const auto & a, const auto & b) { return a.idx < b.idx; });
+ const auto origin = *std::prev(pastOrigin);
+ const auto offset = offsets[idx];
+ return {offset.line, offset.column, origin.origin};
}
};
-extern Pos noPos;
+inline PosIdx noPos = {};
std::ostream & operator << (std::ostream & str, const Pos & pos);
@@ -64,13 +137,13 @@ struct AttrName
{
Symbol symbol;
Expr * expr;
- AttrName(const Symbol & s) : symbol(s) {};
+ AttrName(Symbol s) : symbol(s) {};
AttrName(Expr * e) : expr(e) {};
};
typedef std::vector<AttrName> AttrPath;
-std::string showAttrPath(const AttrPath & attrPath);
+std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
/* Abstract syntax of Nix expressions. */
@@ -78,27 +151,26 @@ std::string showAttrPath(const AttrPath & attrPath);
struct Expr
{
virtual ~Expr() { };
- virtual void show(std::ostream & str) const;
- virtual void bindVars(const StaticEnv & env);
+ virtual void show(const SymbolTable & symbols, std::ostream & str) const;
+ virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
- virtual void setName(Symbol & name);
+ virtual void setName(Symbol name);
+ virtual PosIdx getPos() const { return noPos; }
};
-std::ostream & operator << (std::ostream & str, const Expr & e);
-
#define COMMON_METHODS \
- void show(std::ostream & str) const; \
- void eval(EvalState & state, Env & env, Value & v); \
- void bindVars(const StaticEnv & env);
+ void show(const SymbolTable & symbols, std::ostream & str) const override; \
+ void eval(EvalState & state, Env & env, Value & v) override; \
+ void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
struct ExprInt : Expr
{
NixInt n;
Value v;
ExprInt(NixInt n) : n(n) { v.mkInt(n); };
+ Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
- Value * maybeThunk(EvalState & state, Env & env);
};
struct ExprFloat : Expr
@@ -106,8 +178,8 @@ struct ExprFloat : Expr
NixFloat nf;
Value v;
ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); };
+ Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
- Value * maybeThunk(EvalState & state, Env & env);
};
struct ExprString : Expr
@@ -115,8 +187,8 @@ struct ExprString : Expr
std::string s;
Value v;
ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
+ Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
- Value * maybeThunk(EvalState & state, Env & env);
};
struct ExprPath : Expr
@@ -124,8 +196,8 @@ struct ExprPath : Expr
std::string s;
Value v;
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
+ Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
- Value * maybeThunk(EvalState & state, Env & env);
};
typedef uint32_t Level;
@@ -133,7 +205,7 @@ typedef uint32_t Displacement;
struct ExprVar : Expr
{
- Pos pos;
+ PosIdx pos;
Symbol name;
/* Whether the variable comes from an environment (e.g. a rec, let
@@ -149,19 +221,21 @@ struct ExprVar : Expr
Level level;
Displacement displ;
- ExprVar(const Symbol & name) : name(name) { };
- ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
+ ExprVar(Symbol name) : name(name) { };
+ ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
+ Value * maybeThunk(EvalState & state, Env & env) override;
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
- Value * maybeThunk(EvalState & state, Env & env);
};
struct ExprSelect : Expr
{
- Pos pos;
+ PosIdx pos;
Expr * e, * def;
AttrPath attrPath;
- ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
- ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
+ ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
+ ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -170,19 +244,20 @@ struct ExprOpHasAttr : Expr
Expr * e;
AttrPath attrPath;
ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { };
+ PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};
struct ExprAttrs : Expr
{
bool recursive;
- Pos pos;
+ PosIdx pos;
struct AttrDef {
bool inherited;
Expr * e;
- Pos pos;
+ PosIdx pos;
Displacement displ; // displacement
- AttrDef(Expr * e, const Pos & pos, bool inherited=false)
+ AttrDef(Expr * e, const PosIdx & pos, bool inherited=false)
: inherited(inherited), e(e), pos(pos) { };
AttrDef() { };
};
@@ -190,14 +265,15 @@ struct ExprAttrs : Expr
AttrDefs attrs;
struct DynamicAttrDef {
Expr * nameExpr, * valueExpr;
- Pos pos;
- DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos)
+ PosIdx pos;
+ DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos)
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
};
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs;
- ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { };
- ExprAttrs() : recursive(false), pos(noPos) { };
+ ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
+ ExprAttrs() : recursive(false) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -206,14 +282,18 @@ struct ExprList : Expr
std::vector<Expr *> elems;
ExprList() { };
COMMON_METHODS
+
+ PosIdx getPos() const override
+ {
+ return elems.empty() ? noPos : elems.front()->getPos();
+ }
};
struct Formal
{
- Pos pos;
+ PosIdx pos;
Symbol name;
Expr * def;
- Formal(const Pos & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { };
};
struct Formals
@@ -222,18 +302,20 @@ struct Formals
Formals_ formals;
bool ellipsis;
- bool has(Symbol arg) const {
+ bool has(Symbol arg) const
+ {
auto it = std::lower_bound(formals.begin(), formals.end(), arg,
[] (const Formal & f, const Symbol & sym) { return f.name < sym; });
return it != formals.end() && it->name == arg;
}
- std::vector<Formal> lexicographicOrder() const
+ std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<Formal> result(formals.begin(), formals.end());
std::sort(result.begin(), result.end(),
- [] (const Formal & a, const Formal & b) {
- return std::string_view(a.name) < std::string_view(b.name);
+ [&] (const Formal & a, const Formal & b) {
+ std::string_view sa = symbols[a.name], sb = symbols[b.name];
+ return sa < sb;
});
return result;
}
@@ -241,18 +323,23 @@ struct Formals
struct ExprLambda : Expr
{
- Pos pos;
+ PosIdx pos;
Symbol name;
Symbol arg;
Formals * formals;
Expr * body;
- ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body)
+ ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
: pos(pos), arg(arg), formals(formals), body(body)
{
};
- void setName(Symbol & name);
- std::string showNamePos() const;
+ ExprLambda(PosIdx pos, Formals * formals, Expr * body)
+ : pos(pos), formals(formals), body(body)
+ {
+ }
+ void setName(Symbol name) override;
+ std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; }
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -260,10 +347,11 @@ struct ExprCall : Expr
{
Expr * fun;
std::vector<Expr *> args;
- Pos pos;
- ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args)
+ PosIdx pos;
+ ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos)
{ }
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -277,26 +365,29 @@ struct ExprLet : Expr
struct ExprWith : Expr
{
- Pos pos;
+ PosIdx pos;
Expr * attrs, * body;
size_t prevWith;
- ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
+ ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprIf : Expr
{
- Pos pos;
+ PosIdx pos;
Expr * cond, * then, * else_;
- ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
+ ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprAssert : Expr
{
- Pos pos;
+ PosIdx pos;
Expr * cond, * body;
- ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
+ ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -310,19 +401,20 @@ struct ExprOpNot : Expr
#define MakeBinOp(name, s) \
struct name : Expr \
{ \
- Pos pos; \
+ PosIdx pos; \
Expr * e1, * e2; \
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
- name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
- void show(std::ostream & str) const \
+ name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
+ void show(const SymbolTable & symbols, std::ostream & str) const override \
{ \
- str << "(" << *e1 << " " s " " << *e2 << ")"; \
+ str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \
} \
- void bindVars(const StaticEnv & env) \
+ void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
{ \
- e1->bindVars(env); e2->bindVars(env); \
+ e1->bindVars(es, env); e2->bindVars(es, env); \
} \
- void eval(EvalState & state, Env & env, Value & v); \
+ void eval(EvalState & state, Env & env, Value & v) override; \
+ PosIdx getPos() const override { return pos; } \
};
MakeBinOp(ExprOpEq, "==")
@@ -335,18 +427,20 @@ MakeBinOp(ExprOpConcatLists, "++")
struct ExprConcatStrings : Expr
{
- Pos pos;
+ PosIdx pos;
bool forceString;
- std::vector<std::pair<Pos, Expr *> > * es;
- ExprConcatStrings(const Pos & pos, bool forceString, std::vector<std::pair<Pos, Expr *> > * es)
+ std::vector<std::pair<PosIdx, Expr *>> * es;
+ ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es)
: pos(pos), forceString(forceString), es(es) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
struct ExprPos : Expr
{
- Pos pos;
- ExprPos(const Pos & pos) : pos(pos) { };
+ PosIdx pos;
+ ExprPos(const PosIdx & pos) : pos(pos) { };
+ PosIdx getPos() const override { return pos; }
COMMON_METHODS
};
@@ -384,7 +478,7 @@ struct StaticEnv
vars.erase(it, end);
}
- Vars::const_iterator find(const Symbol & name) const
+ Vars::const_iterator find(Symbol name) const
{
Vars::value_type key(name, 0);
auto i = std::lower_bound(vars.begin(), vars.end(), key);
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 919b9cfae..ffb364a90 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -32,13 +32,8 @@ namespace nix {
SymbolTable & symbols;
Expr * result;
Path basePath;
- Symbol file;
- FileOrigin origin;
+ PosTable::Origin origin;
std::optional<ErrorInfo> error;
- ParseData(EvalState & state)
- : state(state)
- , symbols(state.symbols)
- { };
};
struct ParserFormals {
@@ -77,26 +72,26 @@ using namespace nix;
namespace nix {
-static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
+static void dupAttr(const EvalState & state, const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%",
- showAttrPath(attrPath), prevPos),
- .errPos = pos
+ showAttrPath(state.symbols, attrPath), state.positions[prevPos]),
+ .errPos = state.positions[pos]
});
}
-static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
+static void dupAttr(const EvalState & state, Symbol attr, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
- .msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos),
- .errPos = pos
+ .msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]),
+ .errPos = state.positions[pos]
});
}
static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
- Expr * e, const Pos & pos)
+ Expr * e, const PosIdx pos, const nix::EvalState & state)
{
AttrPath::iterator i;
// All attrpaths have at least one attr
@@ -104,15 +99,15 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
// Checking attrPath validity.
// ===========================
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
- if (i->symbol.set()) {
+ if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
if (!j->second.inherited) {
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
- if (!attrs2) dupAttr(attrPath, pos, j->second.pos);
+ if (!attrs2) dupAttr(state, attrPath, pos, j->second.pos);
attrs = attrs2;
} else
- dupAttr(attrPath, pos, j->second.pos);
+ dupAttr(state, attrPath, pos, j->second.pos);
} else {
ExprAttrs * nested = new ExprAttrs;
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
@@ -126,7 +121,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
}
// Expr insertion.
// ==========================
- if (i->symbol.set()) {
+ if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
// This attr path is already defined. However, if both
@@ -139,11 +134,11 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
for (auto & ad : ae->attrs) {
auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
- dupAttr(ad.first, j2->second.pos, ad.second.pos);
+ dupAttr(state, ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs.emplace(ad.first, ad.second);
}
} else {
- dupAttr(attrPath, pos, j->second.pos);
+ dupAttr(state, attrPath, pos, j->second.pos);
}
} else {
// This attr path is not defined. Let's create it.
@@ -157,14 +152,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
static Formals * toFormals(ParseData & data, ParserFormals * formals,
- Pos pos = noPos, Symbol arg = {})
+ PosIdx pos = noPos, Symbol arg = {})
{
std::sort(formals->formals.begin(), formals->formals.end(),
[] (const auto & a, const auto & b) {
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos);
});
- std::optional<std::pair<Symbol, Pos>> duplicate;
+ std::optional<std::pair<Symbol, PosIdx>> duplicate;
for (size_t i = 0; i + 1 < formals->formals.size(); i++) {
if (formals->formals[i].name != formals->formals[i + 1].name)
continue;
@@ -173,18 +168,18 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
}
if (duplicate)
throw ParseError({
- .msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first),
- .errPos = duplicate->second
+ .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]),
+ .errPos = data.state.positions[duplicate->second]
});
Formals result;
result.ellipsis = formals->ellipsis;
result.formals = std::move(formals->formals);
- if (arg.set() && result.has(arg))
+ if (arg && result.has(arg))
throw ParseError({
- .msg = hintfmt("duplicate formal function argument '%1%'", arg),
- .errPos = pos
+ .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]),
+ .errPos = data.state.positions[pos]
});
delete formals;
@@ -192,8 +187,8 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
}
-static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
- std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es)
+static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
+ std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> & es)
{
if (es.empty()) return new ExprString("");
@@ -233,7 +228,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
}
/* Strip spaces from each line. */
- std::vector<std::pair<Pos, Expr *> > * es2 = new std::vector<std::pair<Pos, Expr *> >;
+ auto * es2 = new std::vector<std::pair<PosIdx, Expr *>>;
atStartOfLine = true;
size_t curDropped = 0;
size_t n = es.size();
@@ -284,9 +279,9 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
}
-static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
+static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
{
- return Pos(data->origin, data->file, loc.first_line, loc.first_column);
+ return data->state.positions.add(data->origin, loc.first_line, loc.first_column);
}
#define CUR_POS makeCurPos(*yylocp, data)
@@ -299,7 +294,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
{
data->error = {
.msg = hintfmt(error),
- .errPos = makeCurPos(*loc, data)
+ .errPos = data->state.positions[makeCurPos(*loc, data)]
};
}
@@ -320,8 +315,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
StringToken uri;
StringToken str;
std::vector<nix::AttrName> * attrNames;
- std::vector<std::pair<nix::Pos, nix::Expr *> > * string_parts;
- std::vector<std::pair<nix::Pos, std::variant<nix::Expr *, StringToken> > > * ind_string_parts;
+ std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
+ std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, StringToken>>> * ind_string_parts;
}
%type <e> start expr expr_function expr_if expr_op
@@ -369,15 +364,15 @@ expr_function
: ID ':' expr_function
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); }
| '{' formals '}' ':' expr_function
- { $$ = new ExprLambda(CUR_POS, data->symbols.create(""), toFormals(*data, $2), $5); }
+ { $$ = new ExprLambda(CUR_POS, toFormals(*data, $2), $5); }
| '{' formals '}' '@' ID ':' expr_function
{
- Symbol arg = data->symbols.create($5);
+ auto arg = data->symbols.create($5);
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7);
}
| ID '@' '{' formals '}' ':' expr_function
{
- Symbol arg = data->symbols.create($1);
+ auto arg = data->symbols.create($1);
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7);
}
| ASSERT expr ';' expr_function
@@ -388,7 +383,7 @@ expr_function
{ if (!$2->dynamicAttrs.empty())
throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in let"),
- .errPos = CUR_POS
+ .errPos = data->state.positions[CUR_POS]
});
$$ = new ExprLet($2, $4);
}
@@ -405,21 +400,21 @@ expr_op
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
- | expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
- | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
- | expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
- | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
- | expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); }
- | expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); }
- | expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); }
- | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
+ | expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
+ | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
+ | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
+ | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
+ | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
+ | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
+ | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
+ | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op
- { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
- | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
- | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
- | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
- | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); }
+ { $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
+ | expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
+ | expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
+ | expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); }
+ | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); }
| expr_app
;
@@ -477,7 +472,7 @@ expr_simple
if (noURLLiterals)
throw ParseError({
.msg = hintfmt("URL literals are disabled"),
- .errPos = CUR_POS
+ .errPos = data->state.positions[CUR_POS]
});
$$ = new ExprString(std::string($1));
}
@@ -503,9 +498,9 @@ string_parts_interpolated
: string_parts_interpolated STR
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); }
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
- | DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
+ | DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(makeCurPos(@1, data), $2); }
| STR DOLLAR_CURLY expr '}' {
- $$ = new std::vector<std::pair<Pos, Expr *> >;
+ $$ = new std::vector<std::pair<PosIdx, Expr *>>;
$$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1)));
$$->emplace_back(makeCurPos(@2, data), $3);
}
@@ -520,6 +515,12 @@ path_start
$$ = new ExprPath(path);
}
| HPATH {
+ if (evalSettings.pureEval) {
+ throw Error(
+ "the path '%s' can not be resolved in pure mode",
+ std::string_view($1.p, $1.l)
+ );
+ }
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(path);
}
@@ -528,17 +529,17 @@ path_start
ind_string_parts
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); }
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
- | { $$ = new std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > >; }
+ | { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>>; }
;
binds
- : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); }
+ : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); }
| binds INHERIT attrs ';'
{ $$ = $1;
for (auto & i : *$3) {
if ($$->attrs.find(i.symbol) != $$->attrs.end())
- dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
- Pos pos = makeCurPos(@3, data);
+ dupAttr(data->state, i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
+ auto pos = makeCurPos(@3, data);
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true));
}
}
@@ -547,7 +548,7 @@ binds
/* !!! Should ensure sharing of the expression in $4. */
for (auto & i : *$6) {
if ($$->attrs.find(i.symbol) != $$->attrs.end())
- dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
+ dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
}
}
@@ -565,7 +566,7 @@ attrs
} else
throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in inherit"),
- .errPos = makeCurPos(@2, data)
+ .errPos = data->state.positions[makeCurPos(@2, data)]
});
}
| { $$ = new AttrPath; }
@@ -621,8 +622,8 @@ formals
;
formal
- : ID { $$ = new Formal(CUR_POS, data->symbols.create($1), 0); }
- | ID '?' expr { $$ = new Formal(CUR_POS, data->symbols.create($1), $3); }
+ : ID { $$ = new Formal{CUR_POS, data->symbols.create($1), 0}; }
+ | ID '?' expr { $$ = new Formal{CUR_POS, data->symbols.create($1), $3}; }
;
%%
@@ -637,29 +638,26 @@ formal
#include "filetransfer.hh"
#include "fetchers.hh"
#include "store-api.hh"
+#include "flake/flake.hh"
namespace nix {
-Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
- const PathView path, const PathView basePath, StaticEnv & staticEnv)
+Expr * EvalState::parse(
+ char * text,
+ size_t length,
+ Pos::Origin origin,
+ Path basePath,
+ std::shared_ptr<StaticEnv> & staticEnv)
{
yyscan_t scanner;
- ParseData data(*this);
- data.origin = origin;
- switch (origin) {
- case foFile:
- data.file = data.symbols.create(path);
- break;
- case foStdin:
- case foString:
- data.file = data.symbols.create(text);
- break;
- default:
- assert(false);
- }
- data.basePath = basePath;
+ ParseData data {
+ .state = *this,
+ .symbols = symbols,
+ .basePath = std::move(basePath),
+ .origin = {origin},
+ };
yylex_init(&scanner);
yy_scan_buffer(text, length, scanner);
@@ -668,7 +666,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
if (res) throw ParseError(data.error.value());
- data.result->bindVars(staticEnv);
+ data.result->bindVars(*this, staticEnv);
return data.result;
}
@@ -706,19 +704,20 @@ Expr * EvalState::parseExprFromFile(const Path & path)
}
-Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
+Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
{
auto buffer = readFile(path);
// readFile should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foFile, path, dirOf(path), staticEnv);
+ return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv);
}
-Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv)
+Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{
- s.append("\0\0", 2);
- return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
+ auto s = make_ref<std::string>(std::move(s_));
+ s->append("\0\0", 2);
+ return parse(s->data(), s->size(), Pos::String{.source = s}, basePath, staticEnv);
}
@@ -734,7 +733,8 @@ Expr * EvalState::parseStdin()
auto buffer = drainFD(0);
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
- return parse(buffer.data(), buffer.size(), foStdin, "", absPath("."), staticBaseEnv);
+ auto s = make_ref<std::string>(std::move(buffer));
+ return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv);
}
@@ -760,7 +760,7 @@ Path EvalState::findFile(const std::string_view path)
}
-Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos)
+Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
{
for (auto & i : searchPath) {
std::string suffix;
@@ -782,13 +782,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4));
- throw ThrownError({
+ debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval
? "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)",
path),
- .errPos = pos
- });
+ .errPos = positions[pos]
+ }), 0, 0);
}
@@ -799,17 +799,28 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
std::pair<bool, std::string> res;
- if (isUri(elem.second)) {
+ if (EvalSettings::isPseudoUrl(elem.second)) {
try {
- res = { true, store->toRealPath(fetchers::downloadTarball(
- store, resolveUri(elem.second), "source", false).first.storePath) };
+ auto storePath = fetchers::downloadTarball(
+ store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath;
+ res = { true, store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
});
res = { false, "" };
}
- } else {
+ }
+
+ else if (hasPrefix(elem.second, "flake:")) {
+ settings.requireExperimentalFeature(Xp::Flakes);
+ auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false);
+ debug("fetching flake search path element '%s''", elem.second);
+ auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
+ res = { true, store->toRealPath(storePath) };
+ }
+
+ else {
auto path = absPath(elem.second);
if (pathExists(path))
res = { true, path };
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 2ccc63328..7e4b944b1 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -5,14 +5,15 @@
#include "globals.hh"
#include "json-to-value.hh"
#include "names.hh"
+#include "references.hh"
#include "store-api.hh"
#include "util.hh"
-#include "json.hh"
#include "value-to-json.hh"
#include "value-to-xml.hh"
#include "primops.hh"
#include <boost/container/small_vector.hpp>
+#include <nlohmann/json.hpp>
#include <sys/types.h>
#include <sys/stat.h>
@@ -46,7 +47,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
auto [ctx, outputName] = decodeContext(*store, i);
auto ctxS = store->printStorePath(ctx);
if (!store->isValidPath(ctx))
- throw InvalidPathError(store->printStorePath(ctx));
+ debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
if (!outputName.empty() && ctx.isDerivation()) {
drvs.push_back({ctx, {outputName}});
} else {
@@ -57,9 +58,9 @@ StringMap EvalState::realiseContext(const PathSet & context)
if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation)
- throw Error(
+ debugThrowLastTrace(Error(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
- store->printStorePath(drvs.begin()->drvPath));
+ store->printStorePath(drvs.begin()->drvPath)));
/* Build/substitute the context. */
std::vector<DerivedPath> buildReqs;
@@ -68,14 +69,15 @@ StringMap EvalState::realiseContext(const PathSet & context)
/* Get all the output paths corresponding to the placeholders we had */
for (auto & [drvPath, outputs] : drvs) {
- auto outputPaths = store->queryDerivationOutputMap(drvPath);
+ const auto outputPaths = store->queryDerivationOutputMap(drvPath);
for (auto & outputName : outputs) {
- if (outputPaths.count(outputName) == 0)
- throw Error("derivation '%s' does not have an output named '%s'",
- store->printStorePath(drvPath), outputName);
+ auto outputPath = get(outputPaths, outputName);
+ if (!outputPath)
+ debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
+ store->printStorePath(drvPath), outputName));
res.insert_or_assign(
downstreamPlaceholder(*store, drvPath, outputName),
- store->printStorePath(outputPaths.at(outputName))
+ store->printStorePath(*outputPath)
);
}
}
@@ -96,19 +98,11 @@ struct RealisePathFlags {
bool checkForPureEval = true;
};
-static Path realisePath(EvalState & state, const Pos & pos, Value & v, const RealisePathFlags flags = {})
+static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
{
PathSet context;
- auto path = [&]()
- {
- try {
- return state.coerceToPath(pos, v, context);
- } catch (Error & e) {
- e.addTrace(pos, "while realising the context of a path");
- throw;
- }
- }();
+ auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
try {
StringMap rewrites = state.realiseContext(context);
@@ -119,7 +113,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea
? state.checkSourcePath(realPath)
: realPath;
} catch (Error & e) {
- e.addTrace(pos, "while realising the context of path '%s'", path);
+ e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
throw;
}
}
@@ -157,7 +151,7 @@ static void mkOutputString(
/* Load and evaluate an expression from path specified by the
argument. */
-static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vScope, Value & v)
+static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{
auto path = realisePath(state, pos, vPath);
@@ -195,9 +189,9 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
, "/"), **state.vImportedDrvToDerivation);
}
- state.forceFunction(**state.vImportedDrvToDerivation, pos);
+ state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
v.mkApp(*state.vImportedDrvToDerivation, w);
- state.forceAttrs(v, pos);
+ state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
else if (path == corepkgsPrefix + "fetchurl.nix") {
@@ -210,16 +204,16 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
if (!vScope)
state.evalFile(path, v);
else {
- state.forceAttrs(*vScope, pos);
+ state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
Env * env = &state.allocEnv(vScope->attrs->size());
env->up = &state.baseEnv;
- StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size());
+ auto staticEnv = std::make_shared<StaticEnv>(false, state.staticBaseEnv.get(), vScope->attrs->size());
unsigned int displ = 0;
for (auto & attr : *vScope->attrs) {
- staticEnv.vars.emplace_back(attr.name, displ);
+ staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value;
}
@@ -237,7 +231,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info {
.name = "scopedImport",
.arity = 2,
- .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
+ .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
import(state, pos, *args[1], args[0], v);
}
@@ -299,7 +293,7 @@ static RegisterPrimOp primop_import({
(The function argument doesn’t have to be called `x` in `foo.nix`;
any name would work.)
)",
- .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
+ .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
import(state, pos, *args[0], nullptr, v);
}
@@ -310,25 +304,24 @@ static RegisterPrimOp primop_import({
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
/* Load a ValueInitializer from a DSO and return whatever it initializes */
-void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
- std::string sym(state.forceStringNoCtx(*args[1], pos));
+ std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
- throw EvalError("could not open '%1%': %2%", path, dlerror());
+ state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) {
char *message = dlerror();
if (message)
- throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message);
+ state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
else
- throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected",
- sym, path);
+ state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
}
(func)(state, v);
@@ -338,52 +331,52 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
/* Execute a program and parse its output */
-void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
+ state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.exec");
auto elems = args[0]->listElems();
auto count = args[0]->listSize();
- if (count == 0) {
- throw EvalError({
+ if (count == 0)
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("at least one argument to 'exec' required"),
- .errPos = pos
- });
- }
+ .errPos = state.positions[pos]
+ }));
PathSet context;
- auto program = state.coerceToString(pos, *elems[0], context, false, false).toOwned();
+ auto program = state.coerceToString(pos, *elems[0], context, false, false,
+ "while evaluating the first element of the argument passed to builtins.exec").toOwned();
Strings commandArgs;
for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
- commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false).toOwned());
+ commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false,
+ "while evaluating an element of the argument passed to builtins.exec").toOwned());
}
try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) {
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
program, e.path),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
}
auto output = runProgram(program, true, commandArgs);
Expr * parsed;
try {
- parsed = state.parseExprFromString(std::move(output), pos.file);
+ parsed = state.parseExprFromString(std::move(output), "/");
} catch (Error & e) {
- e.addTrace(pos, "While parsing the output from '%1%'", program);
+ e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
throw;
}
try {
state.eval(parsed, v);
} catch (Error & e) {
- e.addTrace(pos, "While evaluating the output from '%1%'", program);
+ e.addTrace(state.positions[pos], "while evaluating the output from '%1%'", program);
throw;
}
}
-
/* Return a string representing the type of the expression. */
-static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
std::string t;
@@ -402,7 +395,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
case nFloat: t = "float"; break;
case nThunk: abort();
}
- v.mkString(state.symbols.create(t));
+ v.mkString(t);
}
static RegisterPrimOp primop_typeOf({
@@ -417,7 +410,7 @@ static RegisterPrimOp primop_typeOf({
});
/* Determine whether the argument is the null value. */
-static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isNull(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nNull);
@@ -437,7 +430,7 @@ static RegisterPrimOp primop_isNull({
});
/* Determine whether the argument is a function. */
-static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isFunction(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nFunction);
@@ -453,7 +446,7 @@ static RegisterPrimOp primop_isFunction({
});
/* Determine whether the argument is an integer. */
-static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isInt(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nInt);
@@ -469,7 +462,7 @@ static RegisterPrimOp primop_isInt({
});
/* Determine whether the argument is a float. */
-static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isFloat(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nFloat);
@@ -485,7 +478,7 @@ static RegisterPrimOp primop_isFloat({
});
/* Determine whether the argument is a string. */
-static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nString);
@@ -501,7 +494,7 @@ static RegisterPrimOp primop_isString({
});
/* Determine whether the argument is a Boolean. */
-static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isBool(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nBool);
@@ -517,7 +510,7 @@ static RegisterPrimOp primop_isBool({
});
/* Determine whether the argument is a path. */
-static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nPath);
@@ -532,49 +525,75 @@ static RegisterPrimOp primop_isPath({
.fun = prim_isPath,
});
+template<typename Callable>
+ static inline void withExceptionContext(Trace trace, Callable&& func)
+{
+ try
+ {
+ func();
+ }
+ catch(Error & e)
+ {
+ e.pushTrace(trace);
+ throw;
+ }
+}
+
struct CompareValues
{
EvalState & state;
+ const PosIdx pos;
+ const std::string_view errorCtx;
- CompareValues(EvalState & state) : state(state) { };
+ CompareValues(EvalState & state, const PosIdx pos, const std::string_view && errorCtx) : state(state), pos(pos), errorCtx(errorCtx) { };
bool operator () (Value * v1, Value * v2) const
{
- if (v1->type() == nFloat && v2->type() == nInt)
- return v1->fpoint < v2->integer;
- if (v1->type() == nInt && v2->type() == nFloat)
- return v1->integer < v2->fpoint;
- if (v1->type() != v2->type())
- throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
- switch (v1->type()) {
- case nInt:
- return v1->integer < v2->integer;
- case nFloat:
- return v1->fpoint < v2->fpoint;
- case nString:
- return strcmp(v1->string.s, v2->string.s) < 0;
- case nPath:
- return strcmp(v1->path, v2->path) < 0;
- case nList:
- // Lexicographic comparison
- for (size_t i = 0;; i++) {
- if (i == v2->listSize()) {
- return false;
- } else if (i == v1->listSize()) {
- return true;
- } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i])) {
- return (*this)(v1->listElems()[i], v2->listElems()[i]);
+ return (*this)(v1, v2, errorCtx);
+ }
+
+ bool operator () (Value * v1, Value * v2, std::string_view errorCtx) const
+ {
+ try {
+ if (v1->type() == nFloat && v2->type() == nInt)
+ return v1->fpoint < v2->integer;
+ if (v1->type() == nInt && v2->type() == nFloat)
+ return v1->integer < v2->fpoint;
+ if (v1->type() != v2->type())
+ state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>();
+ switch (v1->type()) {
+ case nInt:
+ return v1->integer < v2->integer;
+ case nFloat:
+ return v1->fpoint < v2->fpoint;
+ case nString:
+ return strcmp(v1->string.s, v2->string.s) < 0;
+ case nPath:
+ return strcmp(v1->path, v2->path) < 0;
+ case nList:
+ // Lexicographic comparison
+ for (size_t i = 0;; i++) {
+ if (i == v2->listSize()) {
+ return false;
+ } else if (i == v1->listSize()) {
+ return true;
+ } else if (!state.eqValues(*v1->listElems()[i], *v2->listElems()[i], pos, errorCtx)) {
+ return (*this)(v1->listElems()[i], v2->listElems()[i], "while comparing two list elements");
+ }
}
- }
- default:
- throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
+ default:
+ state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
+ }
+ } catch (Error & e) {
+ e.addTrace(nullptr, errorCtx);
+ throw;
}
}
};
#if HAVE_BOEHMGC
-typedef std::list<Value *, gc_allocator<Value *> > ValueList;
+typedef std::list<Value *, gc_allocator<Value *>> ValueList;
#else
typedef std::list<Value *> ValueList;
#endif
@@ -582,105 +601,75 @@ typedef std::list<Value *> ValueList;
static Bindings::iterator getAttr(
EvalState & state,
- std::string_view funcName,
Symbol attrSym,
Bindings * attrSet,
- const Pos & pos)
+ std::string_view errorCtx)
{
Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) {
- hintformat errorMsg = hintfmt(
- "attribute '%s' missing for call to '%s'",
- attrSym,
- funcName
- );
-
- Pos aPos = *attrSet->pos;
- if (aPos == noPos) {
- throw TypeError({
- .msg = errorMsg,
- .errPos = pos,
- });
- } else {
- auto e = TypeError({
- .msg = errorMsg,
- .errPos = aPos,
- });
-
- // Adding another trace for the function name to make it clear
- // which call received wrong arguments.
- e.addTrace(pos, hintfmt("while invoking '%s'", funcName));
- throw e;
- }
+ throw TypeError({
+ .msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)),
+ .errPos = state.positions[attrSet->pos],
+ });
+ // TODO XXX
+ // Adding another trace for the function name to make it clear
+ // which call received wrong arguments.
+ //e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
+ //state.debugThrowLastTrace(e);
}
-
return value;
}
-static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], noPos, "while evaluating the first argument passed to builtins.genericClosure");
/* Get the start set. */
- Bindings::iterator startSet = getAttr(
- state,
- "genericClosure",
- state.sStartSet,
- args[0]->attrs,
- pos
- );
+ Bindings::iterator startSet = getAttr(state, state.sStartSet, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure");
- state.forceList(*startSet->value, pos);
+ state.forceList(*startSet->value, noPos, "while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure");
ValueList workSet;
for (auto elem : startSet->value->listItems())
workSet.push_back(elem);
- /* Get the operator. */
- Bindings::iterator op = getAttr(
- state,
- "genericClosure",
- state.sOperator,
- args[0]->attrs,
- pos
- );
+ if (startSet->value->listSize() == 0) {
+ v = *startSet->value;
+ return;
+ }
- state.forceValue(*op->value, pos);
+ /* Get the operator. */
+ Bindings::iterator op = getAttr(state, state.sOperator, args[0]->attrs, "in the attrset passed as argument to builtins.genericClosure");
+ state.forceFunction(*op->value, noPos, "while evaluating the 'operator' attribute passed as argument to builtins.genericClosure");
- /* Construct the closure by applying the operator to element of
+ /* Construct the closure by applying the operator to elements of
`workSet', adding the result to `workSet', continuing until
no new elements are found. */
ValueList res;
// `doneKeys' doesn't need to be a GC root, because its values are
// reachable from res.
- auto cmp = CompareValues(state);
+ auto cmp = CompareValues(state, noPos, "while comparing the `key` attributes of two genericClosure elements");
std::set<Value *, decltype(cmp)> doneKeys(cmp);
while (!workSet.empty()) {
Value * e = *(workSet.begin());
workSet.pop_front();
- state.forceAttrs(*e, pos);
+ state.forceAttrs(*e, noPos, "while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure");
- Bindings::iterator key =
- e->attrs->find(state.sKey);
- if (key == e->attrs->end())
- throw EvalError({
- .msg = hintfmt("attribute 'key' required"),
- .errPos = pos
- });
- state.forceValue(*key->value, pos);
+ Bindings::iterator key = getAttr(state, state.sKey, e->attrs, "in one of the attrsets generated by (or initially passed to) builtins.genericClosure");
+ state.forceValue(*key->value, noPos);
if (!doneKeys.insert(key->value).second) continue;
res.push_back(e);
/* Call the `operator' function with `e' as argument. */
- Value call;
- call.mkApp(op->value, e);
- state.forceList(call, pos);
+ Value newElements;
+ state.callFunction(*op->value, 1, &e, newElements, noPos);
+ state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure");
/* Add the values returned by the operator to the work set. */
- for (auto elem : call.listItems()) {
- state.forceValue(*elem, pos);
+ for (auto elem : newElements.listItems()) {
+ state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by the `operator` passed to builtins.genericClosure");
workSet.push_back(elem);
}
}
@@ -723,17 +712,53 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.fun = prim_genericClosure,
});
+
+static RegisterPrimOp primop_break({
+ .name = "break",
+ .args = {"v"},
+ .doc = R"(
+ In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
+ Otherwise, return the argument `v`.
+ )",
+ .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
+ {
+ if (state.debugRepl && !state.debugTraces.empty()) {
+ auto error = Error(ErrorInfo {
+ .level = lvlInfo,
+ .msg = hintfmt("breakpoint reached"),
+ .errPos = state.positions[pos],
+ });
+
+ auto & dt = state.debugTraces.front();
+ state.runDebugRepl(&error, dt.env, dt.expr);
+
+ if (state.debugQuit) {
+ // If the user elects to quit the repl, throw an exception.
+ throw Error(ErrorInfo{
+ .level = lvlInfo,
+ .msg = hintfmt("quit the debugger"),
+ .errPos = nullptr,
+ });
+ }
+ }
+
+ // Return the value we were passed.
+ v = *args[0];
+ }
+});
+
static RegisterPrimOp primop_abort({
.name = "abort",
.args = {"s"},
.doc = R"(
Abort Nix expression evaluation and print the error message *s*.
)",
- .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
+ .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context).toOwned();
- throw Abort("evaluation aborted with the following error message: '%1%'", s);
+ auto s = state.coerceToString(pos, *args[0], context,
+ "while evaluating the error message passed to builtins.abort").toOwned();
+ state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
}
});
@@ -747,22 +772,24 @@ static RegisterPrimOp primop_throw({
derivations, a derivation that throws an error is silently skipped
(which is not the case for `abort`).
)",
- .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
+ .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context).toOwned();
- throw ThrownError(s);
+ auto s = state.coerceToString(pos, *args[0], context,
+ "while evaluating the error message passed to builtin.throw").toOwned();
+ state.debugThrowLastTrace(ThrownError(s));
}
});
-static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
try {
state.forceValue(*args[1], pos);
v = *args[1];
} catch (Error & e) {
PathSet context;
- e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned());
+ e.addTrace(nullptr, state.coerceToString(pos, *args[0], context,
+ "while evaluating the error message passed to builtins.addErrorContext").toOwned());
throw;
}
}
@@ -773,9 +800,10 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info {
.fun = prim_addErrorContext,
});
-static void prim_ceil(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_ceil(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto value = state.forceFloat(*args[0], args[0]->determinePos(pos));
+ auto value = state.forceFloat(*args[0], args[0]->determinePos(pos),
+ "while evaluating the first argument passed to builtins.ceil");
v.mkInt(ceil(value));
}
@@ -792,9 +820,9 @@ static RegisterPrimOp primop_ceil({
.fun = prim_ceil,
});
-static void prim_floor(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_floor(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto value = state.forceFloat(*args[0], args[0]->determinePos(pos));
+ auto value = state.forceFloat(*args[0], args[0]->determinePos(pos), "while evaluating the first argument passed to builtins.floor");
v.mkInt(floor(value));
}
@@ -813,9 +841,21 @@ static RegisterPrimOp primop_floor({
/* Try evaluating the argument. Success => {success=true; value=something;},
* else => {success=false; value=false;} */
-static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto attrs = state.buildBindings(2);
+
+ /* increment state.trylevel, and decrement it when this function returns. */
+ MaintainCount trylevel(state.trylevel);
+
+ void (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
+ if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
+ {
+ /* to prevent starting the repl from exceptions withing a tryEval, null it. */
+ savedDebugRepl = state.debugRepl;
+ state.debugRepl = nullptr;
+ }
+
try {
state.forceValue(*args[0], pos);
attrs.insert(state.sValue, args[0]);
@@ -824,6 +864,11 @@ static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Val
attrs.alloc(state.sValue).mkBool(false);
attrs.alloc("success").mkBool(false);
}
+
+ // restore the debugRepl pointer if we saved it earlier.
+ if (savedDebugRepl)
+ state.debugRepl = savedDebugRepl;
+
v.mkAttrs(attrs);
}
@@ -849,9 +894,9 @@ static RegisterPrimOp primop_tryEval({
});
/* Return an environment variable. Use with care. */
-static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- std::string name(state.forceStringNoCtx(*args[0], pos));
+ std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getEnv"));
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
}
@@ -873,7 +918,7 @@ static RegisterPrimOp primop_getEnv({
});
/* Evaluate the first argument, then return the second argument. */
-static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_seq(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
@@ -892,7 +937,7 @@ static RegisterPrimOp primop_seq({
/* Evaluate the first argument deeply (i.e. recursing into lists and
attrsets), then return the second argument. */
-static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_deepSeq(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValueDeep(*args[0]);
state.forceValue(*args[1], pos);
@@ -912,13 +957,13 @@ static RegisterPrimOp primop_deepSeq({
/* Evaluate the first expression and print it on standard error. Then
return the second expression. Useful for debugging. */
-static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
if (args[0]->type() == nString)
printError("trace: %1%", args[0]->string.s);
else
- printError("trace: %1%", *args[0]);
+ printError("trace: %1%", printValue(state, *args[0]));
state.forceValue(*args[1], pos);
v = *args[1];
}
@@ -935,6 +980,15 @@ static RegisterPrimOp primop_trace({
});
+/* Takes two arguments and evaluates to the second one. Used as the
+ * builtins.traceVerbose implementation when --trace-verbose is not enabled
+ */
+static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+ state.forceValue(*args[1], pos);
+ v = *args[1];
+}
+
/*************************************************************
* Derivations
*************************************************************/
@@ -947,40 +1001,34 @@ static RegisterPrimOp primop_trace({
derivation; `drvPath' containing the path of the Nix expression;
and `type' set to `derivation' to indicate that this is a
derivation. */
-static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ using nlohmann::json;
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
/* Figure out the name first (for stack backtraces). */
- Bindings::iterator attr = getAttr(
- state,
- "derivationStrict",
- state.sName,
- args[0]->attrs,
- pos
- );
+ Bindings::iterator attr = getAttr(state, state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict");
std::string drvName;
- Pos & posDrvName(*attr->pos);
+ const auto posDrvName = attr->pos;
try {
- drvName = state.forceStringNoCtx(*attr->value, pos);
+ drvName = state.forceStringNoCtx(*attr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
} catch (Error & e) {
- e.addTrace(posDrvName, "while evaluating the derivation attribute 'name'");
+ e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'");
throw;
}
/* Check whether attributes should be passed as a JSON file. */
- std::ostringstream jsonBuf;
- std::unique_ptr<JSONObject> jsonObject;
+ std::optional<json> jsonObject;
attr = args[0]->attrs->find(state.sStructuredAttrs);
- if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos))
- jsonObject = std::make_unique<JSONObject>(jsonBuf);
+ if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict"))
+ jsonObject = json::object();
/* Check whether null attributes should be ignored. */
bool ignoreNulls = false;
attr = args[0]->attrs->find(state.sIgnoreNulls);
if (attr != args[0]->attrs->end())
- ignoreNulls = state.forceBool(*attr->value, pos);
+ ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute passed to builtins.derivationStrict");
/* Build the derivation expression by processing the attributes. */
Derivation drv;
@@ -997,9 +1045,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
StringSet outputs;
outputs.insert("out");
- for (auto & i : args[0]->attrs->lexicographicOrder()) {
+ for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) {
if (i->name == state.sIgnoreNulls) continue;
- const std::string & key = i->name;
+ const std::string & key = state.symbols[i->name];
vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string_view s) {
@@ -1007,37 +1055,37 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else if (s == "text") ingestionMethod = TextHashMethod {};
else
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
};
auto handleOutputs = [&](const Strings & ss) {
outputs.clear();
for (auto & j : ss) {
if (outputs.find(j) != outputs.end())
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
/* !!! Check whether j is a valid attribute
name. */
/* Derivations cannot be named ‘drv’, because
then we'd have an attribute ‘drvPath’ in
the resulting set. */
if (j == "drv")
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
outputs.insert(j);
}
if (outputs.empty())
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
};
try {
@@ -1048,13 +1096,15 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
}
if (i->name == state.sContentAddressed) {
- contentAddressed = state.forceBool(*i->value, pos);
+ contentAddressed = state.forceBool(*i->value, pos,
+ "while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict");
if (contentAddressed)
settings.requireExperimentalFeature(Xp::CaDerivations);
}
else if (i->name == state.sImpure) {
- isImpure = state.forceBool(*i->value, pos);
+ isImpure = state.forceBool(*i->value, pos,
+ "while evaluating the 'impure' attribute passed to builtins.derivationStrict");
if (isImpure)
settings.requireExperimentalFeature(Xp::ImpureDerivations);
}
@@ -1062,9 +1112,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* The `args' attribute is special: it supplies the
command-line arguments to the builder. */
else if (i->name == state.sArgs) {
- state.forceList(*i->value, pos);
+ state.forceList(*i->value, pos,
+ "while evaluating the `args` attribute passed to builtins.derivationStrict");
for (auto elem : i->value->listItems()) {
- auto s = state.coerceToString(posDrvName, *elem, context, true).toOwned();
+ auto s = state.coerceToString(posDrvName, *elem, context, true,
+ "while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned();
drv.args.push_back(s);
}
}
@@ -1077,30 +1129,29 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
if (i->name == state.sStructuredAttrs) continue;
- auto placeholder(jsonObject->placeholder(key));
- printValueAsJSON(state, true, *i->value, pos, placeholder, context);
+ (*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
if (i->name == state.sBuilder)
- drv.builder = state.forceString(*i->value, context, posDrvName);
+ drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict");
else if (i->name == state.sSystem)
- drv.platform = state.forceStringNoCtx(*i->value, posDrvName);
+ drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `system` attribute passed to builtins.derivationStrict");
else if (i->name == state.sOutputHash)
- outputHash = state.forceStringNoCtx(*i->value, posDrvName);
+ outputHash = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHash` attribute passed to builtins.derivationStrict");
else if (i->name == state.sOutputHashAlgo)
- outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName);
+ outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict");
else if (i->name == state.sOutputHashMode)
- handleHashMode(state.forceStringNoCtx(*i->value, posDrvName));
+ handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashMode` attribute passed to builtins.derivationStrict"));
else if (i->name == state.sOutputs) {
/* Require ‘outputs’ to be a list of strings. */
- state.forceList(*i->value, posDrvName);
+ state.forceList(*i->value, posDrvName, "while evaluating the `outputs` attribute passed to builtins.derivationStrict");
Strings ss;
for (auto elem : i->value->listItems())
- ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName));
+ ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "while evaluating an element of the `outputs` attribute passed to builtins.derivationStrict"));
handleOutputs(ss);
}
} else {
- auto s = state.coerceToString(*i->pos, *i->value, context, true).toOwned();
+ auto s = state.coerceToString(i->pos, *i->value, context, true, "while evaluating an attribute passed to builtins.derivationStrict").toOwned();
drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s);
@@ -1114,16 +1165,16 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
}
} catch (Error & e) {
- e.addTrace(posDrvName,
- "while evaluating the attribute '%1%' of the derivation '%2%'",
- key, drvName);
+ e.addTrace(nullptr,
+ hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName),
+ true);
throw;
}
}
if (jsonObject) {
+ drv.env.emplace("__json", jsonObject->dump());
jsonObject.reset();
- drv.env.emplace("__json", jsonBuf.str());
}
/* Everything in the context of the strings in the derivation
@@ -1165,23 +1216,23 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Do we have all required attributes? */
if (drv.builder == "")
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
if (drv.platform == "")
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
/* Check whether the derivation name is valid. */
if (isDerivation(drvName) && ingestionMethod != ContentAddressMethod { TextHashMethod { } })
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
if (outputHash) {
/* Handle fixed-output derivations.
@@ -1189,10 +1240,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
Ignore `__contentAddressed` because fixed output derivations are
already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out")
- throw Error({
+ state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
- .errPos = posDrvName
- });
+ .errPos = state.positions[posDrvName]
+ }));
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
@@ -1213,7 +1264,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
if (contentAddressed && isImpure)
throw EvalError({
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
- .errPos = posDrvName
+ .errPos = state.positions[posDrvName]
});
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
@@ -1253,8 +1304,13 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
switch (hashModulo.kind) {
case DrvHash::Kind::Regular:
for (auto & i : outputs) {
- auto h = hashModulo.hashes.at(i);
- auto outPath = state.store->makeOutputPath(i, h, drvName);
+ auto h = get(hashModulo.hashes, i);
+ if (!h)
+ throw AssertionError({
+ .msg = hintfmt("derivation produced no hash for output '%s'", i),
+ .errPos = state.positions[posDrvName],
+ });
+ auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
i,
@@ -1305,9 +1361,9 @@ static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
time, any occurrence of this string in an derivation attribute will
be replaced with the concrete path in the Nix store of the output
‘out’. */
-static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_placeholder(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos)));
+ v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.placeholder")));
}
static RegisterPrimOp primop_placeholder({
@@ -1328,10 +1384,10 @@ static RegisterPrimOp primop_placeholder({
/* Convert the argument to a path. !!! obsolete? */
-static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- Path path = state.coerceToPath(pos, *args[0], context);
+ Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
v.mkString(canonPath(path), context);
}
@@ -1353,25 +1409,25 @@ static RegisterPrimOp primop_toPath({
/nix/store/newhash-oldhash-oldname. In the past, `toPath' had
special case behaviour for store paths, but that created weird
corner cases. */
-static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
PathSet context;
- Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context));
+ Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path))
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
auto path2 = state.store->toStorePath(path).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
@@ -1397,13 +1453,13 @@ static RegisterPrimOp primop_storePath({
.fun = prim_storePath,
});
-static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
/* We don’t check the path right now, because we don’t want to
- throw if the path isn’t allowed, but just return false (and we
- can’t just catch the exception here because we still want to
- throw if something in the evaluation of `*args[0]` tries to
- access an unauthorized path). */
+ throw if the path isn’t allowed, but just return false (and we
+ can’t just catch the exception here because we still want to
+ throw if something in the evaluation of `*args[0]` tries to
+ access an unauthorized path). */
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try {
@@ -1429,10 +1485,10 @@ static RegisterPrimOp primop_pathExists({
/* Return the base name of the given string, i.e., everything
following the last slash. */
-static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false)), context);
+ v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.baseNameOf")), context);
}
static RegisterPrimOp primop_baseNameOf({
@@ -1449,10 +1505,10 @@ static RegisterPrimOp primop_baseNameOf({
/* Return the directory of the given path, i.e., everything before the
last slash. Return either a path or a string depending on the type
of the argument. */
-static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto path = state.coerceToString(pos, *args[0], context, false, false);
+ auto path = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.dirOf");
auto dir = dirOf(*path);
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
}
@@ -1469,18 +1525,22 @@ static RegisterPrimOp primop_dirOf({
});
/* Return the contents of a file as a string. */
-static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
auto s = readFile(path);
if (s.find((char) 0) != std::string::npos)
- throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
+ state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs;
if (state.store->isInStore(path)) {
try {
refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references;
} catch (Error &) { // FIXME: should be InvalidPathError
}
+ // Re-scan references to filter down to just the ones that actually occur in the file.
+ auto refsSink = PathRefScanSink::fromPaths(refs);
+ refsSink << s;
+ refs = refsSink.getResultPaths();
}
auto context = state.store->printStorePathSet(refs);
v.mkString(s, context);
@@ -1497,46 +1557,40 @@ static RegisterPrimOp primop_readFile({
/* Find a file in the Nix search path. Used to implement <x> paths,
which are desugared to 'findFile __nixPath "x"'. */
-static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
+ state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.findFile");
SearchPath searchPath;
for (auto v2 : args[0]->listItems()) {
- state.forceAttrs(*v2, pos);
+ state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.findFile");
std::string prefix;
Bindings::iterator i = v2->attrs->find(state.sPrefix);
if (i != v2->attrs->end())
- prefix = state.forceStringNoCtx(*i->value, pos);
+ prefix = state.forceStringNoCtx(*i->value, pos, "while evaluating the `prefix` attribute of an element of the list passed to builtins.findFile");
- i = getAttr(
- state,
- "findFile",
- state.sPath,
- v2->attrs,
- pos
- );
+ i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
PathSet context;
- auto path = state.coerceToString(pos, *i->value, context, false, false).toOwned();
+ auto path = state.coerceToString(pos, *i->value, context, false, false,
+ "while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned();
try {
auto rewrites = state.realiseContext(context);
path = rewriteStrings(path, rewrites);
} catch (InvalidPathError & e) {
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
}
-
searchPath.emplace_back(prefix, path);
}
- auto path = state.forceStringNoCtx(*args[1], pos);
+ auto path = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
}
@@ -1548,15 +1602,15 @@ static RegisterPrimOp primop_findFile(RegisterPrimOp::Info {
});
/* Return the cryptographic hash of a file in base-16. */
-static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto type = state.forceStringNoCtx(*args[0], pos);
+ auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
std::optional<HashType> ht = parseHashType(type);
if (!ht)
- throw Error({
+ state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash type '%1%'", type),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
auto path = realisePath(state, pos, *args[1]);
@@ -1575,7 +1629,7 @@ static RegisterPrimOp primop_hashFile({
});
/* Read a directory (without . or ..) */
-static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
@@ -1624,7 +1678,7 @@ static RegisterPrimOp primop_readDir({
/* Convert the argument (which can be any Nix expression) to an XML
representation returned in a string. Not all Nix expressions can
be sensibly or completely represented (e.g., functions). */
-static void prim_toXML(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::ostringstream out;
PathSet context;
@@ -1732,7 +1786,7 @@ static RegisterPrimOp primop_toXML({
/* Convert the argument (which can be any Nix expression) to a JSON
string. Not all Nix expressions can be sensibly or completely
represented (e.g., functions). */
-static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::ostringstream out;
PathSet context;
@@ -1755,13 +1809,13 @@ static RegisterPrimOp primop_toJSON({
});
/* Parse a JSON string to a value. */
-static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fromJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto s = state.forceStringNoCtx(*args[0], pos);
+ auto s = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.fromJSON");
try {
parseJSON(state, s, v);
} catch (JSONParseError &e) {
- e.addTrace(pos, "while decoding a JSON string");
+ e.addTrace(state.positions[pos], "while decoding a JSON string");
throw;
}
}
@@ -1783,35 +1837,36 @@ static RegisterPrimOp primop_fromJSON({
/* Store a string in the Nix store as a source file that can be used
as an input by derivations. */
-static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- std::string name(state.forceStringNoCtx(*args[0], pos));
- std::string contents(state.forceString(*args[1], context, pos));
+ std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.toFile"));
+ std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
StorePathSet refs;
for (auto path : context) {
if (path.at(0) != '/')
- throw EvalError( {
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt(
"in 'toFile': the file named '%1%' must not contain a reference "
"to a derivation but contains (%2%)",
name, path),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
refs.insert(state.store->parseStorePath(path));
}
- auto storePath = state.store->printStorePath(settings.readOnlyMode
+ auto storePath = settings.readOnlyMode
? state.store->computeStorePathForText(name, contents, refs)
- : state.store->addTextToStore(name, contents, refs, state.repair));
+ : state.store->addTextToStore(name, contents, refs, state.repair);
/* Note: we don't need to add `context' to the context of the
result, since `storePath' itself has references to the paths
used in args[1]. */
- v.mkString(storePath, {storePath});
+ /* Add the output of this to the allowed paths. */
+ state.allowAndSetStorePathString(storePath, v);
}
static RegisterPrimOp primop_toFile({
@@ -1866,8 +1921,8 @@ static RegisterPrimOp primop_toFile({
";
```
- Note that `${configFile}` is an
- [antiquotation](language-values.md), so the result of the
+ Note that `${configFile}` is a
+ [string interpolation](language/values.md#type-string), so the result of the
expression `configFile`
(i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be
spliced into the resulting string.
@@ -1894,7 +1949,7 @@ static RegisterPrimOp primop_toFile({
static void addPath(
EvalState & state,
- const Pos & pos,
+ const PosIdx pos,
const std::string & name,
Path path,
Value * filterFun,
@@ -1944,7 +1999,7 @@ static void addPath(
Value res;
state.callFunction(*filterFun, 2, args, res, pos);
- return state.forceBool(res, pos);
+ return state.forceBool(res, pos, "while evaluating the return value of the path filter function");
}) : defaultPathFilter;
std::optional<StorePath> expectedStorePath;
@@ -1962,31 +2017,22 @@ static void addPath(
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs);
if (expectedHash && expectedStorePath != dstPath)
- throw Error("store path mismatch in (possibly filtered) path added from '%s'", path);
+ state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v);
} else
state.allowAndSetStorePathString(*expectedStorePath, v);
} catch (Error & e) {
- e.addTrace(pos, "while adding path '%s'", path);
+ e.addTrace(state.positions[pos], "while adding path '%s'", path);
throw;
}
}
-static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- Path path = state.coerceToPath(pos, *args[1], context);
-
- state.forceValue(*args[0], pos);
- if (args[0]->type() != nFunction)
- throw TypeError({
- .msg = hintfmt(
- "first argument in call to 'filterSource' is not a function but %1%",
- showType(*args[0])),
- .errPos = pos
- });
-
+ Path path = state.coerceToPath(pos, *args[1], context, "while evaluating the second argument (the path to filter) passed to builtins.filterSource");
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
@@ -2045,9 +2091,9 @@ static RegisterPrimOp primop_filterSource({
.fun = prim_filterSource,
});
-static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.path");
Path path;
std::string name;
Value * filterFun = nullptr;
@@ -2056,29 +2102,28 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
PathSet context;
for (auto & attr : *args[0]->attrs) {
- auto & n(attr.name);
+ auto n = state.symbols[attr.name];
if (n == "path")
- path = state.coerceToPath(*attr.pos, *attr.value, context);
+ path = state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the `path` attribute passed to builtins.path");
else if (attr.name == state.sName)
- name = state.forceStringNoCtx(*attr.value, *attr.pos);
- else if (n == "filter") {
- state.forceValue(*attr.value, pos);
- filterFun = attr.value;
- } else if (n == "recursive")
- method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) };
+ name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
+ else if (n == "filter")
+ state.forceFunction(*(filterFun = attr.value), attr.pos, "while evaluating the `filter` parameter passed to builtins.path");
+ else if (n == "recursive")
+ method = FileIngestionMethod { state.forceBool(*attr.value, attr.pos, "while evaluating the `recursive` attribute passed to builtins.path") };
else if (n == "sha256")
- expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+ expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), htSHA256);
else
- throw EvalError({
- .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name),
- .errPos = *attr.pos
- });
+ state.debugThrowLastTrace(EvalError({
+ .msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
+ .errPos = state.positions[attr.pos]
+ }));
}
if (path.empty())
- throw EvalError({
- .msg = hintfmt("'path' required"),
- .errPos = pos
- });
+ state.debugThrowLastTrace(EvalError({
+ .msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
+ .errPos = state.positions[pos]
+ }));
if (name.empty())
name = baseNameOf(path);
@@ -2128,15 +2173,15 @@ static RegisterPrimOp primop_path({
/* Return the names of the attributes in a set as a sorted list of
strings. */
-static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames");
state.mkList(v, args[0]->attrs->size());
size_t n = 0;
for (auto & i : *args[0]->attrs)
- (v.listElems()[n++] = state.allocValue())->mkString(i.name);
+ (v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(v.listElems(), v.listElems() + n,
[](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
@@ -2155,9 +2200,9 @@ static RegisterPrimOp primop_attrNames({
/* Return the values of the attributes in a set as a list, in the same
order as attrNames. */
-static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues");
state.mkList(v, args[0]->attrs->size());
@@ -2166,8 +2211,9 @@ static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args,
v.listElems()[n++] = (Value *) &i;
std::sort(v.listElems(), v.listElems() + n,
- [](Value * v1, Value * v2) {
- std::string_view s1 = ((Attr *) v1)->name, s2 = ((Attr *) v2)->name;
+ [&](Value * v1, Value * v2) {
+ std::string_view s1 = state.symbols[((Attr *) v1)->name],
+ s2 = state.symbols[((Attr *) v2)->name];
return s1 < s2;
});
@@ -2186,19 +2232,18 @@ static RegisterPrimOp primop_attrValues({
});
/* Dynamic version of the `.' operator. */
-void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto attr = state.forceStringNoCtx(*args[0], pos);
- state.forceAttrs(*args[1], pos);
+ auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getAttr");
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.getAttr");
Bindings::iterator i = getAttr(
state,
- "getAttr",
state.symbols.create(attr),
args[1]->attrs,
- pos
+ "in the attribute set under consideration"
);
// !!! add to stack trace?
- if (state.countCalls && *i->pos != noPos) state.attrSelects[*i->pos]++;
+ if (state.countCalls && i->pos) state.attrSelects[i->pos]++;
state.forceValue(*i->value, pos);
v = *i->value;
}
@@ -2216,10 +2261,10 @@ static RegisterPrimOp primop_getAttr({
});
/* Return position information of the specified attribute. */
-static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto attr = state.forceStringNoCtx(*args[0], pos);
- state.forceAttrs(*args[1], pos);
+ auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.unsafeGetAttrPos");
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.unsafeGetAttrPos");
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
if (i == args[1]->attrs->end())
v.mkNull();
@@ -2234,10 +2279,10 @@ static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info {
});
/* Dynamic version of the `?' operator. */
-static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_hasAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto attr = state.forceStringNoCtx(*args[0], pos);
- state.forceAttrs(*args[1], pos);
+ auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hasAttr");
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.hasAttr");
v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end());
}
@@ -2253,7 +2298,7 @@ static RegisterPrimOp primop_hasAttr({
});
/* Determine whether the argument is a set. */
-static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nAttrs);
@@ -2268,10 +2313,10 @@ static RegisterPrimOp primop_isAttrs({
.fun = prim_isAttrs,
});
-static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.removeAttrs");
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.removeAttrs");
/* Get the attribute names to be removed.
We keep them as Attrs instead of Symbols so std::set_difference
@@ -2279,7 +2324,7 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args,
boost::container::small_vector<Attr, 64> names;
names.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) {
- state.forceStringNoCtx(*elem, pos);
+ state.forceStringNoCtx(*elem, pos, "while evaluating the values of the second argument passed to builtins.removeAttrs");
names.emplace_back(state.symbols.create(elem->string.s), nullptr);
}
std::sort(names.begin(), names.end());
@@ -2316,36 +2361,24 @@ static RegisterPrimOp primop_removeAttrs({
"nameN"; value = valueN;}] is transformed to {name1 = value1;
... nameN = valueN;}. In case of duplicate occurrences of the same
name, the first takes precedence. */
-static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
+ state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");
auto attrs = state.buildBindings(args[0]->listSize());
std::set<Symbol> seen;
for (auto v2 : args[0]->listItems()) {
- state.forceAttrs(*v2, pos);
+ state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");
- Bindings::iterator j = getAttr(
- state,
- "listToAttrs",
- state.sName,
- v2->attrs,
- pos
- );
+ Bindings::iterator j = getAttr(state, state.sName, v2->attrs, "in a {name=...; value=...;} pair");
- auto name = state.forceStringNoCtx(*j->value, *j->pos);
+ auto name = state.forceStringNoCtx(*j->value, j->pos, "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
- Symbol sym = state.symbols.create(name);
+ auto sym = state.symbols.create(name);
if (seen.insert(sym).second) {
- Bindings::iterator j2 = getAttr(
- state,
- "listToAttrs",
- state.sValue,
- v2->attrs,
- pos
- );
+ Bindings::iterator j2 = getAttr(state, state.sValue, v2->attrs, "in a {name=...; value=...;} pair");
attrs.insert(sym, j2->value, j2->pos);
}
}
@@ -2360,12 +2393,18 @@ static RegisterPrimOp primop_listToAttrs({
Construct a set from a list specifying the names and values of each
attribute. Each element of the list should be a set consisting of a
string-valued attribute `name` specifying the name of the attribute,
- and an attribute `value` specifying its value. Example:
+ and an attribute `value` specifying its value.
+
+ In case of duplicate occurrences of the same name, the first
+ takes precedence.
+
+ Example:
```nix
builtins.listToAttrs
[ { name = "foo"; value = 123; }
{ name = "bar"; value = 456; }
+ { name = "bar"; value = 420; }
]
```
@@ -2378,17 +2417,67 @@ static RegisterPrimOp primop_listToAttrs({
.fun = prim_listToAttrs,
});
-static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
-{
- state.forceAttrs(*args[0], pos);
- state.forceAttrs(*args[1], pos);
-
- auto attrs = state.buildBindings(std::min(args[0]->attrs->size(), args[1]->attrs->size()));
-
- for (auto & i : *args[0]->attrs) {
- Bindings::iterator j = args[1]->attrs->find(i.name);
- if (j != args[1]->attrs->end())
- attrs.insert(*j);
+static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
+{
+ state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.intersectAttrs");
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.intersectAttrs");
+
+ Bindings &left = *args[0]->attrs;
+ Bindings &right = *args[1]->attrs;
+
+ auto attrs = state.buildBindings(std::min(left.size(), right.size()));
+
+ // The current implementation has good asymptotic complexity and is reasonably
+ // simple. Further optimization may be possible, but does not seem productive,
+ // considering the state of eval performance in 2022.
+ //
+ // I have looked for reusable and/or standard solutions and these are my
+ // findings:
+ //
+ // STL
+ // ===
+ // std::set_intersection is not suitable, as it only performs a simultaneous
+ // linear scan; not taking advantage of random access. This is O(n + m), so
+ // linear in the largest set, which is not acceptable for callPackage in Nixpkgs.
+ //
+ // Simultaneous scan, with alternating simple binary search
+ // ===
+ // One alternative algorithm scans the attrsets simultaneously, jumping
+ // forward using `lower_bound` in case of inequality. This should perform
+ // well on very similar sets, having a local and predictable access pattern.
+ // On dissimilar sets, it seems to need more comparisons than the current
+ // algorithm, as few consecutive attrs match. `lower_bound` could take
+ // advantage of the decreasing remaining search space, but this causes
+ // the medians to move, which can mean that they don't stay in the cache
+ // like they would with the current naive `find`.
+ //
+ // Double binary search
+ // ===
+ // The optimal algorithm may be "Double binary search", which doesn't
+ // scan at all, but rather divides both sets simultaneously.
+ // See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al.
+ // https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf
+ // The only downsides I can think of are not having a linear access pattern
+ // for similar sets, and having to maintain a more intricate algorithm.
+ //
+ // Adaptive
+ // ===
+ // Finally one could run try a simultaneous scan, count misses and fall back
+ // to double binary search when the counter hit some threshold and/or ratio.
+
+ if (left.size() < right.size()) {
+ for (auto & l : left) {
+ Bindings::iterator r = right.find(l.name);
+ if (r != right.end())
+ attrs.insert(*r);
+ }
+ }
+ else {
+ for (auto & r : right) {
+ Bindings::iterator l = left.find(r.name);
+ if (l != left.end())
+ attrs.insert(r);
+ }
}
v.mkAttrs(attrs.alreadySorted());
@@ -2398,22 +2487,24 @@ static RegisterPrimOp primop_intersectAttrs({
.name = "__intersectAttrs",
.args = {"e1", "e2"},
.doc = R"(
- Return a set consisting of the attributes in the set *e2* that also
- exist in the set *e1*.
+ Return a set consisting of the attributes in the set *e2* which have the
+ same name as some attribute in *e1*.
+
+ Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size.
)",
.fun = prim_intersectAttrs,
});
-static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos));
- state.forceList(*args[1], pos);
+ auto attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.catAttrs"));
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.catAttrs");
Value * res[args[1]->listSize()];
unsigned int found = 0;
for (auto v2 : args[1]->listItems()) {
- state.forceAttrs(*v2, pos);
+ state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs");
Bindings::iterator i = v2->attrs->find(attrName);
if (i != v2->attrs->end())
res[found++] = i->value;
@@ -2441,7 +2532,7 @@ static RegisterPrimOp primop_catAttrs({
.fun = prim_catAttrs,
});
-static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) {
@@ -2449,10 +2540,10 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
return;
}
if (!args[0]->isLambda())
- throw TypeError({
+ state.debugThrowLastTrace(TypeError({
.msg = hintfmt("'functionArgs' requires a function"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
@@ -2462,7 +2553,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals)
// !!! should optimise booleans (allocate only once)
- attrs.alloc(i.name, ptr(&i.pos)).mkBool(i.def);
+ attrs.alloc(i.name, i.pos).mkBool(i.def);
v.mkAttrs(attrs);
}
@@ -2484,16 +2575,16 @@ static RegisterPrimOp primop_functionArgs({
});
/* */
-static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[1], pos);
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.mapAttrs");
auto attrs = state.buildBindings(args[1]->attrs->size());
for (auto & i : *args[1]->attrs) {
Value * vName = state.allocValue();
Value * vFun2 = state.allocValue();
- vName->mkString(i.name);
+ vName->mkString(state.symbols[i.name]);
vFun2->mkApp(args[0], vName);
attrs.alloc(i.name).mkApp(vFun2, i.value);
}
@@ -2516,7 +2607,7 @@ static RegisterPrimOp primop_mapAttrs({
.fun = prim_mapAttrs,
});
-static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
// we will first count how many values are present for each given key.
// we then allocate a single attrset and pre-populate it with lists of
@@ -2527,20 +2618,20 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args
std::map<Symbol, std::pair<size_t, Value * *>> attrsSeen;
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith");
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith");
const auto listSize = args[1]->listSize();
const auto listElems = args[1]->listElems();
for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n];
try {
- state.forceAttrs(*vElem, noPos);
+ state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs)
attrsSeen[attr.name].first++;
} catch (TypeError & e) {
- e.addTrace(pos, hintfmt("while invoking '%s'", "zipAttrsWith"));
- throw;
+ e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
+ state.debugThrowLastTrace(e);
}
}
@@ -2560,7 +2651,7 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args
for (auto & attr : *v.attrs) {
auto name = state.allocValue();
- name->mkString(attr.name);
+ name->mkString(state.symbols[attr.name]);
auto call1 = state.allocValue();
call1->mkApp(args[0], name);
auto call2 = state.allocValue();
@@ -2608,7 +2699,7 @@ static RegisterPrimOp primop_zipAttrsWith({
/* Determine whether the argument is a list. */
-static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_isList(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
v.mkBool(args[0]->type() == nList);
@@ -2623,22 +2714,22 @@ static RegisterPrimOp primop_isList({
.fun = prim_isList,
});
-static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v)
+static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v)
{
- state.forceList(list, pos);
+ state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
if (n < 0 || (unsigned int) n >= list.listSize())
- throw Error({
+ state.debugThrowLastTrace(Error({
.msg = hintfmt("list index %1% is out of bounds", n),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
state.forceValue(*list.listElems()[n], pos);
v = *list.listElems()[n];
}
/* Return the n-1'th element of a list. */
-static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v);
+ elemAt(state, pos, *args[0], state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.elemAt"), v);
}
static RegisterPrimOp primop_elemAt({
@@ -2652,7 +2743,7 @@ static RegisterPrimOp primop_elemAt({
});
/* Return the first element of a list. */
-static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_head(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
elemAt(state, pos, *args[0], 0, v);
}
@@ -2671,14 +2762,14 @@ static RegisterPrimOp primop_head({
/* Return a list consisting of everything but the first element of
a list. Warning: this function takes O(n) time, so you probably
don't want to use it! */
-static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
+ state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
if (args[0]->listSize() == 0)
- throw Error({
+ state.debugThrowLastTrace(Error({
.msg = hintfmt("'tail' called on an empty list"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
state.mkList(v, args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n)
@@ -2702,12 +2793,18 @@ static RegisterPrimOp primop_tail({
});
/* Apply a function to every element of a list. */
-static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[1], pos);
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.map");
- state.mkList(v, args[1]->listSize());
+ if (args[1]->listSize() == 0) {
+ v = *args[1];
+ return;
+ }
+
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map");
+ state.mkList(v, args[1]->listSize());
for (unsigned int n = 0; n < v.listSize(); ++n)
(v.listElems()[n] = state.allocValue())->mkApp(
args[0], args[1]->listElems()[n]);
@@ -2721,7 +2818,7 @@ static RegisterPrimOp primop_map({
example,
```nix
- map (x: "foo" + x) [ "bar" "bla" "abc" ]
+ map (x"foo" + x) [ "bar" "bla" "abc" ]
```
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
@@ -2732,10 +2829,16 @@ static RegisterPrimOp primop_map({
/* Filter a list using a predicate; that is, return a list containing
every element from the list for which the predicate function
returns true. */
-static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.filter");
+
+ if (args[1]->listSize() == 0) {
+ v = *args[1];
+ return;
+ }
+
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filter");
// FIXME: putting this on the stack is risky.
Value * vs[args[1]->listSize()];
@@ -2745,7 +2848,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
Value res;
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
- if (state.forceBool(res, pos))
+ if (state.forceBool(res, pos, "while evaluating the return value of the filtering function passed to builtins.filter"))
vs[k++] = args[1]->listElems()[n];
else
same = false;
@@ -2770,12 +2873,12 @@ static RegisterPrimOp primop_filter({
});
/* Return true if a list contains a given element. */
-static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_elem(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
bool res = false;
- state.forceList(*args[1], pos);
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.elem");
for (auto elem : args[1]->listItems())
- if (state.eqValues(*args[0], *elem)) {
+ if (state.eqValues(*args[0], *elem, pos, "while searching for the presence of the given element in the list")) {
res = true;
break;
}
@@ -2793,10 +2896,10 @@ static RegisterPrimOp primop_elem({
});
/* Concatenate a list of lists. */
-static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_concatLists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
- state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos);
+ state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.concatLists");
+ state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos, "while evaluating a value of the list passed to builtins.concatLists");
}
static RegisterPrimOp primop_concatLists({
@@ -2809,9 +2912,9 @@ static RegisterPrimOp primop_concatLists({
});
/* Return the length of a list. This is an O(1) time operation. */
-static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_length(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
+ state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.length");
v.mkInt(args[0]->listSize());
}
@@ -2826,10 +2929,10 @@ static RegisterPrimOp primop_length({
/* Reduce a list by applying a binary operator, from left to
right. The operator is applied strictly. */
-static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[2], pos);
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.foldlStrict");
+ state.forceList(*args[2], pos, "while evaluating the third argument passed to builtins.foldlStrict");
if (args[2]->listSize()) {
Value * vCur = args[1];
@@ -2859,15 +2962,15 @@ static RegisterPrimOp primop_foldlStrict({
.fun = prim_foldlStrict,
});
-static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceFunction(*args[0], pos, std::string("while evaluating the first argument passed to builtins.") + (any ? "any" : "all"));
+ state.forceList(*args[1], pos, std::string("while evaluating the second argument passed to builtins.") + (any ? "any" : "all"));
Value vTmp;
for (auto elem : args[1]->listItems()) {
state.callFunction(*args[0], *elem, vTmp, pos);
- bool res = state.forceBool(vTmp, pos);
+ bool res = state.forceBool(vTmp, pos, std::string("while evaluating the return value of the function passed to builtins.") + (any ? "any" : "all"));
if (res == any) {
v.mkBool(any);
return;
@@ -2878,7 +2981,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg
}
-static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_any(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
anyOrAll(true, state, pos, args, v);
}
@@ -2893,7 +2996,7 @@ static RegisterPrimOp primop_any({
.fun = prim_any,
});
-static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_all(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
anyOrAll(false, state, pos, args, v);
}
@@ -2908,15 +3011,15 @@ static RegisterPrimOp primop_all({
.fun = prim_all,
});
-static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto len = state.forceInt(*args[1], pos);
+ auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
if (len < 0)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot create list of size %1%", len),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
state.mkList(v, len);
@@ -2943,15 +3046,21 @@ static RegisterPrimOp primop_genList({
.fun = prim_genList,
});
-static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v);
+static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v);
-static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.sort");
auto len = args[1]->listSize();
+ if (len == 0) {
+ v = *args[1];
+ return;
+ }
+
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort");
+
state.mkList(v, len);
for (unsigned int n = 0; n < len; ++n) {
state.forceValue(*args[1]->listElems()[n], pos);
@@ -2962,12 +3071,12 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
/* Optimization: if the comparator is lessThan, bypass
callFunction. */
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
- return CompareValues(state)(a, b);
+ return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
Value * vs[] = {a, b};
Value vBool;
- state.callFunction(*args[0], 2, vs, vBool, pos);
- return state.forceBool(vBool, pos);
+ state.callFunction(*args[0], 2, vs, vBool, noPos);
+ return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
};
/* FIXME: std::sort can segfault if the comparator is not a strict
@@ -2997,10 +3106,10 @@ static RegisterPrimOp primop_sort({
.fun = prim_sort,
});
-static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_partition(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.partition");
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.partition");
auto len = args[1]->listSize();
@@ -3011,7 +3120,7 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V
state.forceValue(*vElem, pos);
Value res;
state.callFunction(*args[0], *vElem, res, pos);
- if (state.forceBool(res, pos))
+ if (state.forceBool(res, pos, "while evaluating the return value of the partition function passed to builtins.partition"))
right.push_back(vElem);
else
wrong.push_back(vElem);
@@ -3057,18 +3166,18 @@ static RegisterPrimOp primop_partition({
.fun = prim_partition,
});
-static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.groupBy");
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.groupBy");
ValueVectorMap attrs;
for (auto vElem : args[1]->listItems()) {
Value res;
state.callFunction(*args[0], *vElem, res, pos);
- auto name = state.forceStringNoCtx(res, pos);
- Symbol sym = state.symbols.create(name);
+ auto name = state.forceStringNoCtx(res, pos, "while evaluating the return value of the grouping function passed to builtins.groupBy");
+ auto sym = state.symbols.create(name);
auto vector = attrs.try_emplace(sym, ValueVector()).first;
vector->second.push_back(vElem);
}
@@ -3109,10 +3218,10 @@ static RegisterPrimOp primop_groupBy({
.fun = prim_groupBy,
});
-static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceFunction(*args[0], pos);
- state.forceList(*args[1], pos);
+ state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.concatMap");
+ state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.concatMap");
auto nrLists = args[1]->listSize();
Value lists[nrLists];
@@ -3122,10 +3231,10 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V
Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos);
try {
- state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)));
+ state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
} catch (TypeError &e) {
- e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap"));
- throw;
+ e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
+ state.debugThrowLastTrace(e);
}
len += lists[n].listSize();
}
@@ -3156,14 +3265,16 @@ static RegisterPrimOp primop_concatMap({
*************************************************************/
-static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
- v.mkFloat(state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos));
+ v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the addition")
+ + state.forceFloat(*args[1], pos, "while evaluating the second argument of the addition"));
else
- v.mkInt(state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
+ v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the addition")
+ + state.forceInt(*args[1], pos, "while evaluating the second argument of the addition"));
}
static RegisterPrimOp primop_add({
@@ -3175,14 +3286,16 @@ static RegisterPrimOp primop_add({
.fun = prim_add,
});
-static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
- v.mkFloat(state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos));
+ v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first argument of the subtraction")
+ - state.forceFloat(*args[1], pos, "while evaluating the second argument of the subtraction"));
else
- v.mkInt(state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
+ v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the subtraction")
+ - state.forceInt(*args[1], pos, "while evaluating the second argument of the subtraction"));
}
static RegisterPrimOp primop_sub({
@@ -3194,14 +3307,16 @@ static RegisterPrimOp primop_sub({
.fun = prim_sub,
});
-static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
- v.mkFloat(state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos));
+ v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first of the multiplication")
+ * state.forceFloat(*args[1], pos, "while evaluating the second argument of the multiplication"));
else
- v.mkInt(state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
+ v.mkInt( state.forceInt(*args[0], pos, "while evaluating the first argument of the multiplication")
+ * state.forceInt(*args[1], pos, "while evaluating the second argument of the multiplication"));
}
static RegisterPrimOp primop_mul({
@@ -3213,29 +3328,29 @@ static RegisterPrimOp primop_mul({
.fun = prim_mul,
});
-static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
- NixFloat f2 = state.forceFloat(*args[1], pos);
+ NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
if (f2 == 0)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("division by zero"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
- v.mkFloat(state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
+ v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
} else {
- NixInt i1 = state.forceInt(*args[0], pos);
- NixInt i2 = state.forceInt(*args[1], pos);
+ NixInt i1 = state.forceInt(*args[0], pos, "while evaluating the first 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. */
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("overflow in integer division"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
v.mkInt(i1 / i2);
}
@@ -3250,9 +3365,10 @@ static RegisterPrimOp primop_div({
.fun = prim_div,
});
-static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos));
+ v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd")
+ & state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitAnd"));
}
static RegisterPrimOp primop_bitAnd({
@@ -3264,9 +3380,10 @@ static RegisterPrimOp primop_bitAnd({
.fun = prim_bitAnd,
});
-static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_bitOr(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos));
+ v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitOr")
+ | state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitOr"));
}
static RegisterPrimOp primop_bitOr({
@@ -3278,9 +3395,10 @@ static RegisterPrimOp primop_bitOr({
.fun = prim_bitOr,
});
-static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_bitXor(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- v.mkInt(state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos));
+ v.mkInt(state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitXor")
+ ^ state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.bitXor"));
}
static RegisterPrimOp primop_bitXor({
@@ -3292,11 +3410,12 @@ static RegisterPrimOp primop_bitXor({
.fun = prim_bitXor,
});
-static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
- CompareValues comp{state};
+ // pos is exact here, no need for a message.
+ CompareValues comp(state, pos, "");
v.mkBool(comp(args[0], args[1]));
}
@@ -3320,10 +3439,10 @@ static RegisterPrimOp primop_lessThan({
/* Convert the argument to a string. Paths are *not* copied to the
store, so `toString /foo/bar' yields `"/foo/bar"', not
`"/nix/store/whatever..."'. */
-static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context, true, false);
+ auto s = state.coerceToString(pos, *args[0], context, true, false, "while evaluating the first argument passed to builtins.toString");
v.mkString(*s, context);
}
@@ -3355,18 +3474,18 @@ static RegisterPrimOp primop_toString({
at character position `min(start, stringLength str)' inclusive and
ending at `min(start + len, stringLength str)'. `start' must be
non-negative. */
-static void prim_substring(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- int start = state.forceInt(*args[0], pos);
- int len = state.forceInt(*args[1], pos);
+ int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
+ int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
PathSet context;
- auto s = state.coerceToString(pos, *args[2], context);
+ auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
if (start < 0)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("negative start position in 'substring'"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
}
@@ -3391,10 +3510,10 @@ static RegisterPrimOp primop_substring({
.fun = prim_substring,
});
-static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context);
+ auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
v.mkInt(s->size());
}
@@ -3409,18 +3528,18 @@ static RegisterPrimOp primop_stringLength({
});
/* Return the cryptographic hash of a string in base-16. */
-static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto type = state.forceStringNoCtx(*args[0], pos);
+ auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
std::optional<HashType> ht = parseHashType(type);
if (!ht)
- throw Error({
+ state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash type '%1%'", type),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
PathSet context; // discarded
- auto s = state.forceString(*args[1], context, pos);
+ auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
v.mkString(hashString(*ht, s).to_string(Base16, false));
}
@@ -3457,16 +3576,16 @@ std::shared_ptr<RegexCache> makeRegexCache()
return std::make_shared<RegexCache>();
}
-void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto re = state.forceStringNoCtx(*args[0], pos);
+ auto re = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.match");
try {
auto regex = state.regexCache->get(re);
PathSet context;
- const auto str = state.forceString(*args[1], context, pos);
+ const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match");
std::cmatch match;
if (!std::regex_match(str.begin(), str.end(), match, regex)) {
@@ -3484,19 +3603,18 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
(v.listElems()[i] = state.allocValue())->mkString(match[i + 1].str());
}
- } catch (std::regex_error &e) {
+ } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
- .errPos = pos
- });
- } else {
- throw EvalError({
+ .errPos = state.positions[pos]
+ }));
+ } else
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
- .errPos = pos
- });
- }
+ .errPos = state.positions[pos]
+ }));
}
}
@@ -3531,23 +3649,23 @@ static RegisterPrimOp primop_match({
builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " FOO "
```
- Evaluates to `[ "foo" ]`.
+ Evaluates to `[ "FOO" ]`.
)s",
.fun = prim_match,
});
/* Split a string with a regular expression, and return a list of the
non-matching parts interleaved by the lists of the matching groups. */
-void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto re = state.forceStringNoCtx(*args[0], pos);
+ auto re = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.split");
try {
auto regex = state.regexCache->get(re);
PathSet context;
- const auto str = state.forceString(*args[1], context, pos);
+ const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split");
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
auto end = std::cregex_iterator();
@@ -3589,19 +3707,18 @@ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v)
assert(idx == 2 * len + 1);
- } catch (std::regex_error &e) {
+ } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
- .errPos = pos
- });
- } else {
- throw EvalError({
+ .errPos = state.positions[pos]
+ }));
+ } else
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
- .errPos = pos
- });
- }
+ .errPos = state.positions[pos]
+ }));
}
}
@@ -3634,7 +3751,7 @@ static RegisterPrimOp primop_split({
Evaluates to `[ "" [ "a" null ] "b" [ null "c" ] "" ]`.
```nix
- builtins.split "([[:upper:]]+)" " FOO "
+ builtins.split "([[:upper:]]+)" " FOO "
```
Evaluates to `[ " " [ "FOO" ] " " ]`.
@@ -3642,12 +3759,12 @@ static RegisterPrimOp primop_split({
.fun = prim_split,
});
-static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto sep = state.forceString(*args[0], context, pos);
- state.forceList(*args[1], pos);
+ auto sep = state.forceString(*args[0], context, pos, "while evaluating the first argument (the separator string) passed to builtins.concatStringsSep");
+ state.forceList(*args[1], pos, "while evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep");
std::string res;
res.reserve((args[1]->listSize() + 32) * sep.size());
@@ -3655,7 +3772,7 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * *
for (auto elem : args[1]->listItems()) {
if (first) first = false; else res += sep;
- res += *state.coerceToString(pos, *elem, context);
+ res += *state.coerceToString(pos, *elem, context, "while evaluating one element of the list of strings to concat passed to builtins.concatStringsSep");
}
v.mkString(res, context);
@@ -3672,31 +3789,31 @@ static RegisterPrimOp primop_concatStringsSep({
.fun = prim_concatStringsSep,
});
-static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceList(*args[0], pos);
- state.forceList(*args[1], pos);
+ 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");
if (args[0]->listSize() != args[1]->listSize())
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
std::vector<std::string> from;
from.reserve(args[0]->listSize());
for (auto elem : args[0]->listItems())
- from.emplace_back(state.forceString(*elem, pos));
+ from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace in builtins.replaceStrings"));
std::vector<std::pair<std::string, PathSet>> to;
to.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) {
PathSet ctx;
- auto s = state.forceString(*elem, ctx, pos);
+ auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings of builtins.replaceStrings");
to.emplace_back(s, std::move(ctx));
}
PathSet context;
- auto s = state.forceString(*args[2], context, pos);
+ auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
std::string res;
// Loops one past last character to handle the case where 'from' contains an empty string.
@@ -3752,9 +3869,9 @@ static RegisterPrimOp primop_replaceStrings({
*************************************************************/
-static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_parseDrvName(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto name = state.forceStringNoCtx(*args[0], pos);
+ auto name = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.parseDrvName");
DrvName parsed(name);
auto attrs = state.buildBindings(2);
attrs.alloc(state.sName).mkString(parsed.name);
@@ -3767,8 +3884,8 @@ static RegisterPrimOp primop_parseDrvName({
.args = {"s"},
.doc = R"(
Split the string *s* into a package name and version. The package
- name is everything up to but not including the first dash followed
- by a digit, and the version is everything following that dash. The
+ name is everything up to but not including the first dash not followed
+ by a letter, and the version is everything following that dash. The
result is returned in a set `{ name, version }`. Thus,
`builtins.parseDrvName "nix-0.12pre12876"` returns `{ name =
"nix"; version = "0.12pre12876"; }`.
@@ -3776,10 +3893,10 @@ static RegisterPrimOp primop_parseDrvName({
.fun = prim_parseDrvName,
});
-static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_compareVersions(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto version1 = state.forceStringNoCtx(*args[0], pos);
- auto version2 = state.forceStringNoCtx(*args[1], pos);
+ auto version1 = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.compareVersions");
+ auto version2 = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.compareVersions");
v.mkInt(compareVersions(version1, version2));
}
@@ -3796,9 +3913,9 @@ static RegisterPrimOp primop_compareVersions({
.fun = prim_compareVersions,
});
-static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_splitVersion(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- auto version = state.forceStringNoCtx(*args[0], pos);
+ auto version = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.splitVersion");
auto iter = version.cbegin();
Strings components;
while (iter != version.cend()) {
@@ -3898,6 +4015,18 @@ void EvalState::createBaseEnv()
addPrimOp("__exec", 1, prim_exec);
}
+ addPrimOp({
+ .fun = evalSettings.traceVerbose ? prim_trace : prim_second,
+ .arity = 2,
+ .name = "__traceVerbose",
+ .args = { "e1", "e2" },
+ .doc = R"(
+ Evaluate *e1* and print its abstract syntax representation on standard
+ error if `--trace-verbose` is enabled. Then return *e2*. This function
+ is useful for debugging.
+ )",
+ });
+
/* Add a value containing the current Nix expression search path. */
mkList(v, searchPath.size());
int n = 0;
@@ -3917,7 +4046,7 @@ void EvalState::createBaseEnv()
addPrimOp({
.fun = primOp.fun,
.arity = std::max(primOp.args.size(), primOp.arity),
- .name = symbols.create(primOp.name),
+ .name = primOp.name,
.args = primOp.args,
.doc = primOp.doc,
});
@@ -3925,7 +4054,7 @@ void EvalState::createBaseEnv()
/* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */
- sDerivationNix = symbols.create("//builtin/derivation.nix");
+ sDerivationNix = symbols.create(derivationNixPath);
auto vDerivation = allocValue();
addConstant("derivation", vDerivation);
@@ -3933,7 +4062,7 @@ void EvalState::createBaseEnv()
because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort();
- staticBaseEnv.sort();
+ staticBaseEnv->sort();
/* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */
@@ -3942,7 +4071,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
- eval(parse(code, sizeof(code), foFile, sDerivationNix, "/", staticBaseEnv), *vDerivation);
+ eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
}
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index 905bd0366..1cfb4356b 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -38,9 +38,9 @@ struct RegisterPrimOp
them. */
/* Load a ValueInitializer from a DSO and return whatever it initializes */
-void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
+void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v);
/* Execute a program and parse its output */
-void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
+void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v);
}
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index cc74c7f58..9fae0b14d 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -5,20 +5,20 @@
namespace nix {
-static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context);
+ auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s);
}
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
-static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- state.forceString(*args[0], context, pos);
+ state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
v.mkBool(!context.empty());
}
@@ -31,10 +31,10 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
source-only deployment). This primop marks the string context so
that builtins.derivation adds the path to drv.inputSrcs rather than
drv.inputDrvs. */
-static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto s = state.coerceToString(pos, *args[0], context);
+ auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
PathSet context2;
for (auto & p : context)
@@ -65,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu
Note that for a given path any combination of the above attributes
may be present.
*/
-static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
struct ContextInfo {
bool path = false;
@@ -73,7 +73,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
Strings outputs;
};
PathSet context;
- state.forceString(*args[0], context, pos);
+ state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<Path, ContextInfo>();
for (const auto & p : context) {
Path drv;
@@ -134,55 +134,56 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
See the commentary above unsafeGetContext for details of the
context representation.
*/
-static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
- auto orig = state.forceString(*args[0], context, pos);
+ auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
- state.forceAttrs(*args[1], pos);
+ state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) {
- if (!state.store->isStorePath(i.name))
+ const auto & name = state.symbols[i.name];
+ if (!state.store->isStorePath(name))
throw EvalError({
- .msg = hintfmt("Context key '%s' is not a store path", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("context key '%s' is not a store path", name),
+ .errPos = state.positions[i.pos]
});
if (!settings.readOnlyMode)
- state.store->ensurePath(state.store->parseStorePath(i.name));
- state.forceAttrs(*i.value, *i.pos);
+ state.store->ensurePath(state.store->parseStorePath(name));
+ state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) {
- if (state.forceBool(*iter->value, *iter->pos))
- context.insert(i.name);
+ if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
+ context.emplace(name);
}
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
- if (state.forceBool(*iter->value, *iter->pos)) {
- if (!isDerivation(i.name)) {
+ if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
+ if (!isDerivation(name)) {
throw EvalError({
- .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
+ .errPos = state.positions[i.pos]
});
}
- context.insert("=" + std::string(i.name));
+ context.insert(concatStrings("=", name));
}
}
iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) {
- state.forceList(*iter->value, *iter->pos);
- if (iter->value->listSize() && !isDerivation(i.name)) {
+ state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
+ if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({
- .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
+ .errPos = state.positions[i.pos]
});
}
for (auto elem : iter->value->listItems()) {
- auto name = state.forceStringNoCtx(*elem, *iter->pos);
- context.insert(concatStrings("!", name, "!", i.name));
+ auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
+ context.insert(concatStrings("!", outputName, "!", name));
}
}
}
diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc
index 821eba698..0dfa97fa3 100644
--- a/src/libexpr/primops/fetchClosure.cc
+++ b/src/libexpr/primops/fetchClosure.cc
@@ -5,9 +5,9 @@
namespace nix {
-static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
@@ -15,40 +15,45 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
std::optional<StorePath> toPath;
for (auto & attr : *args[0]->attrs) {
- if (attr.name == "fromPath") {
+ const auto & attrName = state.symbols[attr.name];
+
+ if (attrName == "fromPath") {
PathSet context;
- fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
+ fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
+ "while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
}
- else if (attr.name == "toPath") {
- state.forceValue(*attr.value, *attr.pos);
+ else if (attrName == "toPath") {
+ state.forceValue(*attr.value, attr.pos);
toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context;
- toPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
+ toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
+ "while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
}
}
- else if (attr.name == "fromStore")
- fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos);
+ else if (attrName == "fromStore")
+ fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
+ "while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
else
throw Error({
- .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name),
- .errPos = pos
+ .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
+ .errPos = state.positions[pos]
});
}
if (!fromPath)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
if (!fromStoreUrl)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
auto parsedURL = parseURL(*fromStoreUrl);
@@ -58,13 +63,13 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
if (!parsedURL.query.empty())
throw Error({
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
- .errPos = pos
+ .errPos = state.positions[pos]
});
auto fromStore = openStore(parsedURL.to_string());
@@ -80,7 +85,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second),
state.store->printStorePath(*toPath)),
- .errPos = pos
+ .errPos = state.positions[pos]
});
if (!toPath)
throw Error({
@@ -89,7 +94,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
"please set this in the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second)),
- .errPos = pos
+ .errPos = state.positions[pos]
});
}
} else {
@@ -105,7 +110,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
throw Error({
.msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
state.store->printStorePath(*toPath)),
- .errPos = pos
+ .errPos = state.positions[pos]
});
}
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index b7f715859..c9c93bdba 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -7,7 +7,7 @@
namespace nix {
-static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::string url;
std::optional<Hash> rev;
@@ -19,38 +19,36 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
if (args[0]->type() == nAttrs) {
- state.forceAttrs(*args[0], pos);
-
for (auto & attr : *args[0]->attrs) {
- std::string_view n(attr.name);
+ std::string_view n(state.symbols[attr.name]);
if (n == "url")
- url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
+ url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
- auto value = state.forceStringNoCtx(*attr.value, *attr.pos);
+ auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1);
else
ref = value;
}
else if (n == "name")
- name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
throw EvalError({
- .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
- .errPos = *attr.pos
+ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
+ .errPos = state.positions[attr.pos]
});
}
if (url.empty())
throw EvalError({
.msg = hintfmt("'url' argument required"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
} else
- url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
+ url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned();
// FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well.
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index c12872084..4181f0b7d 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -90,7 +90,7 @@ struct FetchTreeParams {
static void fetchTree(
EvalState & state,
- const Pos & pos,
+ const PosIdx pos,
Value * * args,
Value & v,
std::optional<std::string> type,
@@ -102,56 +102,56 @@ static void fetchTree(
state.forceValue(*args[0], pos);
if (args[0]->type() == nAttrs) {
- state.forceAttrs(*args[0], pos);
+ state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
fetchers::Attrs attrs;
if (auto aType = args[0]->attrs->get(state.sType)) {
if (type)
- throw Error({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unexpected attribute 'type'"),
- .errPos = pos
- });
- type = state.forceStringNoCtx(*aType->value, *aType->pos);
+ .errPos = state.positions[pos]
+ }));
+ type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type)
- throw Error({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) {
if (attr.name == state.sType) continue;
- state.forceValue(*attr.value, *attr.pos);
+ state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
- auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
- attrs.emplace(attr.name,
- attr.name == "url"
+ auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
+ attrs.emplace(state.symbols[attr.name],
+ state.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
: s);
}
else if (attr.value->type() == nBool)
- attrs.emplace(attr.name, Explicit<bool>{attr.value->boolean});
+ attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt)
- attrs.emplace(attr.name, uint64_t(attr.value->integer));
+ attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
else
- throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
- attr.name, showType(*attr.value));
+ state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
+ state.symbols[attr.name], showType(*attr.value)));
}
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
- throw Error({
- .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
- .errPos = pos
- });
+ state.debugThrowLastTrace(EvalError({
+ .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
+ .errPos = state.positions[pos]
+ }));
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
- auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
+ auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
if (type == "git") {
fetchers::Attrs attrs;
@@ -167,7 +167,7 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked())
- throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos);
+ state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
auto [tree, input2] = input.fetch(state.store);
@@ -176,7 +176,7 @@ static void fetchTree(
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
}
-static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
settings.requireExperimentalFeature(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
@@ -185,7 +185,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
// FIXME: document
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
-static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
+static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
const std::string & who, bool unpack, std::string name)
{
std::optional<std::string> url;
@@ -195,32 +195,28 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
if (args[0]->type() == nAttrs) {
- state.forceAttrs(*args[0], pos);
-
for (auto & attr : *args[0]->attrs) {
- std::string n(attr.name);
+ std::string_view n(state.symbols[attr.name]);
if (n == "url")
- url = state.forceStringNoCtx(*attr.value, *attr.pos);
+ url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
- expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+ expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
else if (n == "name")
- name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
- throw EvalError({
- .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
- .errPos = *attr.pos
- });
- }
+ state.debugThrowLastTrace(EvalError({
+ .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
+ .errPos = state.positions[attr.pos]
+ }));
+ }
if (!url)
- throw EvalError({
+ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'url' argument required"),
- .errPos = pos
- });
+ .errPos = state.positions[pos]
+ }));
} else
- url = state.forceStringNoCtx(*args[0], pos);
-
- url = resolveUri(*url);
+ url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
state.checkURI(*url);
@@ -228,7 +224,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash)
- throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who);
+ state.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
// early exit if pinned and already in the store
if (expectedHash && expectedHash->type == htSHA256) {
@@ -260,14 +256,14 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
? state.store->queryPathInfo(storePath)->narHash
: hashFile(htSHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash)
- throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
- *url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true));
+ state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
+ *url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
}
state.allowAndSetStorePathString(storePath, v);
}
-static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetch(state, pos, args, v, "fetchurl", false, "");
}
@@ -283,7 +279,7 @@ static RegisterPrimOp primop_fetchurl({
.fun = prim_fetchurl,
});
-static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetch(state, pos, args, v, "fetchTarball", true, "source");
}
@@ -334,7 +330,7 @@ static RegisterPrimOp primop_fetchTarball({
.fun = prim_fetchTarball,
});
-static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
}
@@ -369,6 +365,10 @@ static RegisterPrimOp primop_fetchGit({
A Boolean parameter that specifies whether submodules should be
checked out. Defaults to `false`.
+ - shallow\
+ A Boolean parameter that specifies whether fetching a shallow clone
+ is allowed. Defaults to `false`.
+
- allRefs\
Whether to fetch all refs of the repository. With this argument being
true, it's possible to load a `rev` from *any* `ref` (by default only
diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc
index dd4280030..8a5231781 100644
--- a/src/libexpr/primops/fromTOML.cc
+++ b/src/libexpr/primops/fromTOML.cc
@@ -5,9 +5,9 @@
namespace nix {
-static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val)
+static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
{
- auto toml = state.forceStringNoCtx(*args[0], pos);
+ auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
std::istringstream tomlStream(std::string{toml});
@@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
} catch (std::exception & e) { // TODO: toml::syntax_error
throw EvalError({
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
- .errPos = pos
+ .errPos = state.positions[pos]
});
}
}
diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh
index 48d20c29d..288c15602 100644
--- a/src/libexpr/symbol-table.hh
+++ b/src/libexpr/symbol-table.hh
@@ -5,44 +5,32 @@
#include <unordered_map>
#include "types.hh"
+#include "chunked-vector.hh"
namespace nix {
/* Symbol table used by the parser and evaluator to represent and look
up identifiers and attributes efficiently. SymbolTable::create()
converts a string into a symbol. Symbols have the property that
- they can be compared efficiently (using a pointer equality test),
+ they can be compared efficiently (using an equality test),
because the symbol table stores only one copy of each string. */
-class Symbol
+/* This class mainly exists to give us an operator<< for ostreams. We could also
+ return plain strings from SymbolTable, but then we'd have to wrap every
+ instance of a symbol that is fmt()ed, which is inconvenient and error-prone. */
+class SymbolStr
{
-private:
- const std::string * s; // pointer into SymbolTable
- Symbol(const std::string * s) : s(s) { };
friend class SymbolTable;
-public:
- Symbol() : s(0) { };
+private:
+ const std::string * s;
- bool operator == (const Symbol & s2) const
- {
- return s == s2.s;
- }
+ explicit SymbolStr(const std::string & symbol): s(&symbol) {}
- // FIXME: remove
+public:
bool operator == (std::string_view s2) const
{
- return s->compare(s2) == 0;
- }
-
- bool operator != (const Symbol & s2) const
- {
- return s != s2.s;
- }
-
- bool operator < (const Symbol & s2) const
- {
- return s < s2.s;
+ return *s == s2;
}
operator const std::string & () const
@@ -55,51 +43,78 @@ public:
return *s;
}
- bool set() const
- {
- return s;
- }
+ friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
+};
- bool empty() const
- {
- return s->empty();
- }
+class Symbol
+{
+ friend class SymbolTable;
+
+private:
+ uint32_t id;
+
+ explicit Symbol(uint32_t id): id(id) {}
+
+public:
+ Symbol() : id(0) {}
- friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
+ explicit operator bool() const { return id > 0; }
+
+ bool operator<(const Symbol other) const { return id < other.id; }
+ bool operator==(const Symbol other) const { return id == other.id; }
+ bool operator!=(const Symbol other) const { return id != other.id; }
};
class SymbolTable
{
private:
- std::unordered_map<std::string_view, Symbol> symbols;
- std::list<std::string> store;
+ std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols;
+ ChunkedVector<std::string, 8192> store{16};
public:
+
Symbol create(std::string_view s)
{
// Most symbols are looked up more than once, so we trade off insertion performance
// for lookup performance.
// TODO: could probably be done more efficiently with transparent Hash and Equals
// on the original implementation using unordered_set
+ // FIXME: make this thread-safe.
auto it = symbols.find(s);
- if (it != symbols.end()) return it->second;
+ if (it != symbols.end()) return Symbol(it->second.second + 1);
+
+ const auto & [rawSym, idx] = store.add(std::string(s));
+ symbols.emplace(rawSym, std::make_pair(&rawSym, idx));
+ return Symbol(idx + 1);
+ }
- auto & rawSym = store.emplace_back(s);
- return symbols.emplace(rawSym, Symbol(&rawSym)).first->second;
+ std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const
+ {
+ std::vector<SymbolStr> result;
+ result.reserve(symbols.size());
+ for (auto sym : symbols)
+ result.push_back((*this)[sym]);
+ return result;
+ }
+
+ SymbolStr operator[](Symbol s) const
+ {
+ if (s.id == 0 || s.id > store.size())
+ abort();
+ return SymbolStr(store[s.id - 1]);
}
size_t size() const
{
- return symbols.size();
+ return store.size();
}
size_t totalSize() const;
template<typename T>
- void dump(T callback)
+ void dump(T callback) const
{
- for (auto & s : store)
- callback(s);
+ store.forEach(callback);
}
};
diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc
new file mode 100644
index 000000000..8741ecdd2
--- /dev/null
+++ b/src/libexpr/tests/error_traces.cc
@@ -0,0 +1,94 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "libexprtests.hh"
+
+namespace nix {
+
+ using namespace testing;
+
+ // Testing eval of PrimOp's
+ class ErrorTraceTest : public LibExprTest { };
+
+#define ASSERT_TRACE1(args, type, message) \
+ ASSERT_THROW( \
+ try { \
+ eval("builtins." args); \
+ } catch (BaseError & e) { \
+ ASSERT_EQ(PrintToString(e.info().msg), \
+ PrintToString(message)); \
+ auto trace = e.info().traces.rbegin(); \
+ ASSERT_EQ(PrintToString(trace->hint), \
+ PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
+ throw; \
+ } \
+ , type \
+ )
+
+#define ASSERT_TRACE2(args, type, message, context) \
+ ASSERT_THROW( \
+ try { \
+ eval("builtins." args); \
+ } catch (BaseError & e) { \
+ ASSERT_EQ(PrintToString(e.info().msg), \
+ PrintToString(message)); \
+ auto trace = e.info().traces.rbegin(); \
+ ASSERT_EQ(PrintToString(trace->hint), \
+ PrintToString(context)); \
+ ++trace; \
+ ASSERT_EQ(PrintToString(trace->hint), \
+ PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
+ throw; \
+ } \
+ , type \
+ )
+
+ TEST_F(ErrorTraceTest, genericClosure) { \
+ ASSERT_TRACE2("genericClosure 1",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "an integer"),
+ hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
+
+ ASSERT_TRACE1("genericClosure {}",
+ TypeError,
+ hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
+
+ ASSERT_TRACE2("genericClosure { startSet = 1; }",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "an integer"),
+ hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
+
+ // Okay: "genericClosure { startSet = []; }"
+
+ ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
+ TypeError,
+ hintfmt("value is %s while a function was expected", "a Boolean"),
+ hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
+
+ ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
+ TypeError,
+ hintfmt("value is %s while a list was expected", "a Boolean"),
+ hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
+
+ ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a Boolean"),
+ hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
+
+ ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
+ TypeError,
+ hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
+
+ ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
+ EvalError,
+ hintfmt("cannot compare %s with %s", "a string", "an integer"),
+ hintfmt("while comparing the `key` attributes of two genericClosure elements"));
+
+ ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
+ TypeError,
+ hintfmt("value is %s while a set was expected", "a Boolean"),
+ hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
+
+ }
+
+} /* namespace nix */
diff --git a/src/libexpr/tests/json.cc b/src/libexpr/tests/json.cc
new file mode 100644
index 000000000..f1ea1b197
--- /dev/null
+++ b/src/libexpr/tests/json.cc
@@ -0,0 +1,68 @@
+#include "libexprtests.hh"
+#include "value-to-json.hh"
+
+namespace nix {
+// Testing the conversion to JSON
+
+ class JSONValueTest : public LibExprTest {
+ protected:
+ std::string getJSONValue(Value& value) {
+ std::stringstream ss;
+ PathSet ps;
+ printValueAsJSON(state, true, value, noPos, ss, ps);
+ return ss.str();
+ }
+ };
+
+ TEST_F(JSONValueTest, null) {
+ Value v;
+ v.mkNull();
+ ASSERT_EQ(getJSONValue(v), "null");
+ }
+
+ TEST_F(JSONValueTest, BoolFalse) {
+ Value v;
+ v.mkBool(false);
+ ASSERT_EQ(getJSONValue(v),"false");
+ }
+
+ TEST_F(JSONValueTest, BoolTrue) {
+ Value v;
+ v.mkBool(true);
+ ASSERT_EQ(getJSONValue(v), "true");
+ }
+
+ TEST_F(JSONValueTest, IntPositive) {
+ Value v;
+ v.mkInt(100);
+ ASSERT_EQ(getJSONValue(v), "100");
+ }
+
+ TEST_F(JSONValueTest, IntNegative) {
+ Value v;
+ v.mkInt(-100);
+ ASSERT_EQ(getJSONValue(v), "-100");
+ }
+
+ TEST_F(JSONValueTest, String) {
+ Value v;
+ v.mkString("test");
+ ASSERT_EQ(getJSONValue(v), "\"test\"");
+ }
+
+ TEST_F(JSONValueTest, StringQuotes) {
+ Value v;
+
+ v.mkString("test\"");
+ ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
+ }
+
+ // The dummy store doesn't support writing files. Fails with this exception message:
+ // C++ exception with description "error: operation 'addToStoreFromDump' is
+ // not supported by store 'dummy'" thrown in the test body.
+ TEST_F(JSONValueTest, DISABLED_Path) {
+ Value v;
+ v.mkPath("test");
+ ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
+ }
+} /* namespace nix */
diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh
new file mode 100644
index 000000000..03e468fbb
--- /dev/null
+++ b/src/libexpr/tests/libexprtests.hh
@@ -0,0 +1,137 @@
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "value.hh"
+#include "nixexpr.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "store-api.hh"
+
+
+namespace nix {
+ class LibExprTest : public ::testing::Test {
+ public:
+ static void SetUpTestSuite() {
+ initLibStore();
+ initGC();
+ }
+
+ protected:
+ LibExprTest()
+ : store(openStore("dummy://"))
+ , state({}, store)
+ {
+ }
+ Value eval(std::string input, bool forceValue = true) {
+ Value v;
+ Expr * e = state.parseExprFromString(input, "");
+ assert(e);
+ state.eval(e, v);
+ if (forceValue)
+ state.forceValue(v, noPos);
+ return v;
+ }
+
+ Symbol createSymbol(const char * value) {
+ return state.symbols.create(value);
+ }
+
+ ref<Store> store;
+ EvalState state;
+ };
+
+ MATCHER(IsListType, "") {
+ return arg != nList;
+ }
+
+ MATCHER(IsList, "") {
+ return arg.type() == nList;
+ }
+
+ MATCHER(IsString, "") {
+ return arg.type() == nString;
+ }
+
+ MATCHER(IsNull, "") {
+ return arg.type() == nNull;
+ }
+
+ MATCHER(IsThunk, "") {
+ return arg.type() == nThunk;
+ }
+
+ MATCHER(IsAttrs, "") {
+ return arg.type() == nAttrs;
+ }
+
+ MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) {
+ if (arg.type() != nString) {
+ return false;
+ }
+ return std::string_view(arg.string.s) == s;
+ }
+
+ MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
+ if (arg.type() != nInt) {
+ return false;
+ }
+ return arg.integer == v;
+ }
+
+ MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
+ if (arg.type() != nFloat) {
+ return false;
+ }
+ return arg.fpoint == v;
+ }
+
+ MATCHER(IsTrue, "") {
+ if (arg.type() != nBool) {
+ return false;
+ }
+ return arg.boolean == true;
+ }
+
+ MATCHER(IsFalse, "") {
+ if (arg.type() != nBool) {
+ return false;
+ }
+ return arg.boolean == false;
+ }
+
+ MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
+ if (arg.type() != nPath) {
+ *result_listener << "Expected a path got " << arg.type();
+ return false;
+ } else if (std::string_view(arg.string.s) != p) {
+ *result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s;
+ return false;
+ }
+ return true;
+ }
+
+
+ MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) {
+ if (arg.type() != nList) {
+ *result_listener << "Expected list got " << arg.type();
+ return false;
+ } else if (arg.listSize() != (size_t)n) {
+ *result_listener << "Expected as list of size " << n << " got " << arg.listSize();
+ return false;
+ }
+ return true;
+ }
+
+ MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
+ if (arg.type() != nAttrs) {
+ *result_listener << "Expected set got " << arg.type();
+ return false;
+ } else if (arg.attrs->size() != (size_t)n) {
+ *result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
+ return false;
+ }
+ return true;
+ }
+
+
+} /* namespace nix */
diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk
new file mode 100644
index 000000000..b95980cab
--- /dev/null
+++ b/src/libexpr/tests/local.mk
@@ -0,0 +1,15 @@
+check: libexpr-tests_RUN
+
+programs += libexpr-tests
+
+libexpr-tests_DIR := $(d)
+
+libexpr-tests_INSTALL_DIR :=
+
+libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
+
+libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
+
+libexpr-tests_LIBS = libexpr libutil libstore libfetchers
+
+libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock
diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc
new file mode 100644
index 000000000..9cdcf64a1
--- /dev/null
+++ b/src/libexpr/tests/primops.cc
@@ -0,0 +1,832 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "libexprtests.hh"
+
+namespace nix {
+ class CaptureLogger : public Logger
+ {
+ std::ostringstream oss;
+
+ public:
+ CaptureLogger() {}
+
+ std::string get() const {
+ return oss.str();
+ }
+
+ void log(Verbosity lvl, const FormatOrString & fs) override {
+ oss << fs.s << std::endl;
+ }
+
+ void logEI(const ErrorInfo & ei) override {
+ showErrorInfo(oss, ei, loggerSettings.showTrace.get());
+ }
+ };
+
+ class CaptureLogging {
+ Logger * oldLogger;
+ std::unique_ptr<CaptureLogger> tempLogger;
+ public:
+ CaptureLogging() : tempLogger(std::make_unique<CaptureLogger>()) {
+ oldLogger = logger;
+ logger = tempLogger.get();
+ }
+
+ ~CaptureLogging() {
+ logger = oldLogger;
+ }
+
+ std::string get() const {
+ return tempLogger->get();
+ }
+ };
+
+
+ // Testing eval of PrimOp's
+ class PrimOpTest : public LibExprTest {};
+
+
+ TEST_F(PrimOpTest, throw) {
+ ASSERT_THROW(eval("throw \"foo\""), ThrownError);
+ }
+
+ TEST_F(PrimOpTest, abort) {
+ ASSERT_THROW(eval("abort \"abort\""), Abort);
+ }
+
+ TEST_F(PrimOpTest, ceil) {
+ auto v = eval("builtins.ceil 1.9");
+ ASSERT_THAT(v, IsIntEq(2));
+ }
+
+ TEST_F(PrimOpTest, floor) {
+ auto v = eval("builtins.floor 1.9");
+ ASSERT_THAT(v, IsIntEq(1));
+ }
+
+ TEST_F(PrimOpTest, tryEvalFailure) {
+ auto v = eval("builtins.tryEval (throw \"\")");
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+ auto s = createSymbol("success");
+ auto p = v.attrs->get(s);
+ ASSERT_NE(p, nullptr);
+ ASSERT_THAT(*p->value, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, tryEvalSuccess) {
+ auto v = eval("builtins.tryEval 123");
+ ASSERT_THAT(v, IsAttrs());
+ auto s = createSymbol("success");
+ auto p = v.attrs->get(s);
+ ASSERT_NE(p, nullptr);
+ ASSERT_THAT(*p->value, IsTrue());
+ s = createSymbol("value");
+ p = v.attrs->get(s);
+ ASSERT_NE(p, nullptr);
+ ASSERT_THAT(*p->value, IsIntEq(123));
+ }
+
+ TEST_F(PrimOpTest, getEnv) {
+ setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1);
+ auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\"");
+ ASSERT_THAT(v, IsStringEq("test value"));
+ }
+
+ TEST_F(PrimOpTest, seq) {
+ ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError);
+ }
+
+ TEST_F(PrimOpTest, seqNotDeep) {
+ auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }");
+ ASSERT_THAT(v, IsAttrs());
+ }
+
+ TEST_F(PrimOpTest, deepSeq) {
+ ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError);
+ }
+
+ TEST_F(PrimOpTest, trace) {
+ CaptureLogging l;
+ auto v = eval("builtins.trace \"test string 123\" 123");
+ ASSERT_THAT(v, IsIntEq(123));
+ auto text = l.get();
+ ASSERT_NE(text.find("test string 123"), std::string::npos);
+ }
+
+ TEST_F(PrimOpTest, placeholder) {
+ auto v = eval("builtins.placeholder \"out\"");
+ ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"));
+ }
+
+ TEST_F(PrimOpTest, baseNameOf) {
+ auto v = eval("builtins.baseNameOf /some/path");
+ ASSERT_THAT(v, IsStringEq("path"));
+ }
+
+ TEST_F(PrimOpTest, dirOf) {
+ auto v = eval("builtins.dirOf /some/path");
+ ASSERT_THAT(v, IsPathEq("/some"));
+ }
+
+ TEST_F(PrimOpTest, attrValues) {
+ auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }");
+ ASSERT_THAT(v, IsListOfSize(2));
+ ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
+ ASSERT_THAT(*v.listElems()[1], IsStringEq("foo"));
+ }
+
+ TEST_F(PrimOpTest, getAttr) {
+ auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }");
+ ASSERT_THAT(v, IsStringEq("foo"));
+ }
+
+ TEST_F(PrimOpTest, getAttrNotFound) {
+ // FIXME: TypeError is really bad here, also the error wording is worse
+ // than on Nix <=2.3
+ ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError);
+ }
+
+ TEST_F(PrimOpTest, unsafeGetAttrPos) {
+ // The `y` attribute is at position
+ const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
+ auto v = eval(expr);
+ ASSERT_THAT(v, IsNull());
+ }
+
+ TEST_F(PrimOpTest, hasAttr) {
+ auto v = eval("builtins.hasAttr \"x\" { x = 1; }");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, hasAttrNotFound) {
+ auto v = eval("builtins.hasAttr \"x\" { }");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, isAttrs) {
+ auto v = eval("builtins.isAttrs {}");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, isAttrsFalse) {
+ auto v = eval("builtins.isAttrs null");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, removeAttrs) {
+ auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]");
+ ASSERT_THAT(v, IsAttrsOfSize(0));
+ }
+
+ TEST_F(PrimOpTest, removeAttrsRetains) {
+ auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
+ ASSERT_THAT(v, IsAttrsOfSize(1));
+ ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr);
+ }
+
+ TEST_F(PrimOpTest, listToAttrsEmptyList) {
+ auto v = eval("builtins.listToAttrs []");
+ ASSERT_THAT(v, IsAttrsOfSize(0));
+ ASSERT_EQ(v.type(), nAttrs);
+ ASSERT_EQ(v.attrs->size(), 0);
+ }
+
+ TEST_F(PrimOpTest, listToAttrsNotFieldName) {
+ ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error);
+ }
+
+ TEST_F(PrimOpTest, listToAttrs) {
+ auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
+ ASSERT_THAT(v, IsAttrsOfSize(1));
+ auto key = v.attrs->find(createSymbol("key"));
+ ASSERT_NE(key, nullptr);
+ ASSERT_THAT(*key->value, IsIntEq(123));
+ }
+
+ TEST_F(PrimOpTest, intersectAttrs) {
+ auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
+ ASSERT_THAT(v, IsAttrsOfSize(1));
+ auto b = v.attrs->find(createSymbol("b"));
+ ASSERT_NE(b, nullptr);
+ ASSERT_THAT(*b->value, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, catAttrs) {
+ auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]");
+ ASSERT_THAT(v, IsListOfSize(2));
+ ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
+ ASSERT_THAT(*v.listElems()[1], IsIntEq(2));
+ }
+
+ TEST_F(PrimOpTest, functionArgs) {
+ auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+
+ auto x = v.attrs->find(createSymbol("x"));
+ ASSERT_NE(x, nullptr);
+ ASSERT_THAT(*x->value, IsFalse());
+
+ auto y = v.attrs->find(createSymbol("y"));
+ ASSERT_NE(y, nullptr);
+ ASSERT_THAT(*y->value, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, mapAttrs) {
+ auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+
+ auto a = v.attrs->find(createSymbol("a"));
+ ASSERT_NE(a, nullptr);
+ ASSERT_THAT(*a->value, IsThunk());
+ state.forceValue(*a->value, noPos);
+ ASSERT_THAT(*a->value, IsIntEq(10));
+
+ auto b = v.attrs->find(createSymbol("b"));
+ ASSERT_NE(b, nullptr);
+ ASSERT_THAT(*b->value, IsThunk());
+ state.forceValue(*b->value, noPos);
+ ASSERT_THAT(*b->value, IsIntEq(20));
+ }
+
+ TEST_F(PrimOpTest, isList) {
+ auto v = eval("builtins.isList []");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, isListFalse) {
+ auto v = eval("builtins.isList null");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, elemtAt) {
+ auto v = eval("builtins.elemAt [0 1 2 3] 3");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, elemtAtOutOfBounds) {
+ ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error);
+ }
+
+ TEST_F(PrimOpTest, head) {
+ auto v = eval("builtins.head [ 3 2 1 0 ]");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, headEmpty) {
+ ASSERT_THROW(eval("builtins.head [ ]"), Error);
+ }
+
+ TEST_F(PrimOpTest, headWrongType) {
+ ASSERT_THROW(eval("builtins.head { }"), Error);
+ }
+
+ TEST_F(PrimOpTest, tail) {
+ auto v = eval("builtins.tail [ 3 2 1 0 ]");
+ ASSERT_THAT(v, IsListOfSize(3));
+ for (const auto [n, elem] : enumerate(v.listItems()))
+ ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n)));
+ }
+
+ TEST_F(PrimOpTest, tailEmpty) {
+ ASSERT_THROW(eval("builtins.tail []"), Error);
+ }
+
+ TEST_F(PrimOpTest, map) {
+ auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]");
+ ASSERT_THAT(v, IsListOfSize(3));
+ auto elem = v.listElems()[0];
+ ASSERT_THAT(*elem, IsThunk());
+ state.forceValue(*elem, noPos);
+ ASSERT_THAT(*elem, IsStringEq("foobar"));
+
+ elem = v.listElems()[1];
+ ASSERT_THAT(*elem, IsThunk());
+ state.forceValue(*elem, noPos);
+ ASSERT_THAT(*elem, IsStringEq("foobla"));
+
+ elem = v.listElems()[2];
+ ASSERT_THAT(*elem, IsThunk());
+ state.forceValue(*elem, noPos);
+ ASSERT_THAT(*elem, IsStringEq("fooabc"));
+ }
+
+ TEST_F(PrimOpTest, filter) {
+ auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]");
+ ASSERT_THAT(v, IsListOfSize(3));
+ for (const auto elem : v.listItems())
+ ASSERT_THAT(*elem, IsIntEq(2));
+ }
+
+ TEST_F(PrimOpTest, elemTrue) {
+ auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, elemFalse) {
+ auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, concatLists) {
+ auto v = eval("builtins.concatLists [[1 2] [3 4]]");
+ ASSERT_THAT(v, IsListOfSize(4));
+ for (const auto [i, elem] : enumerate(v.listItems()))
+ ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
+ }
+
+ TEST_F(PrimOpTest, length) {
+ auto v = eval("builtins.length [ 1 2 3 ]");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, foldStrict) {
+ auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]");
+ ASSERT_THAT(v, IsIntEq(6));
+ }
+
+ TEST_F(PrimOpTest, anyTrue) {
+ auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, anyFalse) {
+ auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, allTrue) {
+ auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, allFalse) {
+ auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, genList) {
+ auto v = eval("builtins.genList (x: x + 1) 3");
+ ASSERT_EQ(v.type(), nList);
+ ASSERT_EQ(v.listSize(), 3);
+ for (const auto [i, elem] : enumerate(v.listItems())) {
+ ASSERT_THAT(*elem, IsThunk());
+ state.forceValue(*elem, noPos);
+ ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
+ }
+ }
+
+ TEST_F(PrimOpTest, sortLessThan) {
+ auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]");
+ ASSERT_EQ(v.type(), nList);
+ ASSERT_EQ(v.listSize(), 6);
+
+ const std::vector<int> numbers = { 42, 77, 147, 249, 483, 526 };
+ for (const auto [n, elem] : enumerate(v.listItems()))
+ ASSERT_THAT(*elem, IsIntEq(numbers[n]));
+ }
+
+ TEST_F(PrimOpTest, partition) {
+ auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+
+ auto right = v.attrs->get(createSymbol("right"));
+ ASSERT_NE(right, nullptr);
+ ASSERT_THAT(*right->value, IsListOfSize(2));
+ ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23));
+ ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42));
+
+ auto wrong = v.attrs->get(createSymbol("wrong"));
+ ASSERT_NE(wrong, nullptr);
+ ASSERT_EQ(wrong->value->type(), nList);
+ ASSERT_EQ(wrong->value->listSize(), 3);
+ ASSERT_THAT(*wrong->value, IsListOfSize(3));
+ ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1));
+ ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9));
+ ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, concatMap) {
+ auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]");
+ ASSERT_EQ(v.type(), nList);
+ ASSERT_EQ(v.listSize(), 6);
+
+ const std::vector<int> numbers = { 1, 2, 0, 3, 4, 0 };
+ for (const auto [n, elem] : enumerate(v.listItems()))
+ ASSERT_THAT(*elem, IsIntEq(numbers[n]));
+ }
+
+ TEST_F(PrimOpTest, addInt) {
+ auto v = eval("builtins.add 3 5");
+ ASSERT_THAT(v, IsIntEq(8));
+ }
+
+ TEST_F(PrimOpTest, addFloat) {
+ auto v = eval("builtins.add 3.0 5.0");
+ ASSERT_THAT(v, IsFloatEq(8.0));
+ }
+
+ TEST_F(PrimOpTest, addFloatToInt) {
+ auto v = eval("builtins.add 3.0 5");
+ ASSERT_THAT(v, IsFloatEq(8.0));
+
+ v = eval("builtins.add 3 5.0");
+ ASSERT_THAT(v, IsFloatEq(8.0));
+ }
+
+ TEST_F(PrimOpTest, subInt) {
+ auto v = eval("builtins.sub 5 2");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, subFloat) {
+ auto v = eval("builtins.sub 5.0 2.0");
+ ASSERT_THAT(v, IsFloatEq(3.0));
+ }
+
+ TEST_F(PrimOpTest, subFloatFromInt) {
+ auto v = eval("builtins.sub 5.0 2");
+ ASSERT_THAT(v, IsFloatEq(3.0));
+
+ v = eval("builtins.sub 4 2.0");
+ ASSERT_THAT(v, IsFloatEq(2.0));
+ }
+
+ TEST_F(PrimOpTest, mulInt) {
+ auto v = eval("builtins.mul 3 5");
+ ASSERT_THAT(v, IsIntEq(15));
+ }
+
+ TEST_F(PrimOpTest, mulFloat) {
+ auto v = eval("builtins.mul 3.0 5.0");
+ ASSERT_THAT(v, IsFloatEq(15.0));
+ }
+
+ TEST_F(PrimOpTest, mulFloatMixed) {
+ auto v = eval("builtins.mul 3 5.0");
+ ASSERT_THAT(v, IsFloatEq(15.0));
+
+ v = eval("builtins.mul 2.0 5");
+ ASSERT_THAT(v, IsFloatEq(10.0));
+ }
+
+ TEST_F(PrimOpTest, divInt) {
+ auto v = eval("builtins.div 5 (-1)");
+ ASSERT_THAT(v, IsIntEq(-5));
+ }
+
+ TEST_F(PrimOpTest, divIntZero) {
+ ASSERT_THROW(eval("builtins.div 5 0"), EvalError);
+ }
+
+ TEST_F(PrimOpTest, divFloat) {
+ auto v = eval("builtins.div 5.0 (-1)");
+ ASSERT_THAT(v, IsFloatEq(-5.0));
+ }
+
+ TEST_F(PrimOpTest, divFloatZero) {
+ ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError);
+ }
+
+ TEST_F(PrimOpTest, bitOr) {
+ auto v = eval("builtins.bitOr 1 2");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(PrimOpTest, bitXor) {
+ auto v = eval("builtins.bitXor 3 2");
+ ASSERT_THAT(v, IsIntEq(1));
+ }
+
+ TEST_F(PrimOpTest, lessThanFalse) {
+ auto v = eval("builtins.lessThan 3 1");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(PrimOpTest, lessThanTrue) {
+ auto v = eval("builtins.lessThan 1 3");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(PrimOpTest, toStringAttrsThrows) {
+ ASSERT_THROW(eval("builtins.toString {}"), EvalError);
+ }
+
+ TEST_F(PrimOpTest, toStringLambdaThrows) {
+ ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError);
+ }
+
+ class ToStringPrimOpTest :
+ public PrimOpTest,
+ public testing::WithParamInterface<std::tuple<std::string, std::string_view>>
+ {};
+
+ TEST_P(ToStringPrimOpTest, toString) {
+ const auto [input, output] = GetParam();
+ auto v = eval(input);
+ ASSERT_THAT(v, IsStringEq(output));
+ }
+
+#define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " input), std::string_view(output)))
+ INSTANTIATE_TEST_SUITE_P(
+ toString,
+ ToStringPrimOpTest,
+ testing::Values(
+ CASE(R"("foo")", "foo"),
+ CASE(R"(1)", "1"),
+ CASE(R"([1 2 3])", "1 2 3"),
+ CASE(R"(.123)", "0.123000"),
+ CASE(R"(true)", "1"),
+ CASE(R"(false)", ""),
+ CASE(R"(null)", ""),
+ CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
+ CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
+ CASE(R"({ outPath = "foo"; })", "foo"),
+ CASE(R"(./test)", "/test")
+ )
+ );
+#undef CASE
+
+ TEST_F(PrimOpTest, substring){
+ auto v = eval("builtins.substring 0 3 \"nixos\"");
+ ASSERT_THAT(v, IsStringEq("nix"));
+ }
+
+ TEST_F(PrimOpTest, substringSmallerString){
+ auto v = eval("builtins.substring 0 3 \"n\"");
+ ASSERT_THAT(v, IsStringEq("n"));
+ }
+
+ TEST_F(PrimOpTest, substringEmptyString){
+ auto v = eval("builtins.substring 1 3 \"\"");
+ ASSERT_THAT(v, IsStringEq(""));
+ }
+
+ TEST_F(PrimOpTest, stringLength) {
+ auto v = eval("builtins.stringLength \"123\"");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+ TEST_F(PrimOpTest, hashStringMd5) {
+ auto v = eval("builtins.hashString \"md5\" \"asdf\"");
+ ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570"));
+ }
+
+ TEST_F(PrimOpTest, hashStringSha1) {
+ auto v = eval("builtins.hashString \"sha1\" \"asdf\"");
+ ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c"));
+ }
+
+ TEST_F(PrimOpTest, hashStringSha256) {
+ auto v = eval("builtins.hashString \"sha256\" \"asdf\"");
+ ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"));
+ }
+
+ TEST_F(PrimOpTest, hashStringSha512) {
+ auto v = eval("builtins.hashString \"sha512\" \"asdf\"");
+ ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"));
+ }
+
+ TEST_F(PrimOpTest, hashStringInvalidHashType) {
+ ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error);
+ }
+
+ TEST_F(PrimOpTest, nixPath) {
+ auto v = eval("builtins.nixPath");
+ ASSERT_EQ(v.type(), nList);
+ // We can't test much more as currently the EvalSettings are a global
+ // that we can't easily swap / replace
+ }
+
+ TEST_F(PrimOpTest, langVersion) {
+ auto v = eval("builtins.langVersion");
+ ASSERT_EQ(v.type(), nInt);
+ }
+
+ TEST_F(PrimOpTest, storeDir) {
+ auto v = eval("builtins.storeDir");
+ ASSERT_THAT(v, IsStringEq(settings.nixStore));
+ }
+
+ TEST_F(PrimOpTest, nixVersion) {
+ auto v = eval("builtins.nixVersion");
+ ASSERT_THAT(v, IsStringEq(nixVersion));
+ }
+
+ TEST_F(PrimOpTest, currentSystem) {
+ auto v = eval("builtins.currentSystem");
+ ASSERT_THAT(v, IsStringEq(settings.thisSystem.get()));
+ }
+
+ TEST_F(PrimOpTest, derivation) {
+ auto v = eval("derivation");
+ ASSERT_EQ(v.type(), nFunction);
+ ASSERT_TRUE(v.isLambda());
+ ASSERT_NE(v.lambda.fun, nullptr);
+ ASSERT_TRUE(v.lambda.fun->hasFormals());
+ }
+
+ TEST_F(PrimOpTest, currentTime) {
+ auto v = eval("builtins.currentTime");
+ ASSERT_EQ(v.type(), nInt);
+ ASSERT_TRUE(v.integer > 0);
+ }
+
+ TEST_F(PrimOpTest, splitVersion) {
+ auto v = eval("builtins.splitVersion \"1.2.3git\"");
+ ASSERT_THAT(v, IsListOfSize(4));
+
+ const std::vector<std::string_view> strings = { "1", "2", "3", "git" };
+ for (const auto [n, p] : enumerate(v.listItems()))
+ ASSERT_THAT(*p, IsStringEq(strings[n]));
+ }
+
+ class CompareVersionsPrimOpTest :
+ public PrimOpTest,
+ public testing::WithParamInterface<std::tuple<std::string, const int>>
+ {};
+
+ TEST_P(CompareVersionsPrimOpTest, compareVersions) {
+ auto [expression, expectation] = GetParam();
+ auto v = eval(expression);
+ ASSERT_THAT(v, IsIntEq(expectation));
+ }
+
+#define CASE(a, b, expected) (std::make_tuple("builtins.compareVersions \"" #a "\" \"" #b "\"", expected))
+ INSTANTIATE_TEST_SUITE_P(
+ compareVersions,
+ CompareVersionsPrimOpTest,
+ testing::Values(
+ // The first two are weird cases. Intuition tells they should
+ // be the same but they aren't.
+ CASE(1.0, 1.0.0, -1),
+ CASE(1.0.0, 1.0, 1),
+ // the following are from the nix-env manual:
+ CASE(1.0, 2.3, -1),
+ CASE(2.1, 2.3, -1),
+ CASE(2.3, 2.3, 0),
+ CASE(2.5, 2.3, 1),
+ CASE(3.1, 2.3, 1),
+ CASE(2.3.1, 2.3, 1),
+ CASE(2.3.1, 2.3a, 1),
+ CASE(2.3pre1, 2.3, -1),
+ CASE(2.3pre3, 2.3pre12, -1),
+ CASE(2.3a, 2.3c, -1),
+ CASE(2.3pre1, 2.3c, -1),
+ CASE(2.3pre1, 2.3q, -1)
+ )
+ );
+#undef CASE
+
+
+ class ParseDrvNamePrimOpTest :
+ public PrimOpTest,
+ public testing::WithParamInterface<std::tuple<std::string, std::string_view, std::string_view>>
+ {};
+
+ TEST_P(ParseDrvNamePrimOpTest, parseDrvName) {
+ auto [input, expectedName, expectedVersion] = GetParam();
+ const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
+ auto v = eval(expr);
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+
+ auto name = v.attrs->find(createSymbol("name"));
+ ASSERT_TRUE(name);
+ ASSERT_THAT(*name->value, IsStringEq(expectedName));
+
+ auto version = v.attrs->find(createSymbol("version"));
+ ASSERT_TRUE(version);
+ ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
+ }
+
+ INSTANTIATE_TEST_SUITE_P(
+ parseDrvName,
+ ParseDrvNamePrimOpTest,
+ testing::Values(
+ std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"),
+ std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git")
+ )
+ );
+
+ TEST_F(PrimOpTest, replaceStrings) {
+ // FIXME: add a test that verifies the string context is as expected
+ auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\"");
+ ASSERT_EQ(v.type(), nString);
+ ASSERT_EQ(v.string.s, std::string_view("fabir"));
+ }
+
+ TEST_F(PrimOpTest, concatStringsSep) {
+ // FIXME: add a test that verifies the string context is as expected
+ auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]");
+ ASSERT_EQ(v.type(), nString);
+ ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz");
+ }
+
+ TEST_F(PrimOpTest, split1) {
+ // v = [ "" [ "a" ] "c" ]
+ auto v = eval("builtins.split \"(a)b\" \"abc\"");
+ ASSERT_THAT(v, IsListOfSize(3));
+
+ ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
+
+ ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
+ ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
+
+ ASSERT_THAT(*v.listElems()[2], IsStringEq("c"));
+ }
+
+ TEST_F(PrimOpTest, split2) {
+ // v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ]
+ auto v = eval("builtins.split \"([ac])\" \"abc\"");
+ ASSERT_THAT(v, IsListOfSize(5));
+
+ ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
+
+ ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
+ ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
+
+ ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
+
+ ASSERT_THAT(*v.listElems()[3], IsListOfSize(1));
+ ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c"));
+
+ ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
+ }
+
+ TEST_F(PrimOpTest, split3) {
+ auto v = eval("builtins.split \"(a)|(c)\" \"abc\"");
+ ASSERT_THAT(v, IsListOfSize(5));
+
+ // First list element
+ ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
+
+ // 2nd list element is a list [ "" null ]
+ ASSERT_THAT(*v.listElems()[1], IsListOfSize(2));
+ ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
+ ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull());
+
+ // 3rd element
+ ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
+
+ // 4th element is a list: [ null "c" ]
+ ASSERT_THAT(*v.listElems()[3], IsListOfSize(2));
+ ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull());
+ ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c"));
+
+ // 5th element is the empty string
+ ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
+ }
+
+ TEST_F(PrimOpTest, split4) {
+ auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \"");
+ ASSERT_THAT(v, IsListOfSize(3));
+ auto first = v.listElems()[0];
+ auto second = v.listElems()[1];
+ auto third = v.listElems()[2];
+
+ ASSERT_THAT(*first, IsStringEq(" "));
+
+ ASSERT_THAT(*second, IsListOfSize(1));
+ ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO"));
+
+ ASSERT_THAT(*third, IsStringEq(" "));
+ }
+
+ TEST_F(PrimOpTest, match1) {
+ auto v = eval("builtins.match \"ab\" \"abc\"");
+ ASSERT_THAT(v, IsNull());
+ }
+
+ TEST_F(PrimOpTest, match2) {
+ auto v = eval("builtins.match \"abc\" \"abc\"");
+ ASSERT_THAT(v, IsListOfSize(0));
+ }
+
+ TEST_F(PrimOpTest, match3) {
+ auto v = eval("builtins.match \"a(b)(c)\" \"abc\"");
+ ASSERT_THAT(v, IsListOfSize(2));
+ ASSERT_THAT(*v.listElems()[0], IsStringEq("b"));
+ ASSERT_THAT(*v.listElems()[1], IsStringEq("c"));
+ }
+
+ TEST_F(PrimOpTest, match4) {
+ auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \"");
+ ASSERT_THAT(v, IsListOfSize(1));
+ ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
+ }
+
+ TEST_F(PrimOpTest, attrNames) {
+ auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }");
+ ASSERT_THAT(v, IsListOfSize(4));
+
+ // ensure that the list is sorted
+ const std::vector<std::string_view> expected { "a", "x", "y", "z" };
+ for (const auto [n, elem] : enumerate(v.listItems()))
+ ASSERT_THAT(*elem, IsStringEq(expected[n]));
+ }
+
+ TEST_F(PrimOpTest, genericClosure_not_strict) {
+ // Operator should not be used when startSet is empty
+ auto v = eval("builtins.genericClosure { startSet = []; }");
+ ASSERT_THAT(v, IsListOfSize(0));
+ }
+} /* namespace nix */
diff --git a/src/libexpr/tests/trivial.cc b/src/libexpr/tests/trivial.cc
new file mode 100644
index 000000000..8ce276e52
--- /dev/null
+++ b/src/libexpr/tests/trivial.cc
@@ -0,0 +1,196 @@
+#include "libexprtests.hh"
+
+namespace nix {
+ // Testing of trivial expressions
+ class TrivialExpressionTest : public LibExprTest {};
+
+ TEST_F(TrivialExpressionTest, true) {
+ auto v = eval("true");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(TrivialExpressionTest, false) {
+ auto v = eval("false");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(TrivialExpressionTest, null) {
+ auto v = eval("null");
+ ASSERT_THAT(v, IsNull());
+ }
+
+ TEST_F(TrivialExpressionTest, 1) {
+ auto v = eval("1");
+ ASSERT_THAT(v, IsIntEq(1));
+ }
+
+ TEST_F(TrivialExpressionTest, 1plus1) {
+ auto v = eval("1+1");
+ ASSERT_THAT(v, IsIntEq(2));
+ }
+
+ TEST_F(TrivialExpressionTest, minus1) {
+ auto v = eval("-1");
+ ASSERT_THAT(v, IsIntEq(-1));
+ }
+
+ TEST_F(TrivialExpressionTest, 1minus1) {
+ auto v = eval("1-1");
+ ASSERT_THAT(v, IsIntEq(0));
+ }
+
+ TEST_F(TrivialExpressionTest, lambdaAdd) {
+ auto v = eval("let add = a: b: a + b; in add 1 2");
+ ASSERT_THAT(v, IsIntEq(3));
+ }
+
+ TEST_F(TrivialExpressionTest, list) {
+ auto v = eval("[]");
+ ASSERT_THAT(v, IsListOfSize(0));
+ }
+
+ TEST_F(TrivialExpressionTest, attrs) {
+ auto v = eval("{}");
+ ASSERT_THAT(v, IsAttrsOfSize(0));
+ }
+
+ TEST_F(TrivialExpressionTest, float) {
+ auto v = eval("1.234");
+ ASSERT_THAT(v, IsFloatEq(1.234));
+ }
+
+ TEST_F(TrivialExpressionTest, updateAttrs) {
+ auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
+ ASSERT_THAT(v, IsAttrsOfSize(2));
+ auto a = v.attrs->find(createSymbol("a"));
+ ASSERT_NE(a, nullptr);
+ ASSERT_THAT(*a->value, IsIntEq(3));
+
+ auto b = v.attrs->find(createSymbol("b"));
+ ASSERT_NE(b, nullptr);
+ ASSERT_THAT(*b->value, IsIntEq(2));
+ }
+
+ TEST_F(TrivialExpressionTest, hasAttrOpFalse) {
+ auto v = eval("{} ? a");
+ ASSERT_THAT(v, IsFalse());
+ }
+
+ TEST_F(TrivialExpressionTest, hasAttrOpTrue) {
+ auto v = eval("{ a = 123; } ? a");
+ ASSERT_THAT(v, IsTrue());
+ }
+
+ TEST_F(TrivialExpressionTest, withFound) {
+ auto v = eval("with { a = 23; }; a");
+ ASSERT_THAT(v, IsIntEq(23));
+ }
+
+ TEST_F(TrivialExpressionTest, withNotFound) {
+ ASSERT_THROW(eval("with {}; a"), Error);
+ }
+
+ TEST_F(TrivialExpressionTest, withOverride) {
+ auto v = eval("with { a = 23; }; with { a = 42; }; a");
+ ASSERT_THAT(v, IsIntEq(42));
+ }
+
+ TEST_F(TrivialExpressionTest, letOverWith) {
+ auto v = eval("let a = 23; in with { a = 1; }; a");
+ ASSERT_THAT(v, IsIntEq(23));
+ }
+
+ TEST_F(TrivialExpressionTest, multipleLet) {
+ auto v = eval("let a = 23; in let a = 42; in a");
+ ASSERT_THAT(v, IsIntEq(42));
+ }
+
+ TEST_F(TrivialExpressionTest, defaultFunctionArgs) {
+ auto v = eval("({ a ? 123 }: a) {}");
+ ASSERT_THAT(v, IsIntEq(123));
+ }
+
+ TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) {
+ auto v = eval("({ a ? 123 }: a) { a = 5; }");
+ ASSERT_THAT(v, IsIntEq(5));
+ }
+
+ TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) {
+ auto v = eval("({ a ? 123 }@args: args) {}");
+ ASSERT_THAT(v, IsAttrsOfSize(0));
+ }
+
+ TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) {
+ auto v = eval("(args@{ a ? 123 }: args) {}");
+ ASSERT_THAT(v, IsAttrsOfSize(0));
+ }
+
+ TEST_F(TrivialExpressionTest, assertThrows) {
+ ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error);
+ }
+
+ TEST_F(TrivialExpressionTest, assertPassed) {
+ auto v = eval("let x = arg: assert arg == 1; 123; in x 1");
+ ASSERT_THAT(v, IsIntEq(123));
+ }
+
+ class AttrSetMergeTrvialExpressionTest :
+ public TrivialExpressionTest,
+ public testing::WithParamInterface<const char*>
+ {};
+
+ TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) {
+ // Usually Nix rejects duplicate keys in an attrset but it does allow
+ // so if it is an attribute set that contains disjoint sets of keys.
+ // The below is equivalent to `{a.b = 1; a.c = 2; }`.
+ // The attribute set `a` will be a Thunk at first as the attribuets
+ // have to be merged (or otherwise computed) and that is done in a lazy
+ // manner.
+
+ auto expr = GetParam();
+ auto v = eval(expr);
+ ASSERT_THAT(v, IsAttrsOfSize(1));
+
+ auto a = v.attrs->find(createSymbol("a"));
+ ASSERT_NE(a, nullptr);
+
+ ASSERT_THAT(*a->value, IsThunk());
+ state.forceValue(*a->value, noPos);
+
+ ASSERT_THAT(*a->value, IsAttrsOfSize(2));
+
+ auto b = a->value->attrs->find(createSymbol("b"));
+ ASSERT_NE(b, nullptr);
+ ASSERT_THAT(*b->value, IsIntEq(1));
+
+ auto c = a->value->attrs->find(createSymbol("c"));
+ ASSERT_NE(c, nullptr);
+ ASSERT_THAT(*c->value, IsIntEq(2));
+ }
+
+ INSTANTIATE_TEST_SUITE_P(
+ attrsetMergeLazy,
+ AttrSetMergeTrvialExpressionTest,
+ testing::Values(
+ "{ a.b = 1; a.c = 2; }",
+ "{ a = { b = 1; }; a = { c = 2; }; }"
+ )
+ );
+
+ TEST_F(TrivialExpressionTest, functor) {
+ auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
+ ASSERT_THAT(v, IsIntEq(15));
+ }
+
+ TEST_F(TrivialExpressionTest, bindOr) {
+ auto v = eval("{ or = 1; }");
+ ASSERT_THAT(v, IsAttrsOfSize(1));
+ auto b = v.attrs->find(createSymbol("or"));
+ ASSERT_NE(b, nullptr);
+ ASSERT_THAT(*b->value, IsIntEq(1));
+ }
+
+ TEST_F(TrivialExpressionTest, orCantBeUsed) {
+ ASSERT_THROW(eval("let or = 1; in or"), Error);
+ }
+} /* namespace nix */
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index 7b35abca2..5dc453b2e 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -1,105 +1,107 @@
#include "value-to-json.hh"
-#include "json.hh"
#include "eval-inline.hh"
#include "util.hh"
#include <cstdlib>
#include <iomanip>
+#include <nlohmann/json.hpp>
namespace nix {
-
-void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context)
+using json = nlohmann::json;
+json printValueAsJSON(EvalState & state, bool strict,
+ Value & v, const PosIdx pos, PathSet & context, bool copyToStore)
{
checkInterrupt();
if (strict) state.forceValue(v, pos);
+ json out;
+
switch (v.type()) {
case nInt:
- out.write(v.integer);
+ out = v.integer;
break;
case nBool:
- out.write(v.boolean);
+ out = v.boolean;
break;
case nString:
copyContext(v, context);
- out.write(v.string.s);
+ out = v.string.s;
break;
case nPath:
- out.write(state.copyPathToStore(context, v.path));
+ if (copyToStore)
+ out = state.copyPathToStore(context, v.path);
+ else
+ out = v.path;
break;
case nNull:
- out.write(nullptr);
break;
case nAttrs: {
auto maybeString = state.tryAttrsToString(pos, v, context, false, false);
if (maybeString) {
- out.write(*maybeString);
+ out = *maybeString;
break;
}
auto i = v.attrs->find(state.sOutPath);
if (i == v.attrs->end()) {
- auto obj(out.object());
+ out = json::object();
StringSet names;
for (auto & j : *v.attrs)
- names.insert(j.name);
+ names.emplace(state.symbols[j.name]);
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
- auto placeholder(obj.placeholder(j));
- printValueAsJSON(state, strict, *a.value, *a.pos, placeholder, context);
+ out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
}
} else
- printValueAsJSON(state, strict, *i->value, *i->pos, out, context);
+ return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
break;
}
case nList: {
- auto list(out.list());
- for (auto elem : v.listItems()) {
- auto placeholder(list.placeholder());
- printValueAsJSON(state, strict, *elem, pos, placeholder, context);
- }
+ out = json::array();
+ for (auto elem : v.listItems())
+ out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
break;
}
case nExternal:
- v.external->printValueAsJSON(state, strict, out, context);
+ return v.external->printValueAsJSON(state, strict, context, copyToStore);
break;
case nFloat:
- out.write(v.fpoint);
+ out = v.fpoint;
break;
case nThunk:
case nFunction:
auto e = TypeError({
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
- .errPos = v.determinePos(pos)
+ .errPos = state.positions[v.determinePos(pos)]
});
- e.addTrace(pos, hintfmt("message for the trace"));
+ e.addTrace(state.positions[pos], hintfmt("message for the trace"));
+ state.debugThrowLastTrace(e);
throw e;
}
+ return out;
}
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const Pos & pos, std::ostream & str, PathSet & context)
+ Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
{
- JSONPlaceholder out(str);
- printValueAsJSON(state, strict, v, pos, out, context);
+ str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
}
-void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context) const
+json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
+ PathSet & context, bool copyToStore) const
{
- throw TypeError("cannot convert %1% to JSON", showType());
+ state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
}
diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh
index c2f797b29..22f26b790 100644
--- a/src/libexpr/value-to-json.hh
+++ b/src/libexpr/value-to-json.hh
@@ -5,15 +5,14 @@
#include <string>
#include <map>
+#include <nlohmann/json_fwd.hpp>
namespace nix {
-class JSONPlaceholder;
+nlohmann::json printValueAsJSON(EvalState & state, bool strict,
+ Value & v, const PosIdx pos, PathSet & context, bool copyToStore = true);
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context);
-
-void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const Pos & pos, std::ostream & str, PathSet & context);
+ Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
}
diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc
index afeaf5694..3f6222768 100644
--- a/src/libexpr/value-to-xml.cc
+++ b/src/libexpr/value-to-xml.cc
@@ -19,12 +19,13 @@ static XMLAttrs singletonAttrs(const std::string & name, const std::string & val
static void printValueAsXML(EvalState & state, bool strict, bool location,
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
- const Pos & pos);
+ const PosIdx pos);
-static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos)
+static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
- xmlAttrs["path"] = pos.file;
+ if (auto path = std::get_if<Path>(&pos.origin))
+ xmlAttrs["path"] = *path;
xmlAttrs["line"] = (format("%1%") % pos.line).str();
xmlAttrs["column"] = (format("%1%") % pos.column).str();
}
@@ -36,25 +37,25 @@ static void showAttrs(EvalState & state, bool strict, bool location,
StringSet names;
for (auto & i : attrs)
- names.insert(i.name);
+ names.emplace(state.symbols[i.name]);
for (auto & i : names) {
Attr & a(*attrs.find(state.symbols.create(i)));
XMLAttrs xmlAttrs;
xmlAttrs["name"] = i;
- if (location && a.pos != ptr(&noPos)) posToXML(xmlAttrs, *a.pos);
+ if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]);
XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location,
- *a.value, doc, context, drvsSeen, *a.pos);
+ *a.value, doc, context, drvsSeen, a.pos);
}
}
static void printValueAsXML(EvalState & state, bool strict, bool location,
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
- const Pos & pos)
+ const PosIdx pos)
{
checkInterrupt();
@@ -93,14 +94,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
Path drvPath;
a = v.attrs->find(state.sDrvPath);
if (a != v.attrs->end()) {
- if (strict) state.forceValue(*a->value, *a->pos);
+ if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
}
a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) {
- if (strict) state.forceValue(*a->value, *a->pos);
+ if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->string.s;
}
@@ -134,18 +135,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
XMLAttrs xmlAttrs;
- if (location) posToXML(xmlAttrs, v.lambda.fun->pos);
+ if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]);
XMLOpenElement _(doc, "function", xmlAttrs);
if (v.lambda.fun->hasFormals()) {
XMLAttrs attrs;
- if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg;
+ if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg];
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs);
- for (auto & i : v.lambda.fun->formals->lexicographicOrder())
- doc.writeEmptyElement("attr", singletonAttrs("name", i.name));
+ for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
+ doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
} else
- doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg));
+ doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
break;
}
@@ -166,14 +167,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
- const Pos & pos) const
+ const PosIdx pos) const
{
doc.writeEmptyElement("unevaluated");
}
void printValueAsXML(EvalState & state, bool strict, bool location,
- Value & v, std::ostream & out, PathSet & context, const Pos & pos)
+ Value & v, std::ostream & out, PathSet & context, const PosIdx pos)
{
XMLWriter doc(true, out);
XMLOpenElement root(doc, "expr");
diff --git a/src/libexpr/value-to-xml.hh b/src/libexpr/value-to-xml.hh
index cc778a2cb..506f32b6b 100644
--- a/src/libexpr/value-to-xml.hh
+++ b/src/libexpr/value-to-xml.hh
@@ -9,6 +9,6 @@
namespace nix {
void printValueAsXML(EvalState & state, bool strict, bool location,
- Value & v, std::ostream & out, PathSet & context, const Pos & pos);
+ Value & v, std::ostream & out, PathSet & context, const PosIdx pos);
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 3d07c3198..f57597cff 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -7,6 +7,7 @@
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
#endif
+#include <nlohmann/json_fwd.hpp>
namespace nix {
@@ -56,12 +57,12 @@ struct Expr;
struct ExprLambda;
struct PrimOp;
class Symbol;
+class PosIdx;
struct Pos;
class StorePath;
class Store;
class EvalState;
class XMLWriter;
-class JSONPlaceholder;
typedef int64_t NixInt;
@@ -89,7 +90,7 @@ class ExternalValueBase
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error.
*/
- virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
+ virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const;
/* Compare to another value of the same type. Defaults to uncomparable,
* i.e. always false.
@@ -97,13 +98,13 @@ class ExternalValueBase
virtual bool operator ==(const ExternalValueBase & b) const;
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
- virtual void printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context) const;
+ virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict,
+ PathSet & context, bool copyToStore = true) const;
/* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
- const Pos & pos) const;
+ const PosIdx pos) const;
virtual ~ExternalValueBase()
{
@@ -120,11 +121,11 @@ private:
friend std::string showType(const Value & v);
- void print(std::ostream & str, std::set<const void *> * seen) const;
+ void print(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen) const;
public:
- void print(std::ostream & str, bool showRepeated = false) const;
+ void print(const SymbolTable & symbols, std::ostream & str, bool showRepeated = false) const;
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
@@ -250,11 +251,6 @@ public:
void mkStringMove(const char * s, const PathSet & context);
- inline void mkString(const Symbol & s)
- {
- mkString(((const std::string &) s).c_str());
- }
-
inline void mkPath(const char * s)
{
clearValue();
@@ -368,7 +364,7 @@ public:
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
}
- Pos determinePos(const Pos & pos) const;
+ PosIdx determinePos(const PosIdx pos) const;
/* Check whether forcing this value requires a trivial amount of
computation. In particular, function applications are
@@ -408,9 +404,9 @@ public:
#if HAVE_BOEHMGC
-typedef std::vector<Value *, traceable_allocator<Value *> > ValueVector;
-typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *> > > ValueMap;
-typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector> > > ValueVectorMap;
+typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
+typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
+typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap;
#else
typedef std::vector<Value *> ValueVector;
typedef std::map<Symbol, Value *> ValueMap;