diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 9c3c1fc4b..defecb008 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -58,23 +58,17 @@ std::pair findAlongAttrPath(EvalState & state, const string & attr Value * vNew = state.allocValue(); state.autoCallFunction(autoArgs, *v, *vNew); v = vNew; - state.forceValue(*v); /* It should evaluate to either a set or an expression, according to what is specified in the attrPath. */ if (!attrIndex) { - if (v->type() != nAttrs) - throw TypeError( - "the expression selected by the selection path '%1%' should be a set but is %2%", - attrPath, - showType(*v)); if (attr.empty()) throw Error("empty attribute name in selection path '%1%'", attrPath); auto v2 = state.allocValue(); - auto gotField = state.getAttrField(*v, {state.symbols.create(attr)}, pos, *v2); + auto gotField = state.lazyGetAttrField(*v, {state.symbols.create(attr)}, pos, *v2); if (!gotField) throw AttrPathNotFound("attribute '%1%' in selection path '%2%' not found", attr, attrPath); v = v2; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d38917ab7..9415ab46c 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1189,6 +1189,54 @@ void EvalState::updateCacheStats(ValueCache::CacheResult cacheResult) }; } +struct ExprCastedVar : Expr +{ + Value * v; + ExprCastedVar(Value * v) : v(v) {}; + void show(std::ostream & str) const override { + std::set active; + printValue(str, active, *v); + } + + void bindVars(const StaticEnv & env) override {} + void eval(EvalState & state, Env & env, Value & v) override { + v = std::move(*this->v); + } + Value * maybeThunk(EvalState & state, Env & env) override { + return v; + } +}; + +bool EvalState::lazyGetAttrField(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest) +{ + forceValue(attrs, pos); + auto eval_cache = attrs.getEvalCache(); + auto [ cacheResult, resultingCursor ] = eval_cache.getValue(*this, selector, dest); + updateCacheStats(cacheResult); + switch (cacheResult.returnCode) { + case ValueCache::CacheHit: + if (cacheResult.lastQueriedSymbolIfMissing) + return false; + return true; + case ValueCache::Forward: { + auto recordAsVar = new ExprCastedVar(&attrs); + auto accessExpr = new ExprSelect(pos, recordAsVar, selector); + auto thunk = (Thunk*)allocBytes(sizeof(Thunk)); + thunk->expr = accessExpr; + thunk->env = &baseEnv; + + dest.mkCachedThunk( + thunk, + new ValueCache(resultingCursor) + ); + return true; + } + default: + return getAttrField(attrs, selector, pos, dest); + ; + } + +} bool EvalState::getAttrField(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest) { @@ -1449,9 +1497,24 @@ void EvalState::incrFunctionCall(ExprLambda * fun) functionCalls[fun]++; } +std::optional ValueCache::getRawValue() +{ + if (!rawCache) + return std::nullopt; + return rawCache->getCachedValue(); +} void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) { + if (auto evalCache = fun.getEvalCache(); !evalCache.isEmpty()) { + if (auto cacheValue = evalCache.getRawValue()) { + if (std::holds_alternative(*cacheValue)) { + res = fun; + return; + } + } + } + forceValue(fun); if (fun.type() == nAttrs) { diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index a86585035..b995b3f66 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -356,6 +356,9 @@ private: public: bool getAttrField(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest); + // Similar to `getAttrField`, but if the cache says that the result is an + // attribute set, just return a thunk to it rather than forcing it. + bool lazyGetAttrField(Value & attrs, const std::vector & selector, const Pos & pos, Value & dest); }; diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 51a14cd59..da860e141 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -165,7 +165,12 @@ struct ExprSelect : Expr 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 Pos & pos, Expr * e, const Symbol & name) + : ExprSelect(pos, e, std::vector{name}) {}; + ExprSelect(const Pos & pos, Expr * e, const std::vector & symbolicAttrPath) : pos(pos), e(e), def(0) { + for (auto & name : symbolicAttrPath) + attrPath.push_back(AttrName(name)); + }; COMMON_METHODS };