about summary refs log tree commit diff
path: root/tvix/eval/src/compiler/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/compiler/mod.rs')
-rw-r--r--tvix/eval/src/compiler/mod.rs103
1 files changed, 82 insertions, 21 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 12fd269c2f9b..4a44f95691c0 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -14,6 +14,7 @@
 //! mistakes early during development.
 
 mod bindings;
+mod import;
 mod scope;
 
 use codemap::Span;
@@ -30,8 +31,9 @@ use crate::observer::CompilerObserver;
 use crate::opcode::{CodeIdx, Count, JumpOffset, OpCode, UpvalueIdx};
 use crate::spans::LightSpan;
 use crate::spans::ToSpan;
-use crate::value::{Closure, Formals, Lambda, Thunk, Value};
+use crate::value::{Closure, Formals, Lambda, NixAttrs, Thunk, Value};
 use crate::warnings::{EvalWarning, WarningKind};
+use crate::SourceCode;
 
 use self::scope::{LocalIdx, LocalPosition, Scope, Upvalue, UpvalueKind};
 
@@ -73,17 +75,38 @@ impl LambdaCtx {
     }
 }
 
+/// The type of a global as used inside of the compiler. Differs from
+/// Nix's own notion of "builtins" in that it can emit arbitrary code.
+/// Nix's builtins are wrapped inside of this type.
+pub type Global = Rc<dyn Fn(&mut Compiler, Span)>;
+
 /// The map of globally available functions that should implicitly
 /// be resolvable in the global scope.
-pub type GlobalsMap = HashMap<&'static str, Rc<dyn Fn(&mut Compiler, Span)>>;
-
-/// Functions with this type are used to construct a
-/// self-referential `builtins` object; it takes a weak reference to
-/// its own result, similar to how nixpkgs' overlays work.
-/// Rc::new_cyclic() is what "ties the knot".  The heap allocation
-/// (Box) and vtable (dyn) do not impair runtime or compile-time
-/// performance; they exist only during compiler startup.
-pub type GlobalsMapFunc = Box<dyn FnOnce(&Weak<GlobalsMap>) -> GlobalsMap>;
+type GlobalsMap = HashMap<&'static str, Rc<dyn Fn(&mut Compiler, Span)>>;
+
+/// Set of builtins that (if they exist) should be made available in
+/// the global scope, meaning that they can be accessed not just
+/// through `builtins.<name>`, but directly as `<name>`. This is not
+/// configurable, it is based on what Nix 2.3 exposed.
+const GLOBAL_BUILTINS: &'static [&'static str] = &[
+    "abort",
+    "baseNameOf",
+    "derivation",
+    "derivationStrict",
+    "dirOf",
+    "fetchGit",
+    "fetchMercurial",
+    "fetchTarball",
+    "fromTOML",
+    "import",
+    "isNull",
+    "map",
+    "placeholder",
+    "removeAttrs",
+    "scopedImport",
+    "throw",
+    "toString",
+];
 
 pub struct Compiler<'observer> {
     contexts: Vec<LambdaCtx>,
@@ -1183,19 +1206,57 @@ fn optimise_tail_call(chunk: &mut Chunk) {
     }
 }
 
-/// Prepare the full set of globals from additional globals supplied
-/// by the caller of the compiler, as well as the built-in globals
-/// that are always part of the language.  This also "ties the knot"
-/// required in order for import to have a reference cycle back to
-/// the globals.
+/// Prepare the full set of globals available in evaluated code. These
+/// are constructed from the set of builtins supplied by the caller,
+/// which are made available globally under the `builtins` identifier.
+///
+/// A subset of builtins (specified by [`GLOBAL_BUILTINS`]) is
+/// available globally *iff* they are set.
 ///
-/// Note that all builtin functions are *not* considered part of the
-/// language in this sense and MUST be supplied as additional global
-/// values, including the `builtins` set itself.
-pub fn prepare_globals(additional: GlobalsMapFunc) -> Rc<GlobalsMap> {
-    Rc::new_cyclic(Box::new(|weak: &Weak<GlobalsMap>| {
-        let mut globals = additional(weak);
+/// Optionally adds the `import` feature if desired by the caller.
+pub fn prepare_globals(
+    builtins: Vec<(&'static str, Value)>,
+    source: SourceCode,
+    enable_import: bool,
+) -> Rc<GlobalsMap> {
+    Rc::new_cyclic(Box::new(move |weak: &Weak<GlobalsMap>| {
+        // First step is to construct the builtins themselves as
+        // `NixAttrs`.
+        let mut builtins_under_construction: HashMap<&'static str, Value> =
+            HashMap::from_iter(builtins.into_iter());
+
+        // At this point, optionally insert `import` if enabled. To
+        // "tie the knot" of `import` needing the full set of globals
+        // to instantiate its compiler, the `Weak` reference is passed
+        // here.
+        if enable_import {
+            let import = Value::Builtin(import::builtins_import(weak, source));
+            builtins_under_construction.insert("import", import);
+        }
+
+        // Next, the actual map of globals is constructed and
+        // populated with (copies) of the values that should be
+        // available in the global scope (see [`GLOBAL_BUILTINS`]).
+        let mut globals: GlobalsMap = HashMap::new();
+
+        for global in GLOBAL_BUILTINS {
+            if let Some(builtin) = builtins_under_construction.get(global).cloned() {
+                let global_builtin: Global =
+                    Rc::new(move |c, s| c.emit_constant(builtin.clone(), &s));
+                globals.insert(global, global_builtin);
+            }
+        }
+
+        // This is followed by the actual `builtins` attribute set
+        // being constructed and inserted in the global scope.
+        let builtins_set =
+            Value::attrs(NixAttrs::from_iter(builtins_under_construction.into_iter()));
+        globals.insert(
+            "builtins",
+            Rc::new(move |c, s| c.emit_constant(builtins_set.clone(), &s)),
+        );
 
+        // Finally insert the compiler-internal "magic" builtins for top-level values.
         globals.insert(
             "true",
             Rc::new(|compiler, span| {