-
-
Notifications
You must be signed in to change notification settings - Fork 40
add evaluator #38
base: master
Are you sure you want to change the base?
add evaluator #38
Conversation
Ok(self.eval()?.as_map()?.keys().cloned().collect()) | ||
} | ||
|
||
fn hash_uncached(&self) -> Result<String, EvalError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hashing (explained in the PR description) is an ugly hack at the moment. This PR is mostly just to show how integration with an evaluator would work if rnix-lsp wanted to do so.
Oh for context, see the working proof of concept with all features enabled: |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/best-ide-for-writing-nix-expressions/13454/9 |
Does maybe @tazjin also want to have a look at it? |
To clarify, this PR is way too large to properly review its code. But if we're on board with the general idea (the PR description along with the general structure of the diff), I'd be happy to make smaller PRs that would be easier to review and discuss each step of the way |
Oh, we also might want to consider exposing the functionality of this PR as a Rust crate, since it seems like it would be generally helpful for the Nix community to have a way to evaluate and inspect Nix code without shelling out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, incredible, awesome work! ❤️
Below are a few comments from a first review, though I haven't thorougly tested it yet.
Perhaps it make sense to split these changes across a bunch of PRs? If so, I could start by submitting a PR with an arithmetic-only-evaluator then follow-up with PRs to slowly add back functionality until we can evaluate Nixpkgs again.
I think splitting this into multiple commits or suggesting where to start when reviewing might help :)
It uses the gc crate for garbage collection.
Perhaps I'm missing something obvious, but can you explain, why this is needed? I'm not against it, but I doubt that I fully understood the why behind this decision :)
let a: &NixValue = x.borrow(); | ||
a.clone() | ||
}))), | ||
Err(_) => map.insert("value".to_string(), Gc::new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
builtins.tryEval
does only catch errors from throw
and assert
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. We'll want a error type for user-generated errors. This will also help if we later to do the red squiggley diagnostic thing to show exactly which user input (e.g. a nixos option value) is causing an assertion to fail somewhere else.
.replace(")", "\\)") | ||
.replace("%%PIPE%%", "|") | ||
.replace("%%L_PAREN%%", "(") | ||
.replace("%%R_PAREN%%", ")"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind elaborating here what's this about? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah this is a very hacky way to handle this. I don't want this hack in rnix-lsp, so I'll need to replace it before putting it in any PRs I want merged
"unsafeGetAttrPos <?>" ; UnsafeGetAttrPos1(_0: String) => |_param: Gc<Tree>, _attr: &String| { | ||
let mut map = HashMap::new(); | ||
map.insert("column".to_string(), Gc::new(Tree::from_concrete( | ||
NixValue::Integer(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a little bit unexpected. Why not adding an unimplemented
-error here? Or is this used too often to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, iirc unsafeGetAttrPor
is required for evaluating nixpkgs, so I needed to "implement" it to see if an evaluating lsp is even possible. Like many things, I will make sure to provide an actual implementation before putting it in any non-draft PRs to rnix-lsp.
"compareVersions" ; CompareVersions => |param| { | ||
Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::CompareVersions1(param))))) | ||
} | ||
"compareVersions <?>" ; CompareVersions1(_0: Gc<Tree>) => |param: Gc<Tree>, left: &Gc<Tree>| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we use some kind of semver crate in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, although we'll want to make sure we're using the exact same logic as official Nix. I'll make sure to link to the relevant section in the Nix manual in code comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Semver crates would be likely not bug-compatible with nix itself.
@@ -14,6 +11,8 @@ use lazy_static::lazy_static; | |||
|
|||
use std::{process, str}; | |||
use regex; | |||
use gc::Gc; | |||
use crate::scope::Scope; | |||
|
|||
lazy_static! { | |||
static ref BUILTINS: Vec<String> = vec![ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(note for later: we should replace this list by reading the keys from builtins.rs
.
Err(e) => format!("{}", e), | ||
}; | ||
Some((Some(range), val)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we can load attr keys here as well: should we use this procedure for the completions as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much. In nix-eval-lsp
, the only major difference is some code at the end pushing completions to a list:
Note that we'll want to merge rnix-lsp logic with evaluator logic to allow graceful fallback to static analysis when the evaluator gets confused.
Oh definitely. For this PR, I recommend focusing on mostly the description. For actually reviewing code, I think it would make sense for me to make a PR that only handles arithmetic, would would make the code much easier to review. After that's merged, I could make PRs adding more features in small steps. |
Also to clarify, I don't think the code itself is ready to be merged. But I wanted to make sure we're both on board with the structure before I spend significant time making the implementation do exactly what we want. |
No worries. The official Nix evaluator using a tracing garbage collector, and the Nix language is designed for garbage collection. I originally tried to implement this with borrowing and let
fix = (f: let x = f x; in x);
attrs = (self: { x = 1; y = self.x; });
in
(fix attrs).x I also found this helpful blog post about garbage collectors in Rust, written by the author of the |
The overall structure makes sense to me, however I'm not an expert on how
I think such an incremental progress where we (or at least I'd be up for that) could dogfood the changes by using them locally makes sense. My next task here would be to finish incremental parsing (I think I found a small bug), but let me know if I can help! |
@Ma27 do you want to adopt this project? You could also maintain this temporary until we find someone else. |
@Mic92 Aaron and I already have commit access and we decided that they will become the "official" LSP maintainer and I'll become the "official" rnix-parser maintainer even though we'll work on both projects ;) |
I updated the repo description to reflect that. |
Status update: I'm busy for the next few days, but next week I'll submit a PR that does basic arithmetic. I think that should be more helpful for discussion than this draft PR |
71d1d23
to
92ba7ee
Compare
Is there any blockage concerning this PR ? I'm very interested since I use Nix everyday, what is the current state of this PR ? |
Related to #31.
I created this PR to show what integration with an evaluator will look like. We'll also have to figure out how to review all this code (maybe split it across many PRs?), if we want to merge it at all.
This particular PR adds hover-to-see-value functionality. it's requires very little modification to
main.rs
. Using the evaluator for completions and goto-definition would similarly require very little modification tomain.rs
.How it works
The evaluator works by associating rnix-parser text ranges with lazy Nix values. It uses the gc crate for garbage collection.
The heart of the evaluator is the following struct, with comments added:
Files
tests.rs
has a few basic tests for the evaluator.value.rs
definesNixValue
, which holds Nix primitive types such as int/string/bool.scope.rs
definesScope
, which handles scoping for Nix variables.parse.rs
converts a rnix-parser node into aTree
, a struct with associates a range of text with a lazily-evaluated NixValue and scope.eval.rs
converts a Tree's lazy value placeholder to a concreteNixValue
.builtins.rs
uses macros to define a massiveNixBuiltin
enum. This is where all Nix builtins are implemented.derivation.nix
is a written-from-scratch implementation of nixpkgs/nix's derivation.nix, which defines aderivation
function that callsderivationStrict
. Note that the code looks similar due to needing to create sets with the same attribute names; the implementations themselves work in entirely different ways.Code review
Perhaps it make sense to split these changes across a bunch of PRs? If so, I could start by submitting a PR with an arithmetic-only-evaluator then follow-up with PRs to slowly add back functionality until we can evaluate Nixpkgs again.