-
-
Couldn't load subscription status.
- Fork 41
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.
| use crate::scope::Scope; | ||
|
|
||
| lazy_static! { | ||
| static ref BUILTINS: Vec<String> = vec
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.rshas a few basic tests for the evaluator.value.rsdefinesNixValue, which holds Nix primitive types such as int/string/bool.scope.rsdefinesScope, which handles scoping for Nix variables.parse.rsconverts a rnix-parser node into aTree, a struct with associates a range of text with a lazily-evaluated NixValue and scope.eval.rsconverts a Tree's lazy value placeholder to a concreteNixValue.builtins.rsuses macros to define a massiveNixBuiltinenum. This is where all Nix builtins are implemented.derivation.nixis a written-from-scratch implementation of nixpkgs/nix's derivation.nix, which defines aderivationfunction 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.