snix_eval/compiler/
import.rs

1//! This module implements the Nix language's `import` feature, which
2//! is exposed as a builtin in the Nix language.
3//!
4//! This is not a typical builtin, as it needs access to internal
5//! compiler and VM state (such as the [`crate::SourceCode`]
6//! instance, or observers).
7
8use super::GlobalsMap;
9use genawaiter::rc::Gen;
10use std::rc::Weak;
11
12use crate::{
13    builtins::coerce_value_to_path,
14    generators::pin_generator,
15    observer::NoOpObserver,
16    value::{Builtin, Thunk},
17    vm::generators::{self, GenCo},
18    ErrorKind, SourceCode, Value,
19};
20
21async fn import_impl(
22    co: GenCo,
23    globals: Weak<GlobalsMap>,
24    source: SourceCode,
25    mut args: Vec<Value>,
26) -> Result<Value, ErrorKind> {
27    // TODO(sterni): canon_path()?
28    let mut path = match coerce_value_to_path(&co, args.pop().unwrap()).await? {
29        Err(cek) => return Ok(Value::Catchable(Box::new(cek))),
30        Ok(path) => path,
31    };
32
33    if path.is_dir() {
34        path.push("default.nix");
35    }
36
37    if let Some(cached) = generators::request_import_cache_lookup(&co, path.clone()).await {
38        return Ok(cached);
39    }
40
41    let mut reader = generators::request_open_file(&co, path.clone()).await;
42    // We read to a String instead of a Vec<u8> because rnix only supports
43    // string source files.
44    let mut contents = String::new();
45    reader.read_to_string(&mut contents)?;
46
47    let parsed = rnix::ast::Root::parse(&contents);
48    let errors = parsed.errors();
49    let file = source.add_file(path.to_string_lossy().to_string(), contents.to_owned());
50
51    if !errors.is_empty() {
52        return Err(ErrorKind::ImportParseError {
53            path,
54            file,
55            errors: errors.to_vec(),
56        });
57    }
58
59    let result = crate::compiler::compile(
60        &parsed.tree().expr().unwrap(),
61        Some(path.clone()),
62        // The VM must ensure that a strong reference to the globals outlives
63        // any self-references (which are weak) embedded within the globals. If
64        // the expect() below panics, it means that did not happen.
65        globals
66            .upgrade()
67            .expect("globals dropped while still in use"),
68        None,
69        &source,
70        &file,
71        &mut NoOpObserver::default(),
72    )
73    .map_err(|err| ErrorKind::ImportCompilerError {
74        path: path.clone(),
75        errors: vec![err],
76    })?;
77
78    if !result.errors.is_empty() {
79        return Err(ErrorKind::ImportCompilerError {
80            path,
81            errors: result.errors,
82        });
83    }
84
85    for warning in result.warnings {
86        generators::emit_warning(&co, warning).await;
87    }
88
89    // Compilation succeeded, we can construct a thunk from whatever it spat
90    // out and return that.
91    let res = Value::Thunk(Thunk::new_suspended(
92        result.lambda,
93        generators::request_span(&co).await,
94    ));
95
96    generators::request_import_cache_put(&co, path, res.clone()).await;
97
98    Ok(res)
99}
100
101/// Constructs the `import` builtin. This builtin is special in that
102/// it needs to capture the [crate::SourceCode] structure to correctly
103/// track source code locations while invoking a compiler.
104// TODO: need to be able to pass through a CompilationObserver, too.
105// TODO: can the `SourceCode` come from the compiler?
106pub(super) fn builtins_import(globals: &Weak<GlobalsMap>, source: SourceCode) -> Builtin {
107    // This (very cheap, once-per-compiler-startup) clone exists
108    // solely in order to keep the borrow checker happy.  It
109    // resolves the tension between the requirements of
110    // Rc::new_cyclic() and Builtin::new()
111    let globals = globals.clone();
112
113    Builtin::new(
114        "import",
115        Some("Import the given file and return the Nix value it evaluates to"),
116        1,
117        move |args| {
118            Gen::new(|co| pin_generator(import_impl(co, globals.clone(), source.clone(), args)))
119        },
120    )
121}