about summary refs log tree commit diff
path: root/tvix/eval/src/compiler/import.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/compiler/import.rs')
-rw-r--r--tvix/eval/src/compiler/import.rs105
1 files changed, 105 insertions, 0 deletions
diff --git a/tvix/eval/src/compiler/import.rs b/tvix/eval/src/compiler/import.rs
new file mode 100644
index 0000000000..3a8847f2cb
--- /dev/null
+++ b/tvix/eval/src/compiler/import.rs
@@ -0,0 +1,105 @@
+//! This module implements the Nix language's `import` feature, which
+//! is exposed as a builtin in the Nix language.
+//!
+//! This is not a typical builtin, as it needs access to internal
+//! compiler and VM state (such as the [`crate::SourceCode`]
+//! instance, or observers).
+
+use std::rc::Weak;
+
+use crate::{
+    observer::NoOpObserver,
+    value::{Builtin, BuiltinArgument, Thunk},
+    vm::VM,
+    ErrorKind, SourceCode, Value,
+};
+
+use super::GlobalsMap;
+use crate::builtins::coerce_value_to_path;
+
+/// Constructs and inserts the `import` builtin. This builtin is special in that
+/// it needs to capture the [crate::SourceCode] structure to correctly track
+/// source code locations while invoking a compiler.
+// TODO: need to be able to pass through a CompilationObserver, too.
+// TODO: can the `SourceCode` come from the compiler?
+pub(super) fn builtins_import(globals: &Weak<GlobalsMap>, source: SourceCode) -> Builtin {
+    // This (very cheap, once-per-compiler-startup) clone exists
+    // solely in order to keep the borrow checker happy.  It
+    // resolves the tension between the requirements of
+    // Rc::new_cyclic() and Builtin::new()
+    let globals = globals.clone();
+
+    Builtin::new(
+        "import",
+        &[BuiltinArgument {
+            strict: true,
+            name: "path",
+        }],
+        None,
+        move |mut args: Vec<Value>, vm: &mut VM| {
+            let mut path = coerce_value_to_path(&args.pop().unwrap(), vm)?;
+            if path.is_dir() {
+                path.push("default.nix");
+            }
+
+            let current_span = vm.current_light_span();
+
+            if let Some(cached) = vm.import_cache.get(&path) {
+                return Ok(cached.clone());
+            }
+
+            let contents = vm.io().read_to_string(path.clone())?;
+
+            let parsed = rnix::ast::Root::parse(&contents);
+            let errors = parsed.errors();
+
+            let file = source.add_file(path.to_string_lossy().to_string(), contents);
+
+            if !errors.is_empty() {
+                return Err(ErrorKind::ImportParseError {
+                    path,
+                    file,
+                    errors: errors.to_vec(),
+                });
+            }
+
+            let result = crate::compiler::compile(
+                &parsed.tree().expr().unwrap(),
+                Some(path.clone()),
+                file,
+                // The VM must ensure that a strong reference to the
+                // globals outlives any self-references (which are
+                // weak) embedded within the globals.  If the
+                // expect() below panics, it means that did not
+                // happen.
+                globals
+                    .upgrade()
+                    .expect("globals dropped while still in use"),
+                &mut NoOpObserver::default(),
+            )
+            .map_err(|err| ErrorKind::ImportCompilerError {
+                path: path.clone(),
+                errors: vec![err],
+            })?;
+
+            if !result.errors.is_empty() {
+                return Err(ErrorKind::ImportCompilerError {
+                    path,
+                    errors: result.errors,
+                });
+            }
+
+            // Compilation succeeded, we can construct a thunk from whatever it spat
+            // out and return that.
+            let res = Value::Thunk(Thunk::new_suspended(result.lambda, current_span));
+
+            vm.import_cache.insert(path, res.clone());
+
+            for warning in result.warnings {
+                vm.push_warning(warning);
+            }
+
+            Ok(res)
+        },
+    )
+}