729 lines
24 KiB
C++
729 lines
24 KiB
C++
#include "eval.hh"
|
|
#include "primops.hh"
|
|
#include "gc-small-vector.hh"
|
|
|
|
/// This file contains all implementations of `Expr::eval`, and some other helper functions defined by
|
|
/// `Expr` subtypes. Note that some of the evaluation helper functions on `EvalState` that do the heavy
|
|
/// lifting are not in this file but kept in `eval.cc`. In the future, more logic from here will be factored
|
|
/// out into helpers over at `eval.cc` until this file contains a readable and high-level implementation of
|
|
/// the evaluator.
|
|
|
|
namespace nix {
|
|
|
|
/* Create a thunk for the delayed computation of the given expression
|
|
in the given environment. But if the expression is a variable,
|
|
then look it up right away. This significantly reduces the number
|
|
of thunks allocated. */
|
|
Value Expr::maybeThunk(EvalState & state, Env & env)
|
|
{
|
|
state.ctx.stats.nrThunks++;
|
|
return {NewValueAs::thunk, state.ctx.mem, env, *this};
|
|
}
|
|
|
|
Value ExprVar::maybeThunk(EvalState & state, Env & env)
|
|
{
|
|
Value * v = state.lookupVar(&env, *this, true);
|
|
/* The value might not be initialised in the environment yet.
|
|
In that case, ignore it. */
|
|
if (v && !v->isInvalid()) {
|
|
state.ctx.stats.nrAvoided++;
|
|
return *v;
|
|
}
|
|
return Expr::maybeThunk(state, env);
|
|
}
|
|
|
|
Value ExprLiteral::maybeThunk(EvalState & state, Env & env)
|
|
{
|
|
state.ctx.stats.nrAvoided++;
|
|
return v;
|
|
}
|
|
|
|
Value ExprList::maybeThunk(EvalState & state, Env & env)
|
|
{
|
|
if (elems.empty()) {
|
|
return Value::EMPTY_LIST;
|
|
}
|
|
return Expr::maybeThunk(state, env);
|
|
}
|
|
|
|
Value Expr::eval(EvalState & state, Env & env)
|
|
{
|
|
abort();
|
|
}
|
|
|
|
Value ExprLiteral::eval(EvalState & state, Env & env)
|
|
{
|
|
return this->v;
|
|
}
|
|
|
|
Value ExprInheritFrom::eval(EvalState & state, Env & env)
|
|
{
|
|
Value & v2 = env.values[displ];
|
|
state.forceValue(v2, pos);
|
|
return v2;
|
|
}
|
|
|
|
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
|
{
|
|
Env & inheritEnv = state.ctx.mem.allocEnv(inheritFromExprs->size());
|
|
inheritEnv.up = &up;
|
|
|
|
Displacement displ = 0;
|
|
for (auto & from : *inheritFromExprs) {
|
|
inheritEnv.values[displ++] = from->maybeThunk(state, up);
|
|
}
|
|
|
|
return &inheritEnv;
|
|
}
|
|
|
|
Value ExprSet::eval(EvalState & state, Env & env)
|
|
{
|
|
Bindings::Size capacity = attrs.size() + dynamicAttrs.size();
|
|
Value v = {NewValueAs::attrs, state.ctx.buildBindings(capacity).finish()};
|
|
auto dynamicEnv = &env;
|
|
|
|
if (recursive) {
|
|
|
|
/* Create a new environment that contains the attributes in
|
|
this `rec'. */
|
|
Env & env2(state.ctx.mem.allocEnv(attrs.size()));
|
|
env2.up = &env;
|
|
dynamicEnv = &env2;
|
|
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
|
|
|
ExprAttrs::AttrDefs::iterator overrides = attrs.find(state.ctx.symbols.sym___overrides);
|
|
bool hasOverrides = overrides != attrs.end();
|
|
|
|
/* The recursive attributes are evaluated in the new
|
|
environment, while the inherited attributes are evaluated
|
|
in the original environment. */
|
|
Displacement displ = 0;
|
|
for (auto & i : attrs) {
|
|
Value vAttr;
|
|
if (hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
|
|
vAttr = {
|
|
NewValueAs::thunk,
|
|
state.ctx.mem,
|
|
*i.second.chooseByKind(&env2, &env, inheritEnv),
|
|
*i.second.e
|
|
};
|
|
state.ctx.stats.nrThunks++;
|
|
} else {
|
|
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
|
}
|
|
env2.values[displ++] = vAttr;
|
|
v.attrs()->push_back(Attr(i.first, vAttr, i.second.pos));
|
|
}
|
|
|
|
/* If the rec contains an attribute called `__overrides', then
|
|
evaluate it, and add the attributes in that set to the rec.
|
|
This allows overriding of recursive attributes, which is
|
|
otherwise not possible. (You can use the // operator to
|
|
replace an attribute, but other attributes in the rec will
|
|
still reference the original value, because that value has
|
|
been substituted into the bodies of the other attributes.
|
|
Hence we need __overrides.) */
|
|
if (hasOverrides) {
|
|
Value & vOverrides = (*v.attrs())[overrides->second.displ].value;
|
|
state.forceAttrs(vOverrides, noPos, "while evaluating the `__overrides` attribute");
|
|
Bindings * newBnds = state.ctx.mem.allocBindings(capacity + vOverrides.attrs()->size());
|
|
for (auto & i : *v.attrs()) {
|
|
newBnds->push_back(i);
|
|
}
|
|
for (auto & i : *vOverrides.attrs()) {
|
|
ExprAttrs::AttrDefs::iterator j = attrs.find(i.name);
|
|
if (j != attrs.end()) {
|
|
(*newBnds)[j->second.displ] = i;
|
|
env2.values[j->second.displ] = i.value;
|
|
} else {
|
|
newBnds->push_back(i);
|
|
}
|
|
}
|
|
newBnds->sort();
|
|
v = {NewValueAs::attrs, newBnds};
|
|
}
|
|
}
|
|
|
|
else {
|
|
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env) : nullptr;
|
|
for (auto & i : attrs) {
|
|
v.attrs()->push_back(Attr(
|
|
i.first,
|
|
i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)),
|
|
i.second.pos
|
|
));
|
|
}
|
|
}
|
|
|
|
/* Dynamic attrs apply *after* rec and __overrides. */
|
|
for (auto & i : dynamicAttrs) {
|
|
/* Before evaluating dynamic attrs, we blackhole the output attrset and only restore it after the operation.
|
|
* This is to avoid exposing the partially constructed set as a value, see
|
|
* http://github.com/NixOS/nix/issues/7012. Any accesses to the output attrset will thus infrec.
|
|
*/
|
|
Value vBackup = v;
|
|
Symbol nameSym;
|
|
{
|
|
KJ_DEFER(v = vBackup);
|
|
v = Value{NewValueAs::blackhole};
|
|
Value nameVal = i.nameExpr->eval(state, *dynamicEnv);
|
|
state.forceValue(nameVal, i.pos);
|
|
if (nameVal.type() == nNull) {
|
|
continue;
|
|
}
|
|
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
|
|
nameSym = state.ctx.symbols.create(nameVal.str());
|
|
}
|
|
auto j = v.attrs()->get(nameSym);
|
|
if (j) {
|
|
state.ctx.errors
|
|
.make<EvalError>(
|
|
"dynamic attribute '%1%' already defined at %2%",
|
|
state.ctx.symbols[nameSym],
|
|
state.ctx.positions[j->pos]
|
|
)
|
|
.atPos(i.pos)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
|
|
i.valueExpr->setName(nameSym);
|
|
/* Keep sorted order so find can catch duplicates */
|
|
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
|
|
v.attrs()->sort(); // FIXME: inefficient
|
|
}
|
|
|
|
v.attrs()->pos = pos;
|
|
return v;
|
|
}
|
|
|
|
Value ExprLet::eval(EvalState & state, Env & env)
|
|
{
|
|
/* Create a new environment that contains the attributes in this
|
|
`let'. */
|
|
Env & env2(state.ctx.mem.allocEnv(attrs.size()));
|
|
env2.up = &env;
|
|
|
|
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
|
|
|
/* The recursive attributes are evaluated in the new environment,
|
|
while the inherited attributes are evaluated in the original
|
|
environment. */
|
|
Displacement displ = 0;
|
|
for (auto & i : attrs) {
|
|
env2.values[displ++] = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
|
}
|
|
|
|
return body->eval(state, env2);
|
|
}
|
|
|
|
Value ExprList::eval(EvalState & state, Env & env)
|
|
{
|
|
auto result = state.ctx.mem.newList(elems.size());
|
|
Value v = {NewValueAs::list, result};
|
|
for (auto && [n, v2] : enumerate(result->span())) {
|
|
v2 = elems[n]->maybeThunk(state, env);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
Value ExprVar::eval(EvalState & state, Env & env)
|
|
{
|
|
Value * v2 = state.lookupVar(&env, *this, false);
|
|
try {
|
|
state.forceValue(*v2, pos);
|
|
} catch (Error & e) {
|
|
/* `name` can be invalid if we are an ExprInheritFrom */
|
|
if (name) {
|
|
e.addTrace(state.ctx.positions[getPos()], "while evaluating %s", state.ctx.symbols[name]);
|
|
}
|
|
throw;
|
|
}
|
|
return *v2;
|
|
}
|
|
|
|
Value ExprWith::eval(EvalState & state, Env & env)
|
|
{
|
|
Env & env2(state.ctx.mem.allocEnv(1));
|
|
env2.up = &env;
|
|
env2.values[0] = attrs->maybeThunk(state, env);
|
|
|
|
return body->eval(state, env2);
|
|
}
|
|
|
|
Value ExprIf::eval(EvalState & state, Env & env)
|
|
{
|
|
Value vCond = cond->eval(state, env);
|
|
return (state.checkBool(vCond, env, *cond) ? *then : *else_).eval(state, env);
|
|
}
|
|
|
|
Value ExprAssert::eval(EvalState & state, Env & env)
|
|
{
|
|
Value vCond = cond->eval(state, env);
|
|
if (!state.checkBool(vCond, env, *cond)) {
|
|
state.ctx.errors.make<AssertionError>("assertion failed")
|
|
.atPos(pos)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
return body->eval(state, env);
|
|
}
|
|
|
|
Value ExprOpNot::eval(EvalState & state, Env & env)
|
|
{
|
|
Value vInner = e->eval(state, env);
|
|
return {NewValueAs::boolean, !state.checkBool(vInner, env, *e)};
|
|
}
|
|
|
|
Value ExprOpEq::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
Value v2 = e2->eval(state, env);
|
|
return {NewValueAs::boolean, state.eqValues(v1, v2, pos, "while testing two values for equality")};
|
|
}
|
|
|
|
Value ExprOpNEq::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
Value v2 = e2->eval(state, env);
|
|
return {NewValueAs::boolean, !state.eqValues(v1, v2, pos, "while testing two values for inequality")};
|
|
}
|
|
|
|
Value ExprOpAnd::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
/* Explicitly short-circuit */
|
|
if (!state.checkBool(v1, env, *e1)) {
|
|
return {NewValueAs::boolean, false};
|
|
}
|
|
Value v2 = e2->eval(state, env);
|
|
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
|
}
|
|
|
|
Value ExprOpOr::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
/* Explicitly short-circuit */
|
|
if (state.checkBool(v1, env, *e1)) {
|
|
return {NewValueAs::boolean, true};
|
|
}
|
|
Value v2 = e2->eval(state, env);
|
|
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
|
}
|
|
|
|
Value ExprOpImpl::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
/* Explicitly short-circuit (ex falso quodlibet) */
|
|
if (!state.checkBool(v1, env, *e1)) {
|
|
return {NewValueAs::boolean, true};
|
|
}
|
|
Value v2 = e2->eval(state, env);
|
|
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
|
}
|
|
|
|
Value ExprOpUpdate::eval(EvalState & state, Env & env)
|
|
{
|
|
Value v1 = e1->eval(state, env);
|
|
state.checkAttrs(v1, env, *e1);
|
|
Value v2 = e2->eval(state, env);
|
|
state.checkAttrs(v2, env, *e2);
|
|
|
|
state.ctx.stats.nrOpUpdates++;
|
|
|
|
if (v1.attrs()->size() == 0) {
|
|
return v2;
|
|
}
|
|
if (v2.attrs()->size() == 0) {
|
|
return v1;
|
|
}
|
|
|
|
auto attrs = state.ctx.buildBindings(v1.attrs()->size() + v2.attrs()->size());
|
|
|
|
/* Merge the sets, preferring values from the second set. Make
|
|
sure to keep the resulting vector in sorted order. */
|
|
Bindings::iterator i = v1.attrs()->begin();
|
|
Bindings::iterator j = v2.attrs()->begin();
|
|
|
|
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
|
|
if (i->name == j->name) {
|
|
attrs.insert(*j);
|
|
++i;
|
|
++j;
|
|
} else if (i->name < j->name) {
|
|
attrs.insert(*i++);
|
|
} else {
|
|
attrs.insert(*j++);
|
|
}
|
|
}
|
|
|
|
while (i != v1.attrs()->end()) {
|
|
attrs.insert(*i++);
|
|
}
|
|
while (j != v2.attrs()->end()) {
|
|
attrs.insert(*j++);
|
|
}
|
|
|
|
Value v = {NewValueAs::attrs, attrs.alreadySorted()};
|
|
|
|
state.ctx.stats.nrOpUpdateValuesCopied += v.attrs()->size();
|
|
return v;
|
|
}
|
|
|
|
Value ExprOpConcatLists::eval(EvalState & state, Env & env)
|
|
{
|
|
state.ctx.stats.nrListConcats++;
|
|
|
|
/* We don't call into `concatLists` as that loses the position information of the expressions. */
|
|
|
|
Value v1 = e1->eval(state, env);
|
|
state.checkList(v1, env, *e1);
|
|
Value v2 = e2->eval(state, env);
|
|
state.checkList(v2, env, *e2);
|
|
|
|
size_t l1 = v1.listSize(), l2 = v2.listSize(), len = l1 + l2;
|
|
|
|
if (l1 == 0) {
|
|
return v2;
|
|
} else if (l2 == 0) {
|
|
return v1;
|
|
} else {
|
|
auto list = state.ctx.mem.newList(len);
|
|
auto out = list->elems;
|
|
std::copy(v1.listElems(), v1.listElems() + l1, out);
|
|
std::copy(v2.listElems(), v2.listElems() + l2, out + l1);
|
|
return {NewValueAs::list, list};
|
|
}
|
|
}
|
|
|
|
Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
|
{
|
|
NixStringContext context;
|
|
std::vector<BackedStringView> s;
|
|
size_t sSize = 0;
|
|
NixInt n{0};
|
|
NixFloat nf = 0;
|
|
|
|
bool first = !isInterpolation;
|
|
ValueType firstType = nString;
|
|
|
|
const auto str = [&] {
|
|
std::string result;
|
|
result.reserve(sSize);
|
|
for (const auto & part : s) {
|
|
result += *part;
|
|
}
|
|
return result;
|
|
};
|
|
/* build a gc'd value string directly instead of going through str()
|
|
and mkString to save an allocation and copy */
|
|
const auto gcStr = [&] {
|
|
auto result = Value::Str::gcAlloc(sSize);
|
|
|
|
char * tmp = result->contents;
|
|
for (const auto & part : s) {
|
|
memcpy(tmp, part->data(), part->size());
|
|
tmp += part->size();
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// List of returned strings. References to these Values must NOT be persisted.
|
|
SmallTemporaryValueVector<conservativeStackReservation> values;
|
|
values.reserve(es.size());
|
|
|
|
for (auto & [i_pos, i] : es) {
|
|
values.push_back(i->eval(state, env));
|
|
Value & vTmp = values.back();
|
|
|
|
/* If the first element is a path, then the result will also
|
|
be a path, we don't copy anything (yet - that's done later,
|
|
since paths are copied when they are used in a derivation),
|
|
and none of the strings are allowed to have contexts. */
|
|
if (first) {
|
|
firstType = vTmp.type();
|
|
}
|
|
|
|
if (firstType == nInt) {
|
|
if (vTmp.type() == nInt) {
|
|
auto newN = n + vTmp.integer();
|
|
if (auto checked = newN.valueChecked(); checked.has_value()) {
|
|
n = NixInt(*checked);
|
|
} else {
|
|
state.ctx.errors
|
|
.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer())
|
|
.atPos(i_pos)
|
|
.debugThrow();
|
|
}
|
|
} else if (vTmp.type() == nFloat) {
|
|
// Upgrade the type from int to float;
|
|
firstType = nFloat;
|
|
nf = n.value;
|
|
nf += vTmp.fpoint();
|
|
} else {
|
|
state.ctx.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp))
|
|
.atPos(i_pos)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
} else if (firstType == nFloat) {
|
|
if (vTmp.type() == nInt) {
|
|
nf += vTmp.integer().value;
|
|
} else if (vTmp.type() == nFloat) {
|
|
nf += vTmp.fpoint();
|
|
} else {
|
|
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp))
|
|
.atPos(i_pos)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
} else {
|
|
if (s.empty()) {
|
|
s.reserve(es.size());
|
|
}
|
|
|
|
/* If we are coercing inside of an interpolation, we may allow slightly more comfort by coercing
|
|
* things like integers. */
|
|
auto coercionMode = isInterpolation && featureSettings.isEnabled(Xp::CoerceIntegers)
|
|
? StringCoercionMode::Interpolation
|
|
: StringCoercionMode::Strict;
|
|
|
|
/* 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,
|
|
"while evaluating a path segment",
|
|
coercionMode,
|
|
firstType == nString,
|
|
!first
|
|
);
|
|
sSize += part->size();
|
|
s.emplace_back(std::move(part));
|
|
}
|
|
|
|
first = false;
|
|
}
|
|
|
|
if (firstType == nInt) {
|
|
return {NewValueAs::integer, n};
|
|
} else if (firstType == nFloat) {
|
|
return {NewValueAs::floating, nf};
|
|
} else if (firstType == nPath) {
|
|
if (!context.empty()) {
|
|
state.ctx.errors
|
|
.make<EvalError>("a string that refers to a store path cannot be appended to a path")
|
|
.atPos(pos)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
return {NewValueAs::path, CanonPath(canonPath(str()))};
|
|
} else {
|
|
return {NewValueAs::string, gcStr(), context};
|
|
}
|
|
}
|
|
|
|
Value ExprPos::eval(EvalState & state, Env & env)
|
|
{
|
|
return state.mkPos(pos);
|
|
}
|
|
|
|
Value ExprBlackHole::eval(EvalState & state, Env & env)
|
|
{
|
|
state.ctx.errors.make<InfiniteRecursionError>("infinite recursion encountered").debugThrow();
|
|
}
|
|
|
|
Value ExprDebugFrame::eval(EvalState & state, Env & env)
|
|
{
|
|
auto dts = makeDebugTraceStacker(state, *inner, env, state.ctx.positions[pos], message);
|
|
return inner->eval(state, env);
|
|
}
|
|
|
|
/** Returns `nullptr` if we should be using a default instead. */
|
|
Attr const *
|
|
ExprSelect::selectSingleAttr(EvalState & state, Env & env, AttrName const & attrName, Value & vCurrent)
|
|
{
|
|
Symbol const attrSym = getName(attrName, state, env);
|
|
|
|
try {
|
|
state.forceValue(vCurrent, pos);
|
|
} catch (Error & e) {
|
|
// clang-format off
|
|
e.addTrace(state.ctx.positions[attrName.pos], HintFmt(
|
|
"while evaluating an expression to select '%s' on it", state.ctx.symbols[attrSym]
|
|
));
|
|
// clang-format on
|
|
throw;
|
|
}
|
|
|
|
if (vCurrent.type() != nAttrs) {
|
|
// If we have an `or` provided default, then it doesn't have to be an attrset.
|
|
// Let the caller know there's no attr value here.
|
|
if (def != nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Otherwise, we must type error.
|
|
// clang-format off
|
|
state.ctx.errors.make<TypeError>(
|
|
"expected a set but found %s: %s",
|
|
showType(vCurrent),
|
|
ValuePrinter(state, vCurrent, errorPrintOptions)
|
|
).addTrace(
|
|
attrName.pos,
|
|
HintFmt("while selecting '%s'", state.ctx.symbols[attrSym])
|
|
).debugThrow();
|
|
// clang-format on
|
|
}
|
|
|
|
// Now that we know it's an attrset, we can actually look for the name.
|
|
|
|
auto const attrIt = vCurrent.attrs()->get(attrSym);
|
|
if (!attrIt) {
|
|
|
|
// Again if we have an `or` provided default, then missing attr is not an error.
|
|
if (def != nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Otherwise, we collect all attr names and throw an attr missing error.
|
|
|
|
std::set<std::string> const allAttrNames = *vCurrent.attrs()
|
|
| std::views::transform([&state](auto const & attr) {
|
|
return std::string{state.ctx.symbols[attr.name]};
|
|
})
|
|
| std::ranges::to<std::set>();
|
|
|
|
auto suggestions = Suggestions::bestMatches(allAttrNames, state.ctx.symbols[attrSym]);
|
|
state.ctx.errors.make<EvalError>("attribute '%s' missing", state.ctx.symbols[attrSym])
|
|
.atPos(attrName.pos)
|
|
.withSuggestions(suggestions)
|
|
.withFrame(env, *this)
|
|
.debugThrow();
|
|
}
|
|
|
|
// If we made it here, then we successfully found the attribute.
|
|
// Return it to our caller!
|
|
|
|
return attrIt;
|
|
}
|
|
|
|
Value ExprSelect::eval(EvalState & state, Env & env)
|
|
{
|
|
// Position for the current attrset Value in this select chain.
|
|
PosIdx posCurrent;
|
|
// Position for the current selector in this select chain.
|
|
PosIdx posCurrentSyntax;
|
|
|
|
Value baseSelectee = [&]() {
|
|
try {
|
|
// Evaluate the original thing we're selecting on.
|
|
return e->eval(state, env);
|
|
} catch (Error & e) {
|
|
// clang-format off
|
|
e.addTrace(state.ctx.positions[getPos()], HintFmt(
|
|
"while evaluating an expression to select '%s' on it",
|
|
showAttrPath(state.ctx.symbols, attrPath)
|
|
));
|
|
// clang-format on
|
|
throw;
|
|
}
|
|
}();
|
|
|
|
try {
|
|
// With the original selectee evaluated, we'll walk the selection path starting
|
|
// with the evaluated original selectee.
|
|
std::reference_wrapper<Value> curSelectee = std::ref(baseSelectee);
|
|
for (AttrName const & attrName : attrPath) {
|
|
state.ctx.stats.nrLookups++;
|
|
|
|
// Select `attrName` on `curSelectee`.
|
|
auto const attr = selectSingleAttr(state, env, attrName, curSelectee.get());
|
|
if (!attr) {
|
|
// Use default.
|
|
try {
|
|
return this->def->eval(state, env);
|
|
} catch (Error & err) {
|
|
err.addTrace(
|
|
state.ctx.positions[this->def->pos],
|
|
"while evaluating fallback for missing attribute '%s'",
|
|
state.ctx.symbols[getName(attrName, state, env)]
|
|
);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// The selection worked. If we have another iteration, then we use `attr->value`
|
|
// as the thing to select on. If this is the last iteration, then `attr->value`
|
|
// is the final value this ExprSelect evaluated to.
|
|
curSelectee = std::ref(attr->value);
|
|
|
|
posCurrent = attr->pos;
|
|
posCurrentSyntax = attrName.pos;
|
|
if (state.ctx.stats.countCalls) {
|
|
state.ctx.stats.attrSelects[posCurrent]++;
|
|
}
|
|
}
|
|
|
|
state.forceValue(curSelectee.get(), posCurrent ? posCurrent : posCurrentSyntax);
|
|
|
|
return curSelectee.get();
|
|
|
|
} catch (Error & err) {
|
|
auto const & lastPos = state.ctx.positions[posCurrent];
|
|
if (lastPos && !std::get_if<Pos::Hidden>(&lastPos.origin)) {
|
|
err.addTrace(lastPos, "while evaluating the attribute '%s'", showAttrPath(state, env, attrPath));
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
Value ExprOpHasAttr::eval(EvalState & state, Env & env)
|
|
{
|
|
Value vTmp = e->eval(state, env);
|
|
Value * vAttrs = &vTmp;
|
|
|
|
for (auto & i : attrPath) {
|
|
state.forceValue(*vAttrs, getPos());
|
|
const Attr * j;
|
|
auto name = getName(i, state, env);
|
|
if (vAttrs->type() != nAttrs || (j = vAttrs->attrs()->get(name)) == nullptr) {
|
|
return {NewValueAs::boolean, false};
|
|
} else {
|
|
vAttrs = &j->value;
|
|
}
|
|
}
|
|
|
|
return {NewValueAs::boolean, true};
|
|
}
|
|
|
|
Value ExprLambda::eval(EvalState & state, Env & env)
|
|
{
|
|
return {NewValueAs::lambda, state.ctx.mem, env, *this};
|
|
}
|
|
|
|
Value ExprCall::eval(EvalState & state, Env & env)
|
|
{
|
|
Value vFun = fun->eval(state, env);
|
|
|
|
// Empirical arity of Nixpkgs lambdas by regex e.g. ([a-zA-Z]+:(\s|(/\*.*\/)|(#.*\n))*){5}
|
|
// 2: over 4000
|
|
// 3: about 300
|
|
// 4: about 60
|
|
// 5: under 10
|
|
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150
|
|
// total.
|
|
SmallValueVector<4> vArgs;
|
|
vArgs.reserve(args.size());
|
|
for (size_t i = 0; i < args.size(); ++i) {
|
|
vArgs.push_back(args[i]->maybeThunk(state, env));
|
|
}
|
|
|
|
return state.callFunction(vFun, vArgs, pos);
|
|
}
|
|
|
|
}
|