From e5662ba6525c27248d57d8265e9c6c3a46f95c7e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 5 Aug 2020 21:26:17 +0200 Subject: [PATCH] Add a flag to start the REPL on evaluation errors This allows interactively inspecting the state of the evaluator at the point of failure. Example: $ nix eval path:///home/eelco/Dev/nix/flake2#modules.hello-closure._final --start-repl-on-eval-errors error: --- TypeError -------------------------------------------------------------------------------------------------------------------------------------------------------------------- nix at: (20:53) in file: /nix/store/4264z41dxfdiqr95svmpnxxxwhfplhy0-source/flake.nix 19| 20| _final = builtins.foldl' (xs: mod: xs // (mod._module.config { config = _final; })) _defaults _allModules; | ^ 21| }; attempt to call something which is not a function but a set Starting REPL to allow you to inspect the current state of the evaluator. The following extra variables are in scope: arg, fun Welcome to Nix version 2.4. Type :? for help. nix-repl> fun error: --- EvalError -------------------------------------------------------------------------------------------------------------------------------------------------------------------- nix at: (150:28) in file: /nix/store/4264z41dxfdiqr95svmpnxxxwhfplhy0-source/flake.nix 149| 150| tarballClosure = (module { | ^ 151| extends = [ self.modules.derivation ]; attribute 'derivation' missing nix-repl> :t fun a set nix-repl> builtins.attrNames fun [ "tarballClosure" ] nix-repl> --- src/libexpr/eval.cc | 13 ++++++-- src/nix/command.cc | 24 ++++++++++++++ src/nix/command.hh | 8 +++++ src/nix/installables.cc | 7 ---- src/nix/repl.cc | 71 ++++++++++++++++++++++++++++------------- 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0123070d1..5d71e5466 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1171,6 +1171,8 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos) } } +std::function & env)> debuggerHook; + void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos) { auto trace = evalSettings.traceFunctionCalls ? std::make_unique(pos) : nullptr; @@ -1198,8 +1200,15 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po } } - if (fun.type != tLambda) - throwTypeError(pos, "attempt to call something which is not a function but %1%", fun); + if (fun.type != tLambda) { + auto error = TypeError({ + .hint = hintfmt("attempt to call something which is not a function but %1%", showType(fun)), + .errPos = pos + }); + if (debuggerHook) + debuggerHook(error, {{"fun", &fun}, {"arg", &arg}}); + throw error; + } ExprLambda & lambda(*fun.lambda.fun); diff --git a/src/nix/command.cc b/src/nix/command.cc index af36dda89..8b69948b6 100644 --- a/src/nix/command.cc +++ b/src/nix/command.cc @@ -31,6 +31,30 @@ void StoreCommand::run() run(getStore()); } +EvalCommand::EvalCommand() +{ + addFlag({ + .longName = "start-repl-on-eval-errors", + .description = "start an interactive environment if evaluation fails", + .handler = {&startReplOnEvalErrors, true}, + }); +} + +extern std::function & env)> debuggerHook; + +ref EvalCommand::getEvalState() +{ + if (!evalState) { + evalState = std::make_shared(searchPath, getStore()); + if (startReplOnEvalErrors) + debuggerHook = [evalState{ref(evalState)}](const Error & error, const std::map & env) { + printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error.what()); + runRepl(evalState, env); + }; + } + return ref(evalState); +} + StorePathsCommand::StorePathsCommand(bool recursive) : recursive(recursive) { diff --git a/src/nix/command.hh b/src/nix/command.hh index bc46a2028..fe1cd2799 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -36,8 +36,12 @@ private: struct EvalCommand : virtual StoreCommand, MixEvalArgs { + bool startReplOnEvalErrors = false; + ref getEvalState(); + EvalCommand(); + std::shared_ptr evalState; }; @@ -251,4 +255,8 @@ void printClosureDiff( const StorePath & afterPath, std::string_view indent); +void runRepl( + ref evalState, + const std::map & extraEnv); + } diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 59b52ce95..926cac2f8 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -234,13 +234,6 @@ void completeFlakeRefWithFragment( completeFlakeRef(evalState->store, prefix); } -ref EvalCommand::getEvalState() -{ - if (!evalState) - evalState = std::make_shared(searchPath, getStore()); - return ref(evalState); -} - void completeFlakeRef(ref store, std::string_view prefix) { if (prefix == "") diff --git a/src/nix/repl.cc b/src/nix/repl.cc index fb9050d0d..8409c7574 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -41,7 +41,7 @@ namespace nix { struct NixRepl : gc { string curDir; - std::unique_ptr state; + ref state; Bindings * autoArgs; Strings loadedFiles; @@ -54,7 +54,7 @@ struct NixRepl : gc const Path historyFile; - NixRepl(const Strings & searchPath, nix::ref store); + NixRepl(ref state); ~NixRepl(); void mainLoop(const std::vector & files); StringSet completePrefix(string prefix); @@ -65,13 +65,13 @@ struct NixRepl : gc void initEnv(); void reloadFiles(); void addAttrsToScope(Value & attrs); - void addVarToScope(const Symbol & name, Value & v); + void addVarToScope(const Symbol & name, Value * v); Expr * parseString(string s); void evalString(string s, Value & v); typedef set ValuesSeen; - std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); - std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); + std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); + std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); }; @@ -84,8 +84,8 @@ string removeWhitespace(string s) } -NixRepl::NixRepl(const Strings & searchPath, nix::ref store) - : state(std::make_unique(searchPath, store)) +NixRepl::NixRepl(ref state) + : state(state) , staticEnv(false, &state->staticBaseEnv) , historyFile(getDataDir() + "/nix/repl-history") { @@ -176,11 +176,13 @@ void NixRepl::mainLoop(const std::vector & files) string error = ANSI_RED "error:" ANSI_NORMAL " "; std::cout << "Welcome to Nix version " << nixVersion << ". Type :? for help." << std::endl << std::endl; - for (auto & i : files) - loadedFiles.push_back(i); + if (!files.empty()) { + for (auto & i : files) + loadedFiles.push_back(i); - reloadFiles(); - if (!loadedFiles.empty()) std::cout << std::endl; + reloadFiles(); + if (!loadedFiles.empty()) std::cout << std::endl; + } // Allow nix-repl specific settings in .inputrc rl_readline_name = "nix-repl"; @@ -516,10 +518,10 @@ bool NixRepl::processLine(string line) isVarName(name = removeWhitespace(string(line, 0, p)))) { Expr * e = parseString(string(line, p + 1)); - Value & v(*state->allocValue()); - v.type = tThunk; - v.thunk.env = env; - v.thunk.expr = e; + auto v = state->allocValue(); + v->type = tThunk; + v->thunk.env = env; + v->thunk.expr = e; addVarToScope(state->symbols.create(name), v); } else { Value v; @@ -577,17 +579,17 @@ void NixRepl::addAttrsToScope(Value & attrs) { state->forceAttrs(attrs); for (auto & i : *attrs.attrs) - addVarToScope(i.name, *i.value); + addVarToScope(i.name, i.value); std::cout << format("Added %1% variables.") % attrs.attrs->size() << std::endl; } -void NixRepl::addVarToScope(const Symbol & name, Value & v) +void NixRepl::addVarToScope(const Symbol & name, Value * v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); staticEnv.vars[name] = displ; - env->values[displ++] = &v; + env->values[displ++] = v; varNames.insert((string) name); } @@ -754,6 +756,26 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m return str; } +void runRepl( + ref evalState, + const std::map & extraEnv) +{ + auto repl = std::make_unique(evalState); + + repl->initEnv(); + + std::set names; + + for (auto & [name, value] : extraEnv) { + names.insert(ANSI_BOLD + name + ANSI_NORMAL); + repl->addVarToScope(repl->state->symbols.create(name), value); + } + + printError("The following extra variables are in scope: %s\n", concatStringsSep(", ", names)); + + repl->mainLoop({}); +} + struct CmdRepl : StoreCommand, MixEvalArgs { std::vector files; @@ -775,17 +797,20 @@ struct CmdRepl : StoreCommand, MixEvalArgs Examples examples() override { return { - Example{ - "Display all special commands within the REPL:", - "nix repl\n nix-repl> :?" - } + { + "Display all special commands within the REPL:", + "nix repl\n nix-repl> :?" + } }; } void run(ref store) override { evalSettings.pureEval = false; - auto repl = std::make_unique(searchPath, openStore()); + + auto evalState = make_ref(searchPath, store); + + auto repl = std::make_unique(evalState); repl->autoArgs = getAutoArgs(*repl->state); repl->mainLoop(files); }