Add :doc support for __functor

This commit is contained in:
Robert Hensing 2024-08-15 12:29:59 +02:00
parent 6068e32aa7
commit 72a4d1f52d
5 changed files with 232 additions and 0 deletions

View file

@ -616,6 +616,20 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
strdup(ss.data()),
};
}
if (isFunctor(v)) {
try {
Value & functor = *v.attrs()->find(sFunctor)->value;
Value * vp = &v;
Value partiallyApplied;
callFunction(functor, 1, &vp, partiallyApplied, noPos);
auto _level = addCallDepth(noPos);
return getDoc(partiallyApplied);
}
catch (Error & e) {
e.addTrace(nullptr, "while partially calling '%1%' to retrieve documentation", "__functor");
throw;
}
}
return {};
}

View file

@ -640,6 +640,12 @@ public:
const char * doc;
};
/**
* Retrieve the documentation for a value. This will evaluate the value if
* it is a thunk, and it will partially apply __functor if applicable.
*
* @param v The value to get the documentation for.
*/
std::optional<Doc> getDoc(Value & v);
private:

View file

@ -0,0 +1,101 @@
Nix <nix version>
Type :? for help.
nix-repl> :l doc-functor.nix
Added <number omitted> variables.
nix-repl> :doc multiplier
Function `__functor`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:12:23
Multiply the argument by the factor stored in the factor attribute.
nix-repl> :doc doubler
Function `multiply`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:5:17
Look, it's just like a function!
nix-repl> :doc recursive
Function `__functor`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:77:23
This looks bad, but the docs are ok because of the eta expansion.
nix-repl> :doc recursive2
error:
… while partially calling '__functor' to retrieve documentation
… while calling '__functor'
at /path/to/tests/functional/repl/doc-functor.nix:85:17:
84| */
85| __functor = self: self.__functor self;
| ^
86| };
… from call site
at /path/to/tests/functional/repl/doc-functor.nix:85:23:
84| */
85| __functor = self: self.__functor self;
| ^
86| };
(19999 duplicate frames omitted)
error: stack overflow; max-call-depth exceeded
at /path/to/tests/functional/repl/doc-functor.nix:85:23:
84| */
85| __functor = self: self.__functor self;
| ^
86| };
nix-repl> :doc diverging
error:
… while partially calling '__functor' to retrieve documentation
(10000 duplicate frames omitted)
… while calling '__functor'
at /path/to/tests/functional/repl/doc-functor.nix:97:19:
96| f = x: {
97| __functor = self: (f (x + 1));
| ^
98| };
error: stack overflow; max-call-depth exceeded
at /path/to/tests/functional/repl/doc-functor.nix:97:26:
96| f = x: {
97| __functor = self: (f (x + 1));
| ^
98| };
nix-repl> :doc helper
Function `square`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:36:12
Compute x^2
nix-repl> :doc helper2
Function `__functor`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:45:23
This is a function that can be overridden.
nix-repl> :doc lib.helper3
Function `__functor`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:45:23
This is a function that can be overridden.
nix-repl> :doc helper3
Function `__functor`\
… defined at /path/to/tests/functional/repl/doc-functor.nix:45:23
This is a function that can be overridden.

View file

@ -0,0 +1,10 @@
:l doc-functor.nix
:doc multiplier
:doc doubler
:doc recursive
:doc recursive2
:doc diverging
:doc helper
:doc helper2
:doc lib.helper3
:doc helper3

View file

@ -0,0 +1,101 @@
rec {
/**
Look, it's just like a function!
*/
multiply = p: q: p * q;
multiplier = {
factor = 2;
/**
Multiply the argument by the factor stored in the factor attribute.
*/
__functor = self: x: x * self.factor;
};
doubler = {
description = "bla";
/**
Multiply by two. This doc probably won't be rendered because the
returned partial application won't have any reference to this location;
only pointing to the second lambda in the multiply function.
*/
__functor = self: multiply 2;
};
makeOverridable = f: {
/**
This is a function that can be overridden.
*/
__functor = self: f;
override = throw "not implemented";
};
/**
Compute x^2
*/
square = x: x * x;
helper = makeOverridable square;
# Somewhat analogous to the Nixpkgs makeOverridable function.
makeVeryOverridable = f: {
/**
This is a function that can be overridden.
*/
__functor = self: arg: f arg // { override = throw "not implemented"; overrideAttrs = throw "not implemented"; };
override = throw "not implemented";
};
helper2 = makeVeryOverridable square;
# The RFC might be ambiguous here. The doc comment from makeVeryOverridable
# is "inner" in terms of values, but not inner in terms of expressions.
# Returning the following attribute comment might be allowed.
# TODO: I suppose we could look whether the attribute value expression
# contains a doc, and if not, return the attribute comment anyway?
/**
Compute x^3
*/
lib.helper3 = makeVeryOverridable (x: x * x * x);
/**
Compute x^3...
*/
helper3 = makeVeryOverridable (x: x * x * x);
# ------
# getDoc traverses a potentially infinite structure in case of __functor, so
# we need to test with recursive inputs and diverging inputs.
recursive = {
/**
This looks bad, but the docs are ok because of the eta expansion.
*/
__functor = self: x: self x;
};
recursive2 = {
/**
Docs probably won't work in this case, because the "partial" application
of self results in an infinite recursion.
*/
__functor = self: self.__functor self;
};
diverging = let
/**
Docs probably won't work in this case, because the "partial" application
of self results in an diverging computation that causes a stack overflow.
It's not an infinite recursion because each call is different.
This must be handled by the documentation retrieval logic, as it
reimplements the __functor invocation to be partial.
*/
f = x: {
__functor = self: (f (x + 1));
};
in f null;
}