//! 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)
},
)
}