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>
This commit is contained in:
Eelco Dolstra 2020-08-05 21:26:17 +02:00
parent b3e73547a0
commit e5662ba652
5 changed files with 91 additions and 32 deletions

View file

@ -1171,6 +1171,8 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
}
}
std::function<void(const Error & error, const std::map<std::string, Value *> & env)> debuggerHook;
void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
{
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(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);

View file

@ -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<void(const Error & error, const std::map<std::string, Value *> & env)> debuggerHook;
ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState) {
evalState = std::make_shared<EvalState>(searchPath, getStore());
if (startReplOnEvalErrors)
debuggerHook = [evalState{ref<EvalState>(evalState)}](const Error & error, const std::map<std::string, Value *> & 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>(evalState);
}
StorePathsCommand::StorePathsCommand(bool recursive)
: recursive(recursive)
{

View file

@ -36,8 +36,12 @@ private:
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
bool startReplOnEvalErrors = false;
ref<EvalState> getEvalState();
EvalCommand();
std::shared_ptr<EvalState> evalState;
};
@ -251,4 +255,8 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
void runRepl(
ref<EvalState> evalState,
const std::map<std::string, Value *> & extraEnv);
}

View file

@ -234,13 +234,6 @@ void completeFlakeRefWithFragment(
completeFlakeRef(evalState->store, prefix);
}
ref<EvalState> EvalCommand::getEvalState()
{
if (!evalState)
evalState = std::make_shared<EvalState>(searchPath, getStore());
return ref<EvalState>(evalState);
}
void completeFlakeRef(ref<Store> store, std::string_view prefix)
{
if (prefix == "")

View file

@ -41,7 +41,7 @@ namespace nix {
struct NixRepl : gc
{
string curDir;
std::unique_ptr<EvalState> state;
ref<EvalState> state;
Bindings * autoArgs;
Strings loadedFiles;
@ -54,7 +54,7 @@ struct NixRepl : gc
const Path historyFile;
NixRepl(const Strings & searchPath, nix::ref<Store> store);
NixRepl(ref<EvalState> state);
~NixRepl();
void mainLoop(const std::vector<std::string> & 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<Value *> 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> store)
: state(std::make_unique<EvalState>(searchPath, store))
NixRepl::NixRepl(ref<EvalState> state)
: state(state)
, staticEnv(false, &state->staticBaseEnv)
, historyFile(getDataDir() + "/nix/repl-history")
{
@ -176,11 +176,13 @@ void NixRepl::mainLoop(const std::vector<std::string> & 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> evalState,
const std::map<std::string, Value *> & extraEnv)
{
auto repl = std::make_unique<NixRepl>(evalState);
repl->initEnv();
std::set<std::string> 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<std::string> 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> store) override
{
evalSettings.pureEval = false;
auto repl = std::make_unique<NixRepl>(searchPath, openStore());
auto evalState = make_ref<EvalState>(searchPath, store);
auto repl = std::make_unique<NixRepl>(evalState);
repl->autoArgs = getAutoArgs(*repl->state);
repl->mainLoop(files);
}