diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index a5ebd1bf0..4f2fd36c1 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -563,6 +563,25 @@ Value & EvalState::getBuiltin(const string & name) } +std::optional EvalState::getDoc(Value & v) +{ + if (v.type == tPrimOp || v.type == tPrimOpApp) { + auto v2 = &v; + while (v2->type == tPrimOpApp) + v2 = v2->primOpApp.left; + if (v2->primOp->doc) + return Doc { + .pos = noPos, + .name = v2->primOp->name, + .arity = v2->primOp->arity, + .args = v2->primOp->args, + .doc = v2->primOp->doc, + }; + } + return {}; +} + + /* Every "format" object (even temporary) takes up a few hundred bytes of stack space, which is a real killer in the recursive evaluator. So here are some helper functions for throwing diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index db8eb3e16..80078d8a5 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -248,6 +248,17 @@ public: Value & getBuiltin(const string & name); + struct Doc + { + Pos pos; + std::optional name; + size_t arity; + std::vector args; + const char * doc; + }; + + std::optional getDoc(Value & v); + private: inline Value * lookupVar(Env * env, const ExprVar & var, bool noEval); diff --git a/src/nix/repl.cc b/src/nix/repl.cc index c7f8af742..8ecf11900 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -514,23 +514,22 @@ bool NixRepl::processLine(string line) else if (command == ":doc") { Value v; evalString(arg, v); - if (v.type == tPrimOp || v.type == tPrimOpApp) { - auto v2 = &v; - while (v2->type == tPrimOpApp) - v2 = v2->primOpApp.left; - if (v2->primOp->doc) { - auto args = v2->primOp->args; + if (auto doc = state->getDoc(v)) { + std::string markdown; + + if (!doc->args.empty() && doc->name) { + auto args = doc->args; for (auto & arg : args) arg = "*" + arg + "*"; - auto markdown = - "**Synopsis:** `builtins." + (std::string) v2->primOp->name + "` " - + concatStringsSep(" ", args) + "\n\n" - + trim(stripIndentation(v2->primOp->doc)); + markdown += + "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` " + + concatStringsSep(" ", args) + "\n\n"; + } - std::cout << renderMarkdownToTerminal(markdown); - } else - throw Error("builtin function '%s' does not have documentation", v2->primOp->name); + markdown += trim(stripIndentation(doc->doc)); + + std::cout << renderMarkdownToTerminal(markdown); } else throw Error("value does not have documentation"); }