Skip to content

Commit

Permalink
Add a flag to start the REPL on evaluation errors
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
edolstra committed Aug 5, 2020
1 parent b3e7354 commit e5662ba
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 32 deletions.
13 changes: 11 additions & 2 deletions src/libexpr/eval.cc
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
24 changes: 24 additions & 0 deletions src/nix/command.cc
Expand Up @@ -31,6 +31,30 @@ void StoreCommand::run()
run(getStore());
}

EvalCommand::EvalCommand()
{
addFlag({
.longName = "start-repl-on-eval-errors",

This comment has been minimized.

Copy link
@Mic92

Mic92 Aug 9, 2020

Member

It's a bit long. Maybe something like?

         .longName = "repl-on-error",
.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)
{
Expand Down
8 changes: 8 additions & 0 deletions src/nix/command.hh
Expand Up @@ -36,8 +36,12 @@ private:

struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
bool startReplOnEvalErrors = false;

ref<EvalState> getEvalState();

EvalCommand();

std::shared_ptr<EvalState> evalState;
};

Expand Down Expand Up @@ -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);

}
7 changes: 0 additions & 7 deletions src/nix/installables.cc
Expand Up @@ -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 == "")
Expand Down
71 changes: 48 additions & 23 deletions src/nix/repl.cc
Expand Up @@ -41,7 +41,7 @@ namespace nix {
struct NixRepl : gc
{
string curDir;
std::unique_ptr<EvalState> state;
ref<EvalState> state;
Bindings * autoArgs;

Strings loadedFiles;
Expand All @@ -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);
Expand All @@ -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);
};


Expand All @@ -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")
{
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down

0 comments on commit e5662ba

Please sign in to comment.