brewery
notashelf /
63a9eddc49735e9136a6cde78cdc772d9e7ad259

nixir

public

Import-resolving Nix IR plugin

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.git

Commit 63a9eddc4973

tarball

NotAShelf <raf@notashelf.dev> · 2026-02-21 21:18 UTC

unverified1 files changed+406-430
irc/evaluator: fix `e` passed by value in Thunk constructor

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If3bdfd1fc0851d4113b89827474a74a86a6a6964
diff --git a/src/irc/evaluator.cpp b/src/irc/evaluator.cppindex 977ea85..dfd6eb1 100644--- a/src/irc/evaluator.cpp+++ b/src/irc/evaluator.cpp@@ -7,481 +7,457 @@ #include "nix/expr/value.hh" #include "nix/util/error.hh" -#include <unordered_map> #include <stdexcept>+#include <unordered_map>  namespace nix_irc {  using namespace nix;  struct IREnvironment {-    IREnvironment* parent;-    std::vector<Value*> bindings;-    Value* with_attrs;--    explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}--    void bind(Value* val) {-        bindings.push_back(val);+  IREnvironment* parent;+  std::vector<Value*> bindings;+  Value* with_attrs;++  explicit IREnvironment(IREnvironment* p = nullptr) : parent(p), with_attrs(nullptr) {}++  void bind(Value* val) { bindings.push_back(val); }++  Value* lookup(uint32_t index) {+    IREnvironment* env = this;+    while (env) {+      if (index < env->bindings.size()) {+        return env->bindings[index];+      }+      index -= env->bindings.size();+      env = env->parent;     }--    Value* lookup(uint32_t index) {-        IREnvironment* env = this;-        while (env) {-            if (index < env->bindings.size()) {-                return env->bindings[index];-            }-            index -= env->bindings.size();-            env = env->parent;+    return nullptr;+  }++  Value* lookup_with(EvalState& state, const std::string& name) {+    IREnvironment* env = this;+    while (env) {+      if (env->with_attrs && env->with_attrs->type() == nAttrs) {+        auto sym = state.symbols.create(name);+        auto attr = env->with_attrs->attrs()->get(sym);+        if (attr) {+          return attr->value;         }-        return nullptr;-    }--    Value* lookup_with(EvalState& state, const std::string& name) {-        IREnvironment* env = this;-        while (env) {-            if (env->with_attrs && env->with_attrs->type() == nAttrs) {-                auto sym = state.symbols.create(name);-                auto attr = env->with_attrs->attrs()->get(sym);-                if (attr) {-                    return attr->value;-                }-            }-            env = env->parent;-        }-        return nullptr;+      }+      env = env->parent;     }+    return nullptr;+  } };  struct Thunk {-    std::shared_ptr<Node> expr;-    IREnvironment* env;-    bool blackholed;+  std::shared_ptr<Node> expr;+  IREnvironment* env;+  bool blackholed; -    Thunk(std::shared_ptr<Node> e, IREnvironment* en)-        : expr(e), env(en), blackholed(false) {}+  Thunk(const std::shared_ptr<Node>& e, IREnvironment* en) : expr(e), env(en), blackholed(false) {} };  struct Evaluator::Impl {-    EvalState& state;-    std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;-    std::vector<std::unique_ptr<IREnvironment>> environments;+  EvalState& state;+  std::unordered_map<Value*, std::unique_ptr<Thunk>> thunks;+  std::vector<std::unique_ptr<IREnvironment>> environments; -    explicit Impl(EvalState& s) : state(s) {}+  explicit Impl(EvalState& s) : state(s) {} -    ~Impl() {-        for (auto& env : environments) {-            delete env.release();-        }+  ~Impl() {+    for (auto& env : environments) {+      delete env.release();     }--    IREnvironment* make_env(IREnvironment* parent = nullptr) {-        auto env = new IREnvironment(parent);-        environments.push_back(std::unique_ptr<IREnvironment>(env));-        return env;+  }++  IREnvironment* make_env(IREnvironment* parent = nullptr) {+    auto env = new IREnvironment(parent);+    environments.push_back(std::unique_ptr<IREnvironment>(env));+    return env;+  }++  Value* make_thunk(const std::shared_ptr<Node>& expr, IREnvironment* env) {+    Value* v = state.allocValue();+    thunks[v] = std::make_unique<Thunk>(expr, env);+    return v;+  }++  void force(Value* v) {+    auto it = thunks.find(v);+    if (it == thunks.end()) {+      return;     } -    Value* make_thunk(const std::shared_ptr<Node>& expr, IREnvironment* env) {-        Value* v = state.allocValue();-        thunks[v] = std::make_unique<Thunk>(expr, env);-        return v;+    Thunk* thunk = it->second.get();+    if (thunk->blackholed) {+      state.error<EvalError>("infinite recursion encountered").debugThrow();     } -    void force(Value* v) {-        auto it = thunks.find(v);-        if (it == thunks.end()) {-            return;-        }--        Thunk* thunk = it->second.get();-        if (thunk->blackholed) {-            state.error<EvalError>("infinite recursion encountered").debugThrow();-        }+    thunk->blackholed = true;+    eval_node(thunk->expr, *v, thunk->env);+    thunks.erase(v);+  } -        thunk->blackholed = true;-        eval_node(thunk->expr, *v, thunk->env);-        thunks.erase(v);+  void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {+    if (!node) {+      v.mkNull();+      return;     } -    void eval_node(const std::shared_ptr<Node>& node, Value& v, IREnvironment* env) {-        if (!node) {-            v.mkNull();-            return;-        }--        if (auto* n = node->get_if<ConstIntNode>()) {-            v.mkInt(n->value);-        }-        else if (auto* n = node->get_if<ConstStringNode>()) {-            v.mkString(n->value);-        }-        else if (auto* n = node->get_if<ConstPathNode>()) {-            v.mkPath(state.rootPath(CanonPath(n->value)));-        }-        else if (auto* n = node->get_if<ConstBoolNode>()) {-            v.mkBool(n->value);-        }-        else if (auto* n = node->get_if<ConstNullNode>()) {-            v.mkNull();-        }-        else if (auto* n = node->get_if<VarNode>()) {-            Value* bound = env ? env->lookup(n->index) : nullptr;-            if (!bound && env && n->name.has_value()) {-                bound = env->lookup_with(state, n->name.value());-            }-            if (!bound) {-                state.error<EvalError>("variable not found").debugThrow();-            }-            force(bound);-            v = *bound;-        }-        else if (auto* n = node->get_if<LambdaNode>()) {-            auto lambda_env = env;-            auto body = n->body;--            auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos,-                                                   Value** args, Value& result) {-                auto call_env = make_env(lambda_env);-                call_env->bind(args[0]);-                eval_node(body, result, call_env);-            };--            v.mkPrimOp(new PrimOp {-                .name = n->param_name.value_or("lambda"),-                .arity = static_cast<size_t>(n->arity),-                .fun = primOp-            });-        }-        else if (auto* n = node->get_if<AppNode>()) {-            Value* func_val = state.allocValue();-            eval_node(n->func, *func_val, env);-            force(func_val);--            Value* arg_val = make_thunk(n->arg, env);--            state.callFunction(*func_val, *arg_val, v, noPos);-        }-        else if (auto* n = node->get_if<BinaryOpNode>()) {-            Value* left = state.allocValue();-            Value* right = state.allocValue();--            switch (n->op) {-                case BinaryOp::AND:-                    eval_node(n->left, *left, env);-                    force(left);-                    if (left->type() != nBool) {-                        state.error<EvalError>("type error in logical AND").debugThrow();-                    }-                    if (!left->boolean()) {-                        v.mkBool(false);-                    } else {-                        eval_node(n->right, *right, env);-                        force(right);-                        if (right->type() != nBool) {-                            state.error<EvalError>("type error in logical AND").debugThrow();-                        }-                        v.mkBool(right->boolean());-                    }-                    break;-                case BinaryOp::OR:-                    eval_node(n->left, *left, env);-                    force(left);-                    if (left->type() != nBool) {-                        state.error<EvalError>("type error in logical OR").debugThrow();-                    }-                    if (left->boolean()) {-                        v.mkBool(true);-                    } else {-                        eval_node(n->right, *right, env);-                        force(right);-                        if (right->type() != nBool) {-                            state.error<EvalError>("type error in logical OR").debugThrow();-                        }-                        v.mkBool(right->boolean());-                    }-                    break;-                case BinaryOp::IMPL:-                    eval_node(n->left, *left, env);-                    force(left);-                    if (left->type() != nBool) {-                        state.error<EvalError>("type error in implication").debugThrow();-                    }-                    if (!left->boolean()) {-                        v.mkBool(true);-                    } else {-                        eval_node(n->right, *right, env);-                        force(right);-                        if (right->type() != nBool) {-                            state.error<EvalError>("type error in implication").debugThrow();-                        }-                        v.mkBool(right->boolean());-                    }-                    break;-                default:-                    eval_node(n->left, *left, env);-                    eval_node(n->right, *right, env);-                    force(left);-                    force(right);--                    switch (n->op) {-                        case BinaryOp::ADD:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkInt((left->integer() + right->integer()).valueWrapping());-                            } else if (left->type() == nString && right->type() == nString) {-                                v.mkString(std::string(left->c_str()) + std::string(right->c_str()));-                            } else {-                                state.error<EvalError>("type error in addition").debugThrow();-                            }-                            break;-                        case BinaryOp::SUB:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkInt((left->integer() - right->integer()).valueWrapping());-                            } else {-                                state.error<EvalError>("type error in subtraction").debugThrow();-                            }-                            break;-                        case BinaryOp::MUL:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkInt((left->integer() * right->integer()).valueWrapping());-                            } else {-                                state.error<EvalError>("type error in multiplication").debugThrow();-                            }-                            break;-                        case BinaryOp::DIV:-                            if (left->type() == nInt && right->type() == nInt) {-                                if (right->integer() == NixInt(0)) {-                                    state.error<EvalError>("division by zero").debugThrow();-                                }-                                v.mkInt((left->integer() / right->integer()).valueWrapping());-                            } else {-                                state.error<EvalError>("type error in division").debugThrow();-                            }-                            break;-                        case BinaryOp::EQ:-                            v.mkBool(state.eqValues(*left, *right, noPos, ""));-                            break;-                        case BinaryOp::NE:-                            v.mkBool(!state.eqValues(*left, *right, noPos, ""));-                            break;-                        case BinaryOp::LT:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkBool(left->integer() < right->integer());-                            } else if (left->type() == nString && right->type() == nString) {-                                v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));-                            } else {-                                state.error<EvalError>("type error in comparison").debugThrow();-                            }-                            break;-                        case BinaryOp::GT:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkBool(left->integer() > right->integer());-                            } else if (left->type() == nString && right->type() == nString) {-                                v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));-                            } else {-                                state.error<EvalError>("type error in comparison").debugThrow();-                            }-                            break;-                        case BinaryOp::LE:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkBool(left->integer() <= right->integer());-                            } else if (left->type() == nString && right->type() == nString) {-                                v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));-                            } else {-                                state.error<EvalError>("type error in comparison").debugThrow();-                            }-                            break;-                        case BinaryOp::GE:-                            if (left->type() == nInt && right->type() == nInt) {-                                v.mkBool(left->integer() >= right->integer());-                            } else if (left->type() == nString && right->type() == nString) {-                                v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));-                            } else {-                                state.error<EvalError>("type error in comparison").debugThrow();-                            }-                            break;-                        case BinaryOp::CONCAT:-                            // ++ is list concatenation in Nix; string concat uses ADD (+)-                            state.error<EvalError>("list concatenation not yet implemented").debugThrow();-                            break;-                        default:-                            state.error<EvalError>("unknown binary operator").debugThrow();-                    }-                    break;-            }+    if (auto* n = node->get_if<ConstIntNode>()) {+      v.mkInt(n->value);+    } else if (auto* n = node->get_if<ConstStringNode>()) {+      v.mkString(n->value);+    } else if (auto* n = node->get_if<ConstPathNode>()) {+      v.mkPath(state.rootPath(CanonPath(n->value)));+    } else if (auto* n = node->get_if<ConstBoolNode>()) {+      v.mkBool(n->value);+    } else if (auto* n = node->get_if<ConstNullNode>()) {  // NOLINT(bugprone-branch-clone)+      v.mkNull();+    } else if (auto* n = node->get_if<VarNode>()) {+      Value* bound = env ? env->lookup(n->index) : nullptr;+      if (!bound && env && n->name.has_value()) {+        bound = env->lookup_with(state, n->name.value());+      }+      if (!bound) {+        state.error<EvalError>("variable not found").debugThrow();+      }+      force(bound);+      v = *bound;+    } else if (auto* n = node->get_if<LambdaNode>()) {+      auto lambda_env = env;+      auto body = n->body;++      auto primOp = [this, lambda_env, body](EvalState& state, PosIdx pos, Value** args,+                                             Value& result) {+        auto call_env = make_env(lambda_env);+        call_env->bind(args[0]);+        eval_node(body, result, call_env);+      };++      v.mkPrimOp(new PrimOp{.name = n->param_name.value_or("lambda"),+                            .arity = static_cast<size_t>(n->arity),+                            .fun = primOp});+    } else if (auto* n = node->get_if<AppNode>()) {+      Value* func_val = state.allocValue();+      eval_node(n->func, *func_val, env);+      force(func_val);++      Value* arg_val = make_thunk(n->arg, env);++      state.callFunction(*func_val, *arg_val, v, noPos);+    } else if (auto* n = node->get_if<BinaryOpNode>()) {+      Value* left = state.allocValue();+      Value* right = state.allocValue();++      switch (n->op) {+      case BinaryOp::AND:+        eval_node(n->left, *left, env);+        force(left);+        if (left->type() != nBool) {+          state.error<EvalError>("type error in logical AND").debugThrow();         }-        else if (auto* n = node->get_if<UnaryOpNode>()) {-            Value* operand = state.allocValue();-            eval_node(n->operand, *operand, env);-            force(operand);--            switch (n->op) {-                case UnaryOp::NEG:-                    if (operand->type() == nInt) {-                        v.mkInt((NixInt(0) - operand->integer()).valueWrapping());-                    } else {-                        state.error<EvalError>("type error in negation").debugThrow();-                    }-                    break;-                case UnaryOp::NOT:-                    if (operand->type() == nBool) {-                        v.mkBool(!operand->boolean());-                    } else {-                        state.error<EvalError>("type error in logical NOT").debugThrow();-                    }-                    break;-                default:-                    state.error<EvalError>("unknown unary operator").debugThrow();-            }+        if (!left->boolean()) {+          v.mkBool(false);+        } else {+          eval_node(n->right, *right, env);+          force(right);+          if (right->type() != nBool) {+            state.error<EvalError>("type error in logical AND").debugThrow();+          }+          v.mkBool(right->boolean());         }-        else if (auto* n = node->get_if<IfNode>()) {-            Value* cond = state.allocValue();-            eval_node(n->cond, *cond, env);-            force(cond);--            if (cond->type() != nBool) {-                state.error<EvalError>("condition must be a boolean").debugThrow();-            }--            if (cond->boolean()) {-                eval_node(n->then_branch, v, env);-            } else {-                eval_node(n->else_branch, v, env);-            }+        break;+      case BinaryOp::OR:+        eval_node(n->left, *left, env);+        force(left);+        if (left->type() != nBool) {+          state.error<EvalError>("type error in logical OR").debugThrow();         }-        else if (auto* n = node->get_if<LetNode>()) {-            auto let_env = make_env(env);-            for (const auto& [name, expr] : n->bindings) {-                Value* val = make_thunk(expr, env);-                let_env->bind(val);-            }-            eval_node(n->body, v, let_env);+        if (left->boolean()) {+          v.mkBool(true);+        } else {+          eval_node(n->right, *right, env);+          force(right);+          if (right->type() != nBool) {+            state.error<EvalError>("type error in logical OR").debugThrow();+          }+          v.mkBool(right->boolean());         }-        else if (auto* n = node->get_if<LetRecNode>()) {-            auto letrec_env = make_env(env);-            std::vector<Value*> thunk_vals;--            for (const auto& [name, expr] : n->bindings) {-                Value* val = make_thunk(expr, letrec_env);-                thunk_vals.push_back(val);-                letrec_env->bind(val);-            }--            eval_node(n->body, v, letrec_env);+        break;+      case BinaryOp::IMPL:+        eval_node(n->left, *left, env);+        force(left);+        if (left->type() != nBool) {+          state.error<EvalError>("type error in implication").debugThrow();         }-        else if (auto* n = node->get_if<AttrsetNode>()) {-            auto bindings = state.buildBindings(n->attrs.size());--            IREnvironment* attr_env = env;-            if (n->recursive) {-                attr_env = make_env(env);-                for (const auto& [key, val] : n->attrs) {-                    Value* thunk = make_thunk(val, attr_env);-                    attr_env->bind(thunk);-                }-            }--            for (const auto& [key, val] : n->attrs) {-                Value* attr_val = state.allocValue();-                if (n->recursive) {-                    eval_node(val, *attr_val, attr_env);-                } else {-                    eval_node(val, *attr_val, env);-                }-                bindings.insert(state.symbols.create(key), attr_val);-            }--            v.mkAttrs(bindings.finish());+        if (!left->boolean()) {+          v.mkBool(true);+        } else {+          eval_node(n->right, *right, env);+          force(right);+          if (right->type() != nBool) {+            state.error<EvalError>("type error in implication").debugThrow();+          }+          v.mkBool(right->boolean());         }-        else if (auto* n = node->get_if<SelectNode>()) {-            Value* obj = state.allocValue();-            eval_node(n->expr, *obj, env);-            force(obj);--            Value* attr_val = state.allocValue();-            eval_node(n->attr, *attr_val, env);-            force(attr_val);--            if (obj->type() != nAttrs) {-                state.error<EvalError>("selection on non-attrset").debugThrow();-            }--            if (attr_val->type() != nString) {-                state.error<EvalError>("attribute name must be string").debugThrow();-            }--            auto sym = state.symbols.create(attr_val->c_str());-            auto attr = obj->attrs()->get(sym);--            if (attr) {-                Value* val = attr->value;-                force(val);-                v = *val;-            } else if (n->default_expr) {-                eval_node(*n->default_expr, v, env);-            } else {-                state.error<EvalError>("attribute not found").debugThrow();+        break;+      default:+        eval_node(n->left, *left, env);+        eval_node(n->right, *right, env);+        force(left);+        force(right);++        switch (n->op) {+        case BinaryOp::ADD:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkInt((left->integer() + right->integer()).valueWrapping());+          } else if (left->type() == nString && right->type() == nString) {+            v.mkString(std::string(left->c_str()) + std::string(right->c_str()));+          } else {+            state.error<EvalError>("type error in addition").debugThrow();+          }+          break;+        case BinaryOp::SUB:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkInt((left->integer() - right->integer()).valueWrapping());+          } else {+            state.error<EvalError>("type error in subtraction").debugThrow();+          }+          break;+        case BinaryOp::MUL:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkInt((left->integer() * right->integer()).valueWrapping());+          } else {+            state.error<EvalError>("type error in multiplication").debugThrow();+          }+          break;+        case BinaryOp::DIV:+          if (left->type() == nInt && right->type() == nInt) {+            if (right->integer() == NixInt(0)) {+              state.error<EvalError>("division by zero").debugThrow();             }+            v.mkInt((left->integer() / right->integer()).valueWrapping());+          } else {+            state.error<EvalError>("type error in division").debugThrow();+          }+          break;+        case BinaryOp::EQ:+          v.mkBool(state.eqValues(*left, *right, noPos, ""));+          break;+        case BinaryOp::NE:+          v.mkBool(!state.eqValues(*left, *right, noPos, ""));+          break;+        case BinaryOp::LT:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkBool(left->integer() < right->integer());+          } else if (left->type() == nString && right->type() == nString) {+            v.mkBool(std::string(left->c_str()) < std::string(right->c_str()));+          } else {+            state.error<EvalError>("type error in comparison").debugThrow();+          }+          break;+        case BinaryOp::GT:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkBool(left->integer() > right->integer());+          } else if (left->type() == nString && right->type() == nString) {+            v.mkBool(std::string(left->c_str()) > std::string(right->c_str()));+          } else {+            state.error<EvalError>("type error in comparison").debugThrow();+          }+          break;+        case BinaryOp::LE:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkBool(left->integer() <= right->integer());+          } else if (left->type() == nString && right->type() == nString) {+            v.mkBool(std::string(left->c_str()) <= std::string(right->c_str()));+          } else {+            state.error<EvalError>("type error in comparison").debugThrow();+          }+          break;+        case BinaryOp::GE:+          if (left->type() == nInt && right->type() == nInt) {+            v.mkBool(left->integer() >= right->integer());+          } else if (left->type() == nString && right->type() == nString) {+            v.mkBool(std::string(left->c_str()) >= std::string(right->c_str()));+          } else {+            state.error<EvalError>("type error in comparison").debugThrow();+          }+          break;+        case BinaryOp::CONCAT:+          // ++ is list concatenation in Nix; string concat uses ADD (+)+          state.error<EvalError>("list concatenation not yet implemented").debugThrow();+          break;+        default:+          state.error<EvalError>("unknown binary operator").debugThrow();         }-        else if (auto* n = node->get_if<HasAttrNode>()) {-            Value* obj = state.allocValue();-            eval_node(n->expr, *obj, env);-            force(obj);--            Value* attr_val = state.allocValue();-            eval_node(n->attr, *attr_val, env);-            force(attr_val);--            if (obj->type() != nAttrs) {-                v.mkBool(false);-            } else if (attr_val->type() != nString) {-                state.error<EvalError>("attribute name must be string").debugThrow();-            } else {-                auto sym = state.symbols.create(attr_val->c_str());-                auto attr = obj->attrs()->get(sym);-                v.mkBool(attr != nullptr);-            }+        break;+      }+    } else if (auto* n = node->get_if<UnaryOpNode>()) {+      Value* operand = state.allocValue();+      eval_node(n->operand, *operand, env);+      force(operand);++      switch (n->op) {+      case UnaryOp::NEG:+        if (operand->type() == nInt) {+          v.mkInt((NixInt(0) - operand->integer()).valueWrapping());+        } else {+          state.error<EvalError>("type error in negation").debugThrow();         }-        else if (auto* n = node->get_if<WithNode>()) {-            Value* attrs = state.allocValue();-            eval_node(n->attrs, *attrs, env);-            force(attrs);--            if (attrs->type() != nAttrs) {-                state.error<EvalError>("with expression requires attrset").debugThrow();-            }--            auto with_env = make_env(env);-            with_env->with_attrs = attrs;-            eval_node(n->body, v, with_env);+        break;+      case UnaryOp::NOT:+        if (operand->type() == nBool) {+          v.mkBool(!operand->boolean());+        } else {+          state.error<EvalError>("type error in logical NOT").debugThrow();         }-        else if (auto* n = node->get_if<AssertNode>()) {-            Value* cond = state.allocValue();-            eval_node(n->cond, *cond, env);-            force(cond);--            if (cond->type() != nBool) {-                state.error<EvalError>("assertion must be boolean").debugThrow();-            }--            if (!cond->boolean()) {-                state.error<EvalError>("assertion failed").debugThrow();-            }--            eval_node(n->body, v, env);+        break;+      default:+        state.error<EvalError>("unknown unary operator").debugThrow();+      }+    } else if (auto* n = node->get_if<IfNode>()) {+      Value* cond = state.allocValue();+      eval_node(n->cond, *cond, env);+      force(cond);++      if (cond->type() != nBool) {+        state.error<EvalError>("condition must be a boolean").debugThrow();+      }++      if (cond->boolean()) {+        eval_node(n->then_branch, v, env);+      } else {+        eval_node(n->else_branch, v, env);+      }+    } else if (auto* n = node->get_if<LetNode>()) {+      auto let_env = make_env(env);+      for (const auto& [name, expr] : n->bindings) {+        Value* val = make_thunk(expr, env);+        let_env->bind(val);+      }+      eval_node(n->body, v, let_env);+    } else if (auto* n = node->get_if<LetRecNode>()) {+      auto letrec_env = make_env(env);+      std::vector<Value*> thunk_vals;++      for (const auto& [name, expr] : n->bindings) {+        Value* val = make_thunk(expr, letrec_env);+        thunk_vals.push_back(val);+        letrec_env->bind(val);+      }++      eval_node(n->body, v, letrec_env);+    } else if (auto* n = node->get_if<AttrsetNode>()) {+      auto bindings = state.buildBindings(n->attrs.size());++      IREnvironment* attr_env = env;+      if (n->recursive) {+        attr_env = make_env(env);+        for (const auto& [key, val] : n->attrs) {+          Value* thunk = make_thunk(val, attr_env);+          attr_env->bind(thunk);         }-        else {-            v.mkNull();+      }++      for (const auto& [key, val] : n->attrs) {+        Value* attr_val = state.allocValue();+        if (n->recursive) {+          eval_node(val, *attr_val, attr_env);+        } else {+          eval_node(val, *attr_val, env);         }+        bindings.insert(state.symbols.create(key), attr_val);+      }++      v.mkAttrs(bindings.finish());+    } else if (auto* n = node->get_if<SelectNode>()) {+      Value* obj = state.allocValue();+      eval_node(n->expr, *obj, env);+      force(obj);++      Value* attr_val = state.allocValue();+      eval_node(n->attr, *attr_val, env);+      force(attr_val);++      if (obj->type() != nAttrs) {+        state.error<EvalError>("selection on non-attrset").debugThrow();+      }++      if (attr_val->type() != nString) {+        state.error<EvalError>("attribute name must be string").debugThrow();+      }++      auto sym = state.symbols.create(attr_val->c_str());+      auto attr = obj->attrs()->get(sym);++      if (attr) {+        Value* val = attr->value;+        force(val);+        v = *val;+      } else if (n->default_expr) {+        eval_node(*n->default_expr, v, env);+      } else {+        state.error<EvalError>("attribute not found").debugThrow();+      }+    } else if (auto* n = node->get_if<HasAttrNode>()) {+      Value* obj = state.allocValue();+      eval_node(n->expr, *obj, env);+      force(obj);++      Value* attr_val = state.allocValue();+      eval_node(n->attr, *attr_val, env);+      force(attr_val);++      if (obj->type() != nAttrs) {+        v.mkBool(false);+      } else if (attr_val->type() != nString) {+        state.error<EvalError>("attribute name must be string").debugThrow();+      } else {+        auto sym = state.symbols.create(attr_val->c_str());+        auto attr = obj->attrs()->get(sym);+        v.mkBool(attr != nullptr);+      }+    } else if (auto* n = node->get_if<WithNode>()) {+      Value* attrs = state.allocValue();+      eval_node(n->attrs, *attrs, env);+      force(attrs);++      if (attrs->type() != nAttrs) {+        state.error<EvalError>("with expression requires attrset").debugThrow();+      }++      auto with_env = make_env(env);+      with_env->with_attrs = attrs;+      eval_node(n->body, v, with_env);+    } else if (auto* n = node->get_if<AssertNode>()) {+      Value* cond = state.allocValue();+      eval_node(n->cond, *cond, env);+      force(cond);++      if (cond->type() != nBool) {+        state.error<EvalError>("assertion must be boolean").debugThrow();+      }++      if (!cond->boolean()) {+        state.error<EvalError>("assertion failed").debugThrow();+      }++      eval_node(n->body, v, env);+    } else {+      v.mkNull();     }+  } };  Evaluator::Evaluator(EvalState& state) : pImpl(std::make_unique<Impl>(state)) {} Evaluator::~Evaluator() = default; -void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node,-                            Value& result,+void Evaluator::eval_to_nix(const std::shared_ptr<Node>& ir_node, Value& result,                             IREnvironment* env) {-    pImpl->eval_node(ir_node, result, env);+  pImpl->eval_node(ir_node, result, env); } -}+} // namespace nix_irc