Skip to content

Commit

Permalink
New attempt at lazy attribute names, works much better now
Browse files Browse the repository at this point in the history
However there's a bug that causes an infinite loop when instantiating
nixpkgs derivations

Also there's a whole lot of copying in this implementation currently
  • Loading branch information
infinisil committed Oct 9, 2020
1 parent 85c8be6 commit 5dbf67c
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 23 deletions.
201 changes: 180 additions & 21 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,13 @@ void Expr::eval(EvalState & state, Env & env, Value & v)
abort();
}

bool Expr::evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v)
{
//printError(format("Expr::evalAttr called for %s.%s") % *this % name);
this->eval(state, env, v);
return evalValueAttr(state, name, v);
}


void ExprInt::eval(EvalState & state, Env & env, Value & v)
{
Expand Down Expand Up @@ -1069,6 +1076,23 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
body->eval(state, env2, v);
}

bool ExprLet::evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v)
{
/* Create a new environment that contains the attributes in this
`let'. */
Env & env2(state.allocEnv(attrs->attrs.size()));
env2.up = &env;

/* The recursive attributes are evaluated in the new environment,
while the inherited attributes are evaluated in the original
environment. */
size_t displ = 0;
for (auto & i : attrs->attrs)
env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);

return body->evalAttr(state, env2, name, v);
}


void ExprList::eval(EvalState & state, Env & env, Value & v)
{
Expand All @@ -1085,6 +1109,13 @@ void ExprVar::eval(EvalState & state, Env & env, Value & v)
v = *v2;
}

bool ExprVar::evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v)
{
Value * v2 = state.lookupVar(&env, *this, false);
v = *v2;
return evalValueAttr(state, name, v);
}


static string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPath)
{
Expand All @@ -1105,38 +1136,61 @@ static string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPa

unsigned long nrLookups = 0;

bool evalValueAttr(EvalState & state, const Symbol & name, Value & v) {
//printError(format("evalValueAttr called for %s.%s") % v % name);
if (v.type == tThunk) {
Env * env = v.thunk.env;
Expr * expr = v.thunk.expr;
return expr->evalAttr(state, *env, name, v);
}

state.forceValue(v, Pos());
if (v.type != tAttrs)
return false;

Bindings::iterator j;
if ((j = v.attrs->find(name)) == v.attrs->end())
return false;

v = *j->value;
return true;
}

void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{
//printError(format("ExprSelect::eval called for %s.%s") % *e % showAttrPath(state, env, attrPath));
Value vTmp;
Pos * pos2 = 0;
Value * vAttrs = &vTmp;

e->eval(state, env, vTmp);
mkThunk(vTmp, env, e);

try {

for (auto & i : attrPath) {
nrLookups++;
Bindings::iterator j;
//Bindings::iterator j;
Symbol name = getName(i, state, env);
if (def) {
state.forceValue(*vAttrs, pos);
if (vAttrs->type != tAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
bool hasIt = evalValueAttr(state, name, *vAttrs);
//printError(format("Iterating with attribute %s, and the result is: %s") % name % hasIt);
if (!hasIt) {
if (def) {
def->eval(state, env, v);
return;
}
} else {
state.forceAttrs(*vAttrs, pos);
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
} else {
// Either it wasn't an attribute set, in which case this should trigger an error
//printError("Right before state.forceAttrs");
state.forceAttrs(*vAttrs, pos);
// Or it was but didn't have the required attribute, in which case we trigger this error
throwEvalError(pos, "attribute '%1%' missing", name);
}
}
vAttrs = j->value;
pos2 = j->pos;
//vAttrs = j->value;
//pos2 = j->pos;
if (state.countCalls && pos2) state.attrSelects[*pos2]++;
}

//printError("Right before state.forceValue");
state.forceValue(*vAttrs, ( pos2 != NULL ? *pos2 : this->pos ) );

} catch (Error & e) {
Expand All @@ -1155,19 +1209,14 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
Value vTmp;
Value * vAttrs = &vTmp;

e->eval(state, env, vTmp);
mkThunk(vTmp, env, e);

for (auto & i : attrPath) {
state.forceValue(*vAttrs);
Bindings::iterator j;
Symbol name = getName(i, state, env);
if (vAttrs->type != tAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
bool hasIt = evalValueAttr(state, name, *vAttrs);
if (!hasIt) {
mkBool(v, false);
return;
} else {
vAttrs = j->value;
}
}

Expand All @@ -1191,6 +1240,14 @@ void ExprApp::eval(EvalState & state, Env & env, Value & v)
state.callFunction(vFun, *(e2->maybeThunk(state, env)), v, pos);
}

bool ExprApp::evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v)
{
/* FIXME: vFun prevents GCC from doing tail call optimisation. */
Value vFun;
e1->eval(state, env, vFun);
return state.callFunctionAttr(vFun, *(e2->maybeThunk(state, env)), name, v, pos);
}


void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
{
Expand Down Expand Up @@ -1228,6 +1285,103 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
}
}

bool EvalState::callFunctionAttr(Value & fun, Value & arg, const Symbol & name, Value & v, const Pos & pos)
{
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;

forceValue(fun, pos);

if (fun.type == tPrimOp || fun.type == tPrimOpApp) {
callPrimOp(fun, arg, v, pos);
return evalValueAttr(*this, name, v);
}

if (fun.type == tAttrs) {
auto found = fun.attrs->find(sFunctor);
if (found != fun.attrs->end()) {
/* fun may be allocated on the stack of the calling function,
* but for functors we may keep a reference, so heap-allocate
* a copy and use that instead.
*/
auto & fun2 = *allocValue();
fun2 = fun;
/* !!! Should we use the attr pos here? */
Value v2;
callFunction(*found->value, fun2, v2, pos);
return callFunctionAttr(v2, arg, name, v, pos);
}
}

if (fun.type != tLambda)
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);

ExprLambda & lambda(*fun.lambda.fun);

auto size =
(lambda.arg.empty() ? 0 : 1) +
(lambda.matchAttrs ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = fun.lambda.env;

size_t displ = 0;

if (!lambda.matchAttrs)
env2.values[displ++] = &arg;

else {
forceAttrs(arg, pos);

if (!lambda.arg.empty())
env2.values[displ++] = &arg;

/* For each formal argument, get the actual argument. If
there is no matching actual argument but the formal
argument has a default, use the default. */
size_t attrsUsed = 0;
for (auto & i : lambda.formals->formals) {
Bindings::iterator j = arg.attrs->find(i.name);
if (j == arg.attrs->end()) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
lambda, i.name);
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
attrsUsed++;
env2.values[displ++] = j->value;
}
}

/* Check that each actual argument is listed as a formal
argument (unless the attribute match specifies a `...'). */
if (!lambda.formals->ellipsis && attrsUsed != arg.attrs->size()) {
/* Nope, so show the first unexpected argument to the
user. */
for (auto & i : *arg.attrs)
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
abort(); // can't happen
}
}

nrFunctionCalls++;
if (countCalls) incrFunctionCall(&lambda);

/* Evaluate the body. This is conditional on showTrace, because
catching exceptions makes this function not tail-recursive. */
if (loggerSettings.showTrace.get())
try {
return lambda.body->evalAttr(*this, env2, name, v);
} catch (Error & e) {
addErrorTrace(e, lambda.pos, "while evaluating %s",
(lambda.name.set()
? "'" + (string) lambda.name + "'"
: "anonymous lambda"));
addErrorTrace(e, pos, "from call site%s", "");
throw;
}
else
return fun.lambda.fun->body->evalAttr(*this, env2, name, v);
}

void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
{
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
Expand Down Expand Up @@ -1485,6 +1639,11 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
state.nrOpUpdateValuesCopied += v.attrs->size();
}

bool ExprOpUpdate::evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v) {
//printError(format("ExprOpUpdate::evalAttr called for %s // %s and %s") % *e1 % *e2 % name);
return e2->evalAttr(state, env, name, v) || e1->evalAttr(state, env, name, v);
}


void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
{
Expand Down
2 changes: 2 additions & 0 deletions src/libexpr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace nix {

bool evalValueAttr(EvalState & state, const Symbol & name, Value & v);

class Store;
class EvalState;
Expand Down Expand Up @@ -283,6 +284,7 @@ public:
bool isFunctor(Value & fun);

void callFunction(Value & fun, Value & arg, Value & v, const Pos & pos);
bool callFunctionAttr(Value & fun, Value & arg, const Symbol & name, Value & v, const Pos & pos);
void callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos);

/* Automatically call a function for which each argument has a
Expand Down
45 changes: 43 additions & 2 deletions src/libexpr/nixexpr.hh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct Expr
virtual void show(std::ostream & str) const;
virtual void bindVars(const StaticEnv & env);
virtual void eval(EvalState & state, Env & env, Value & v);
virtual bool evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol & name);
};
Expand Down Expand Up @@ -156,6 +157,8 @@ struct ExprVar : Expr
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);

bool evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v);
};

struct ExprSelect : Expr
Expand Down Expand Up @@ -253,6 +256,7 @@ struct ExprLet : Expr
Expr * body;
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { };
COMMON_METHODS
bool evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v);
};

struct ExprWith : Expr
Expand Down Expand Up @@ -305,15 +309,52 @@ struct ExprOpNot : Expr
void eval(EvalState & state, Env & env, Value & v); \
};

MakeBinOp(ExprApp, "")
//MakeBinOp(ExprApp, "")
MakeBinOp(ExprOpEq, "==")
MakeBinOp(ExprOpNEq, "!=")
MakeBinOp(ExprOpAnd, "&&")
MakeBinOp(ExprOpOr, "||")
MakeBinOp(ExprOpImpl, "->")
MakeBinOp(ExprOpUpdate, "//")
//MakeBinOp(ExprOpUpdate, "//")
MakeBinOp(ExprOpConcatLists, "++")

struct ExprApp : Expr
{
Pos pos;
Expr * e1, * e2;
ExprApp(Expr * e1, Expr * e2) : e1(e1), e2(e2) { };
ExprApp(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { };
void show(std::ostream & str) const
{
str << "(" << *e1 << " " "" " " << *e2 << ")";
}
void bindVars(const StaticEnv & env)
{
e1->bindVars(env); e2->bindVars(env);
}
void eval(EvalState & state, Env & env, Value & v);
bool evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v);
};


struct ExprOpUpdate : Expr
{
Pos pos;
Expr * e1, * e2;
ExprOpUpdate(Expr * e1, Expr * e2) : e1(e1), e2(e2) { };
ExprOpUpdate(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { };
void show(std::ostream & str) const
{
str << "(" << *e1 << " " "//" " " << *e2 << ")";
}
void bindVars(const StaticEnv & env)
{
e1->bindVars(env); e2->bindVars(env);
}
void eval(EvalState & state, Env & env, Value & v);
bool evalAttr(EvalState & state, Env & env, const Symbol & name, Value & v);
};

struct ExprConcatStrings : Expr
{
Pos pos;
Expand Down

0 comments on commit 5dbf67c

Please sign in to comment.