-
Notifications
You must be signed in to change notification settings - Fork 1
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
Added the imports builtin #115
Conversation
@@ -22,6 +24,25 @@ export function getBuiltins() { | |||
); | |||
}, | |||
|
|||
import: (path) => { | |||
const pathStrict = path.toStrict(); |
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.
Not related to your PR, but just wondering if we can find another mechanism for lazy evaluation. Having to call this toStrict()
method is a bit cumbersome. Any ideas are welcome.
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.
Isn't this only invoked when an import is already being evaluated? Like an import invocation is only evaluated when the value of it is required, and when that value is required then the path also needs to be evaluated
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.
Oh wait I see what you mean
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.
I'll think about it, will let you know if I have ideas. Quickly looking over this right now, it feels like toStrict is the best option so far as we do need to explicitly differentiate lazy vs required, and I'm not sure what other options there are.
|
||
let import_module_attr = v8::String::new(scope, "importNixModule").unwrap(); | ||
let import_module_fn = v8::Function::new(scope, import_nix_module).unwrap(); | ||
global |
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.
Could you extract this bit of logic (the one that adds an object into the global scope) into a function, something like this:
fn add_to_js_scope(scope: ?, name: &str, js_object: ?) -> Result<(), String> {
...
}
Also, could you return a Result
with a descriptive error string (instead of using unwrap()
)?
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.
My next PR was going to be a full error handling refactor, is it ok if we skip the unwraps for this one? I was planning to make a proper error type that can be passed between js and rust, possibly with a full nix stack trace too
let source_v8 = to_v8_source(scope, &source_str, "<eval string>"); | ||
let module = v8::script_compiler::compile_module(scope, source_v8).unwrap(); | ||
let module_source_v8 = to_v8_source(scope, &source_str, "<eval string>"); | ||
let module = v8::script_compiler::compile_module(scope, module_source_v8).unwrap(); |
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.
For another day: It would be great to use some kind of caching here (to avoid re-emitting JS or even recompiling JS—with v8's code-caching ability).
out_src += "export const __nixrt = n;\n"; | ||
out_src += "export const __nixValue = (ctx) => "; | ||
let mut out_src = String::new(); | ||
out_src += "export default (ctx) => "; |
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.
Very nice simplification!
src/eval/execution.rs
Outdated
|
||
let resolve_module_callback = |_, _, _, _| panic!("Module resolution not supported."); |
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.
Does this mean you no longer use the resolve_module_callback
function at the bottom of the file? Can it be removed?
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.
Just fixed that, yeah, kept the function because there was a second place it was used (moved the panic there)
src/eval/execution.rs
Outdated
nix_value_from_module(scope, &namespace_obj) | ||
|
||
let nix_value_attr = v8::String::new(scope, "default").unwrap(); | ||
let Some(nix_value) = namespace_obj.get(scope, nix_value_attr.into()) else { |
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 you try using the try_get_js_object_key
here instead?
src/eval/execution.rs
Outdated
nix_value_from_module(scope, root_nix_fn, nixjs_rt_obj) | ||
} | ||
|
||
fn eval_nix_fn_from_string<'s>( |
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.
Nitpick: maybe a slightly different name for the function:
fn eval_nix_fn_from_string<'s>( | |
fn nix_expr_to_js_function<'s>( |
}; | ||
|
||
ret.set(nix_fn.into()); | ||
} |
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.
Very nice and concise! Great work! 🎉
|
||
let obj = module.get_module_namespace().to_object(scope).unwrap(); | ||
|
||
Ok(obj) |
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.
Inline obj?
Ok(obj) | |
Ok(module.get_module_namespace().to_object(scope).unwrap()) |
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.
Personally I often lean towards breaking up lines into variables to be more descriptive, e.g. if I add error handling in the future, I'd need to replace the .unwrap with something else and the line will grow further. Especially when it comes to layers of wrapping, with things like Some and Ok
|
||
let root_nix_fn = eval_nix_fn_from_string(scope, nix_expr)?; | ||
|
||
nix_value_from_module(scope, root_nix_fn, nixjs_rt_obj) |
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.
Maybe avoid passing around nixjs_rt_obj
(since it's already in root_nix_fn
's global scope)?
Also, can you maybe avoid passing around the scope
and get the scope from root_nix_fn
somehow?
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.
imo explicitly passing around nixjs_rt_obj
is better than manually extracting it from the global scope each time, we need to access it in rust anyway because the js_value_to_nix
function needs it. E.g. in the future if we move it out of the scope, or pass the runtime object another way, explicitly passing the rt object as an argument here will keep working.
As for scope, not sure, don't think there's a way.
.try_into() | ||
.expect("`n.toStrict` is not a function."); | ||
let to_strict_fn: v8::Local<v8::Function> = | ||
try_get_js_object_key(scope, &nixrt, "recursiveStrict")? |
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.
I wonder if recursiveStrict
is the right thing to call here. Actually, we should probably not call toStrict
either.
We should only force strict evaluation if the user really wants to see the entire value. For example, if the user requested only a specific attrpath like foo.bar.moo
, then only those values should be strictly evaluated.
I would suggest we move the strict evaluation out of this function, much higher up. What do you think? Should we move this "to strict" bit of logic to the evaluate
function?
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.
The nix_value_from_module
function is only called for the final value that's about to be sent over to Rust anyway, for the root module (only in the evaluate
function), not per-import. Imports are still lazy (I should make a test for that sometime).
I don't think this should be the responsibility of this code specifically. In the future, if we allow the evaluator to only evaluate a specific attrpath, then that should be transpiled into js imo rather than being manually done in rust.
#[test] | ||
fn test_eval_builtin_() { | ||
assert_eq!( | ||
eval_ok("(builtins.import ./flake.nix).description"), |
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.
Haha! Nice test and it's great to see it actually working! 🎉
Added
builtins.import
. In order to achieve this, I needed to refactor some other parts of the code.Changes include:
RIX_NIXRT_JS_MODULE
environment variable. Instead, the file is directly included viainclude_str!
.n
.default
export. That value is extracted from the namespace in rust.importNixModule
intrinsic is injected into the runtime, and it basically just opens a nix file, transpiles it, returns the resulting expression function.I used a lot of .unwrap, in the future we need to do proper error handling (like a full robust error handling solution with text styling within the errors). I just assumed that it's best to avoid overthinking that now.