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) void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & pos)
{ {
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr; 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) if (fun.type != tLambda) {
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun); 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); ExprLambda & lambda(*fun.lambda.fun);

View file

@ -31,6 +31,30 @@ void StoreCommand::run()
run(getStore()); 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) StorePathsCommand::StorePathsCommand(bool recursive)
: recursive(recursive) : recursive(recursive)
{ {

View file

@ -36,8 +36,12 @@ private:
struct EvalCommand : virtual StoreCommand, MixEvalArgs struct EvalCommand : virtual StoreCommand, MixEvalArgs
{ {
bool startReplOnEvalErrors = false;
ref<EvalState> getEvalState(); ref<EvalState> getEvalState();
EvalCommand();
std::shared_ptr<EvalState> evalState; std::shared_ptr<EvalState> evalState;
}; };
@ -251,4 +255,8 @@ void printClosureDiff(
const StorePath & afterPath, const StorePath & afterPath,
std::string_view indent); 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); 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) void completeFlakeRef(ref<Store> store, std::string_view prefix)
{ {
if (prefix == "") if (prefix == "")

View file

@ -41,7 +41,7 @@ namespace nix {
struct NixRepl : gc struct NixRepl : gc
{ {
string curDir; string curDir;
std::unique_ptr<EvalState> state; ref<EvalState> state;
Bindings * autoArgs; Bindings * autoArgs;
Strings loadedFiles; Strings loadedFiles;
@ -54,7 +54,7 @@ struct NixRepl : gc
const Path historyFile; const Path historyFile;
NixRepl(const Strings & searchPath, nix::ref<Store> store); NixRepl(ref<EvalState> state);
~NixRepl(); ~NixRepl();
void mainLoop(const std::vector<std::string> & files); void mainLoop(const std::vector<std::string> & files);
StringSet completePrefix(string prefix); StringSet completePrefix(string prefix);
@ -65,7 +65,7 @@ struct NixRepl : gc
void initEnv(); void initEnv();
void reloadFiles(); void reloadFiles();
void addAttrsToScope(Value & attrs); void addAttrsToScope(Value & attrs);
void addVarToScope(const Symbol & name, Value & v); void addVarToScope(const Symbol & name, Value * v);
Expr * parseString(string s); Expr * parseString(string s);
void evalString(string s, Value & v); void evalString(string s, Value & v);
@ -84,8 +84,8 @@ string removeWhitespace(string s)
} }
NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store) NixRepl::NixRepl(ref<EvalState> state)
: state(std::make_unique<EvalState>(searchPath, store)) : state(state)
, staticEnv(false, &state->staticBaseEnv) , staticEnv(false, &state->staticBaseEnv)
, historyFile(getDataDir() + "/nix/repl-history") , 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 " "; string error = ANSI_RED "error:" ANSI_NORMAL " ";
std::cout << "Welcome to Nix version " << nixVersion << ". Type :? for help." << std::endl << std::endl; std::cout << "Welcome to Nix version " << nixVersion << ". Type :? for help." << std::endl << std::endl;
if (!files.empty()) {
for (auto & i : files) for (auto & i : files)
loadedFiles.push_back(i); loadedFiles.push_back(i);
reloadFiles(); reloadFiles();
if (!loadedFiles.empty()) std::cout << std::endl; if (!loadedFiles.empty()) std::cout << std::endl;
}
// Allow nix-repl specific settings in .inputrc // Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl"; rl_readline_name = "nix-repl";
@ -516,10 +518,10 @@ bool NixRepl::processLine(string line)
isVarName(name = removeWhitespace(string(line, 0, p)))) isVarName(name = removeWhitespace(string(line, 0, p))))
{ {
Expr * e = parseString(string(line, p + 1)); Expr * e = parseString(string(line, p + 1));
Value & v(*state->allocValue()); auto v = state->allocValue();
v.type = tThunk; v->type = tThunk;
v.thunk.env = env; v->thunk.env = env;
v.thunk.expr = e; v->thunk.expr = e;
addVarToScope(state->symbols.create(name), v); addVarToScope(state->symbols.create(name), v);
} else { } else {
Value v; Value v;
@ -577,17 +579,17 @@ void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs); state->forceAttrs(attrs);
for (auto & i : *attrs.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; 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) if (displ >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
staticEnv.vars[name] = displ; staticEnv.vars[name] = displ;
env->values[displ++] = &v; env->values[displ++] = v;
varNames.insert((string) name); varNames.insert((string) name);
} }
@ -754,6 +756,26 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
return str; 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 struct CmdRepl : StoreCommand, MixEvalArgs
{ {
std::vector<std::string> files; std::vector<std::string> files;
@ -775,7 +797,7 @@ struct CmdRepl : StoreCommand, MixEvalArgs
Examples examples() override Examples examples() override
{ {
return { return {
Example{ {
"Display all special commands within the REPL:", "Display all special commands within the REPL:",
"nix repl\n nix-repl> :?" "nix repl\n nix-repl> :?"
} }
@ -785,7 +807,10 @@ struct CmdRepl : StoreCommand, MixEvalArgs
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
evalSettings.pureEval = false; 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->autoArgs = getAutoArgs(*repl->state);
repl->mainLoop(files); repl->mainLoop(files);
} }