Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add :doc command to nix repl #3904

Open
lf- opened this issue Aug 6, 2020 · 12 comments · May be fixed by #9054
Open

Feature: add :doc command to nix repl #3904

lf- opened this issue Aug 6, 2020 · 12 comments · May be fixed by #9054

Comments

@lf-
Copy link
Member

lf- commented Aug 6, 2020

Is your feature request related to a problem? Please describe.

I am always having to look up function documentation in the nixpkgs repository with either ripgrep or the tool I wrote because of this problem, nix-doc, but neither is perfect: ripgrep finds a lot of overridden copies of the function, and you still often have to jump between several files to decipher what's required to call a function.

By comparison, nix-doc uses a heuristic of looking in the AST for function definitions with comments above them to eliminate overrides, but this heuristic misses functions that are defined in their own file such as fetchFromGitHub, which is a problem I don't think I can practically solve in a tool that doesn't intend to understand the Nix language.

The Nix repl currently has the information to provide a better documentation experience as it knows where the actual function is defined through builtins.unsafeGetAttrPos, but it is not immediately obvious how it can nicely be extended to call some external tool to parse the nix file and find the comments above the function and output them.

Describe the solution you'd like

I want to have the following session in nix repl (also imagine it has colour):

nix-repl> nixpkgs = import <nixpkgs> {}
nix-repl> :doc nixpkgs.lib.composeExtensions
    Compose two extending functions of the type expected by 'extends'
    into one where changes made in the first are available in the
    'super' of the second
composeExtensions =  # /nix/store/...-nixpkgs/nixpkgs/lib/fixed-points.nix:74
    f: g: self: super: ...

Describe alternatives you've considered

It is /maybe/ possible to do this in nixpkgs, but you'd need a nix parser in nixpkgs OR make nix expose the comments in some attribute (discussed on IRC; plausible). Ergonomically it is probably superior to have this built into nix repl or to make nix repl support some kind of profile mechanism or other trick to add more :commands.

Additional context

cc @infinisil we discussed this on IRC in #nix-lang a couple of weeks ago

@lf- lf- added the improvement label Aug 6, 2020
@lf- lf- changed the title Feature: add :doc command to nix-repl Feature: add :doc command to nix repl Aug 6, 2020
@Ericson2314
Copy link
Member

Maybe let's start with :locate before we enshrine a inline documentation format in Nix itself?

@lf-
Copy link
Member Author

lf- commented Aug 6, 2020

@Ericson2314 if that is the preference, we should probably be making the repl extensible so we can push back the syntax/etc discussion about a documentation format while a prototype is made available. Currently it is possible to get the documentation of a function definition just by linearly traversing the AST backwards looking for comments and stopping once you hit non-trivia tokens, although this is not yet implemented in Nix to the best of my knowledge. This trivial solution might accidentally hit some comments that are not documentation, but that is perhaps a worthwhile sacrifice, since a real documentation comment syntax could be built on this feature in nixpkgs anyway, assuming it collects all the comments for a given function: a regex could be applied to catch all the ones in the "doc" format. I believe @infinisil sketched out some ideas for how the API for such a thing could work.

I have most of the bits to make a (not nearly as ergonomic/nice) prototype without any changes in Nix itself by shelling out to my own tool, but I don't have the C++ background to know how to begin on doing it natively. This would integrate into a custom commands feature well.

@infinisil
Copy link
Member

The idea I had was that Nix could be extended to have new builtins for adding annotations to expressions like:

builtins.withAnnotation <key> <value> <expression>
# ^^ Returns a value that behaves the same as <expression> but with the <key> annotation set to <value>

builtins.getAnnotation <key> <expression>
# ^^ Gets the <key> annotation of an expression. Returns null if none was set

This mechanism would allow adding annotations to expressions without changing their behavior, but the annotations can still be accessed later. Nix could then use this to automatically transform comments into annotations. E.g. the following code:

{
  # This does foo
  foo = "foo";
}

would be transformed to

{
  foo = builtins.withAnnotation "comment" "This does foo" "foo";
}

Or maybe even

{
  foo = builtins.withAnnotation "attributeName" "foo" (builtins.withAnnotation "comment" "This does foo" "foo");
}

And tools like nix repl could then extract this documentation with builtins.getAnnotation "comment"/"attributeName" and transform it/present it however they like.

@lf-
Copy link
Member Author

lf- commented Aug 7, 2020

I assume that trick is fine for purity since unless you are putting annotations into your derivations or something, changing an annotation wouldn't result in different output?

Looks good in any case!

@lf-
Copy link
Member Author

lf- commented Aug 8, 2020

I am poking around on implementing this without extending Nix and there is a flaw that makes this effort sort of a futile exercise: it doesn't appear that positions of functions are exposed to Nix expressions. It's still possible to use unsafeGetAttrPos, but that requires that functions are attributes in a set, and it doesn't find where the function actually is defined if it is e.g. imported from another file, instead only finding where the attribute position is defined.

This feature appears to require support in Nix itself.

lf- added a commit to lf-/nix that referenced this issue Aug 8, 2020
This is useful for a potential pure-Nix implementation of NixOS#3904.
@lf-
Copy link
Member Author

lf- commented Aug 8, 2020

Update: I may or may not have made irresponsible choices tonight but I did write a prototype nix plugin that adds the documentation functionality as an (evil and bad) builtin using the existing nix-doc parsing infrastructure (unfortunately plugins cannot extend the REPL).

In doing so, I realized something that is going to be problematic for any possible solution: Nix makes it really easy to compose functions everywhere, which means that it is often very hard to find the actual source of the function because so many functions are wrapped. I was not expecting to have issues with this when I have access to the actual objects by running in-process in Nix, but here I am: take the example from above of fetchFromGitHub:

nix-repl> n=import <nixpkgs> {}

nix-repl> builtins.doc n.fetchFromGitHub
error: (string):1:1: value is a set while a lambda was expected

# oops, it's a __functor thing, whatever, let's just point it at the functor

nix-repl> builtins.doc n.fetchFromGitHub.__functor
   TODO: Should we add call-time "type" checking like built in?
func = self: ...
# /nix/store/nm5fxk0kzm3mlx1c22byfs4jizajwbk1-nixpkgs-20.09pre237349.f9f48250fe1/nixpkgs/lib/trivial.nix:318
null

# oops, that's definitely not the documentation for fetchFromGitHub, and where is this source from?

# ... oh. Crap.
#   setFunctionArgs = f: args:
#    { # TODO: Should we add call-time "type" checking like built in?
#      __functor = self: f;
#      __functionArgs = args;
#    };

# I also ported this to my plugin for fun
nix-repl> builtins.unsafeGetLambdaPos n.fetchFromGitHub.__functor
{ column = 19; file = "/nix/store/nm5fxk0kzm3mlx1c22byfs4jizajwbk1-nixpkgs-20.09pre237349.f9f48250fe1/nixpkgs/lib/trivial.nix"; line = 318; }

Edit: If you want to try out this prototype that shows the limitations of a trivial solution, it's available at https://github.com/lf-/nix-doc.

@roberth
Copy link
Member

roberth commented Aug 9, 2020

This pr could be relevant #1652. It was a bit of a hack though.

@lf-
Copy link
Member Author

lf- commented Aug 9, 2020

@roberth gee, the desire to have this feature really is everpresent haha. I really don't want it to die by bikeshedding this time around, as it is incredibly useful to have as a feature, even if a simple implementation returns some garbage sometimes.

I will say, my implementation cannot die to the bikeshed because it cannot in good conscience ever be merged ;-)

@lf-
Copy link
Member Author

lf- commented Aug 22, 2020

Update:

Well, I made it happen: lf-/nix-doc@21df582 plus #3934 constitute essentially a full implementation of the proposal above.

This bug is, in my view, still not fixed because it realistically probably should be in Nix itself, and the documentation markup language bikeshed has not been painted yet to the best of my knowledge. Also, we need a good way of dealing with wrapper functions, such as fetchFromGitHub, above, which is arguably a Nix core thing.

[nix-shell:~/dev/nix-doc]$ ../nix/result/bin/nix --option plugin-files ./target/debug/libnix_doc_plugin.so repl
Welcome to Nix version 3.0pre19700101_a5e02e7. Type :? for help.

nix-repl> :?
The following commands are available:

  <expr>        Evaluate and print expression
  <x> = <expr>  Bind expression to variable
  :a <expr>     Add attributes from resulting set to scope
  :b <expr>     Build derivation
  :e <expr>     Open the derivation in $EDITOR
  :i <expr>     Build derivation, then install result into current profile
  :l <path>     Load Nix expression and add it to scope
  :p <expr>     Evaluate and print expression recursively
  :q            Exit nix-repl
  :r            Reload all files
  :s <expr>     Build dependencies of derivation, then start nix-shell
  :t <expr>     Describe result of evaluation
  :u <expr>     Build derivation, then start nix-shell
  :doc <expr>   Get the `nix-doc` documentation for <expr>

nix-repl> n = import <nixpkgs> {}

nix-repl> :doc n.lib.fix
   Compute the fixed point of the given function `f`, which is usually an
   attribute set that expects its final, non-recursive representation as an
   argument:
   
       f = self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
   
   Nix evaluates this recursion until all references to `self` have been
   resolved. At that point, the final result is returned and `f x = x` holds:
   
       nix-repl> fix f
       { bar = "bar"; foo = "foo"; foobar = "foobar"; }
   
    Type: fix :: (a -> a) -> a
   
   See https://en.wikipedia.org/wiki/Fixed-point_combinator for further
   details.
func = f: ...
# /nix/store/s9rnnyqd9xcc2rvr0wicshvd9v84ibx9-nixpkgs-20.09pre239330.beb9180019b/nixpkgs/lib/fixed-points.nix:19

nix-repl> 

@edolstra
Copy link
Member

We now have a :doc command (on the markdown branch), which can show the documentation for builtin functions:

$ nix repl
Welcome to Nix version 3.0pre20200825_7a02865. Type :? for help.

nix-repl> :doc map
    Synopsis: builtins.map f list

    Apply the function f to each element in the list list. For example,

      map (x: "foo" + x) [ "bar" "bla" "abc" ]

    evaluates to [ "foobar" "foobla" "fooabc" ].

This could be extended by docstring annotations on functions, however we'll have to think about whether this is the right way to improve discoverability in Nix. Adding something like the NixOS module system as a language feature would get us documentation for free (see https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d).

@lf-
Copy link
Member Author

lf- commented Aug 26, 2020

@edolstra I find that although there is existing html documentation for the library and built-in functions, I can't use it very effectively since it's on a massive page so ctrl-f gets a bunch of extraneous results, the page takes a couple of seconds to jump to the section on load, etc. I can keep my focus much better by doing it within Nix (from experience with my plugin), so even if this is resolved, it would be disappointing to have html documentation as the only option. Further, often the web based nixpkgs library documentation is not enough because I'm working in the Haskell infrastructure which largely isn't documented except in the source so I just have to read the source code regardless.

Another thing that's been extremely valuable about my plugin is that it dumps parameter names, which is the thing I really struggle with most of the time: "what do I have to give this thing". This is, in my view, more useful than the annotation.

What I would also enjoy is the ability to add custom commands, like one that directly brings up the given function in my editor, figuring out where it's actually defined and going directly to the line. #3934 is bringing out the big guns for this, allowing pretty much anything to be written, but it would also be good to be able to somehow write commands in something more accessible to users than the partially documented C++ API.

Nix will have trouble with go to definition since we have a lot of generic function wrappers in nixpkgs that end up hiding where the actual implementation I'm looking for is defined (so it's better to get the definition site of the attribute, where the wrapper is called, rather than the lambda). I'd like it if I could traverse these with a documentation tool somehow, maybe through tracing through them? The specific big use case I'm eyeing is being able to jump directly to the Nix file for a given package in nixpkgs, though there's also some wrapped library functions that benefit from this.

@domenkozar
Copy link
Member

Relevant: https://www.unisonweb.org/docs/documentation/

@edolstra edolstra added this to the nix-2.4 milestone Feb 12, 2021
@edolstra edolstra modified the milestones: nix-2.4, nix-3.0 Aug 9, 2021
@edolstra edolstra modified the milestones: nix-2.5, nix-2.6 Dec 2, 2021
@edolstra edolstra modified the milestones: nix-2.6, nix-2.7 Jan 21, 2022
@edolstra edolstra modified the milestones: nix-2.7, nix-2.8 Mar 3, 2022
@edolstra edolstra modified the milestones: nix-2.8, nix-2.9 Apr 14, 2022
@edolstra edolstra modified the milestones: nix-2.9, nix-2.10 May 27, 2022
@edolstra edolstra added this to the nix-2.11 milestone Jul 11, 2022
@edolstra edolstra modified the milestones: nix-2.11, nix-2.12 Aug 25, 2022
@edolstra edolstra modified the milestones: nix-2.12, nix-2.13 Dec 6, 2022
@edolstra edolstra modified the milestones: nix-2.13, nix-2.14 Jan 17, 2023
@edolstra edolstra modified the milestones: nix-2.14, nix-2.15 Feb 28, 2023
@edolstra edolstra modified the milestones: nix-2.15, nix-2.16, nix-2.17 May 26, 2023
@edolstra edolstra modified the milestones: nix-2.17, nix-2.18 Jul 24, 2023
@edolstra edolstra modified the milestones: nix-2.18, nix-2.19 Sep 20, 2023
@inclyc inclyc linked a pull request Sep 27, 2023 that will close this issue
@edolstra edolstra modified the milestones: nix-2.19, nix-2.20 Nov 20, 2023
@edolstra edolstra modified the milestones: nix-2.20, nix-2.21 Mar 4, 2024
@edolstra edolstra modified the milestones: nix-2.21, nix-2.22 Mar 11, 2024
@edolstra edolstra modified the milestones: nix-2.22, nix-2.23 Apr 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants