about summary refs log blame commit diff
path: root/tvix/eval/src/compiler/import.rs
blob: 3a8847f2cbb4d6b39a5f0ce1ff81594b2f5b3257 (plain) (tree)








































































































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