sui-bytecode 0.1.215

Bytecode compiler and VM for the sui Rust-native Nix evaluator
Documentation
//! Builtin bridge: allows the bytecode VM to call tree-walker builtins.
//!
//! The VM has native implementations for ~103 builtins, but nixpkgs needs
//! ~119+. Instead of reimplementing every builtin in the VM's value system,
//! this module provides a callback mechanism that delegates to the
//! tree-walker's builtins for any builtin the VM doesn't handle natively.
//!
//! # Architecture
//!
//! Follows the same pattern as `set_flake_resolver` in `vm.rs`:
//! a thread-local callback with an RAII guard for cleanup.
//!
//! ```text
//! sui-eval (BytecodeEvaluator)
//!//!   ├─ set_builtin_bridge(callback)  ← installs bridge before VM eval
//!//!   └─ VM execution
//!//!       └─ CallBuiltin for "getEnv" etc.
//!//!           └─ BuiltinRegistry stub → call_builtin_bridge("getEnv", args)
//!//!               └─ callback converts VMValue↔Value, calls tree-walker builtin
//! ```

use std::cell::RefCell;

use crate::value::StringKeyedValue;

/// Callback type for bridging tree-walker builtins into the VM.
///
/// Takes: builtin name, args as `StringKeyedValue` (interner-free).
/// Returns: `StringKeyedValue` result or error string.
///
/// We use `StringKeyedValue` instead of `VMValue` because:
/// - It doesn't require an interner for key resolution
/// - `sui-eval` already has `string_keyed_to_eval` / `eval_to_string_keyed`
///   conversion functions
/// - The bridge callback runs in `sui-eval` context where the tree-walker
///   value types are available
pub type BuiltinBridgeFn = Box<dyn Fn(&str, Vec<StringKeyedValue>) -> Result<StringKeyedValue, String>>;

thread_local! {
    static BUILTIN_BRIDGE: RefCell<Option<BuiltinBridgeFn>> = const { RefCell::new(None) };
}

/// Install a builtin bridge callback for the current thread.
///
/// Returns an RAII guard that restores the previous bridge on drop.
/// This ensures the bridge is always properly cleaned up even when
/// evaluation errors occur.
pub fn set_builtin_bridge(bridge: BuiltinBridgeFn) -> BuiltinBridgeGuard {
    let prev = BUILTIN_BRIDGE.with(|b| b.borrow_mut().replace(bridge));
    BuiltinBridgeGuard { _prev: prev }
}

/// RAII guard that restores the previous builtin bridge on drop.
pub struct BuiltinBridgeGuard {
    _prev: Option<BuiltinBridgeFn>,
}

impl Drop for BuiltinBridgeGuard {
    fn drop(&mut self) {
        let prev = self._prev.take();
        BUILTIN_BRIDGE.with(|b| *b.borrow_mut() = prev);
    }
}

/// Call the builtin bridge for a named builtin.
///
/// Returns:
/// - `Ok(Some(result))` if the bridge handled the builtin
/// - `Ok(None)` if no bridge is installed (caller should error)
/// - `Err(msg)` if the bridge returned an error
pub fn call_builtin_bridge(
    name: &str,
    args: Vec<StringKeyedValue>,
) -> Result<Option<StringKeyedValue>, String> {
    BUILTIN_BRIDGE.with(|b| {
        let borrow = b.borrow();
        if let Some(ref bridge) = *borrow {
            bridge(name, args).map(Some)
        } else {
            Ok(None) // No bridge set
        }
    })
}

// ── Path materializer ──────────────────────────────────────────────────
//
// WHY THIS EXISTS (measured 2026-08-17): a fetched flake input is NAMED by a
// `/nix/store/<narhash>-source` path that sui never writes to disk — the bytes
// live in the fetcher cache (`~/.cache/sui/inputs/…`). sui-eval's tree-walker
// redirects every filesystem READ through `crate::path::materialize`, keeping
// the store-path STRING byte-correct while the read lands on the real tree.
//
// The VM had NO such redirect: its `pathExists`/`readFile`/`readFileType` and
// `import` called `std::fs` on the raw store name, so a flake input's files
// read as ABSENT. That is silent — `pathExists` answers `false`, no error is
// raised, and the VM's per-file fallback to the tree-walker structurally
// cannot fire on a wrong-but-successful answer.
//
// `sui-bytecode` CANNOT depend on `sui-eval` (see this crate's Cargo.toml: the
// dev-dep is path-only to break a publish cycle), so the redirect arrives the
// same way the builtin bridge does — a thread-local callback installed by
// sui-eval, with an RAII guard. Bridgeless (`tests/parity.rs`, the benches),
// `materialize` is the IDENTITY and the VM behaves exactly as before.

/// Callback type for redirecting a filesystem path before it is read.
///
/// Takes the path the evaluator *names*; returns the path the read should
/// actually land on. Must be the identity for any path it does not own.
pub type PathMaterializerFn = Box<dyn Fn(&str) -> String>;

thread_local! {
    static PATH_MATERIALIZER: RefCell<Option<PathMaterializerFn>> = const { RefCell::new(None) };
}

/// Install a path materializer for the current thread.
///
/// Returns an RAII guard that restores the previous materializer on drop.
pub fn set_path_materializer(materializer: PathMaterializerFn) -> PathMaterializerGuard {
    let prev = PATH_MATERIALIZER.with(|m| m.borrow_mut().replace(materializer));
    PathMaterializerGuard { _prev: prev }
}

/// RAII guard that restores the previous path materializer on drop.
pub struct PathMaterializerGuard {
    _prev: Option<PathMaterializerFn>,
}

impl Drop for PathMaterializerGuard {
    fn drop(&mut self) {
        let prev = self._prev.take();
        PATH_MATERIALIZER.with(|m| *m.borrow_mut() = prev);
    }
}

/// Redirect a path through the installed materializer.
///
/// The IDENTITY when no materializer is installed — the VM must keep working
/// standalone, so this is never allowed to fail or to change a path it does
/// not own. This is a FILESYSTEM-READ-ONLY redirect: the result is used to
/// touch disk, never to build a value the evaluator observes.
#[must_use]
pub fn materialize(path: &str) -> String {
    PATH_MATERIALIZER.with(|m| {
        let borrow = m.borrow();
        match *borrow {
            Some(ref f) => f(path),
            None => path.to_string(),
        }
    })
}

/// `materialize` for a `&Path`, returning an owned `PathBuf`.
#[must_use]
pub fn materialize_path(path: &std::path::Path) -> std::path::PathBuf {
    std::path::PathBuf::from(materialize(&path.to_string_lossy()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn no_materializer_is_the_identity() {
        // The bridgeless VM (parity.rs, the benches) must see paths unchanged.
        assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
        assert_eq!(materialize("relative/path.nix"), "relative/path.nix");
    }

    #[test]
    fn materializer_redirects_and_guard_restores() {
        {
            let _guard = set_path_materializer(Box::new(|p: &str| {
                p.replace("/nix/store/abc-source", "/cache/abc")
            }));
            assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/cache/abc/flake.nix");
            // A path the materializer does not own passes through untouched.
            assert_eq!(materialize("/etc/nix/nix.conf"), "/etc/nix/nix.conf");
        }
        // Guard dropped — back to the identity.
        assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
    }

    #[test]
    fn materializer_guard_restores_previous() {
        let _outer = set_path_materializer(Box::new(|_: &str| "/outer".to_string()));
        {
            let _inner = set_path_materializer(Box::new(|_: &str| "/inner".to_string()));
            assert_eq!(materialize("/x"), "/inner");
        }
        assert_eq!(materialize("/x"), "/outer");
    }

    #[test]
    fn materialize_path_roundtrips_through_pathbuf() {
        let _guard = set_path_materializer(Box::new(|p: &str| p.replace("/store", "/real")));
        assert_eq!(
            materialize_path(std::path::Path::new("/store/f.nix")),
            std::path::PathBuf::from("/real/f.nix")
        );
    }

    #[test]
    fn no_bridge_returns_none() {
        let result = call_builtin_bridge("getEnv", vec![StringKeyedValue::String("HOME".into())]);
        assert!(matches!(result, Ok(None)));
    }

    #[test]
    fn bridge_handles_call() {
        let _guard = set_builtin_bridge(Box::new(|name, args| {
            assert_eq!(name, "getEnv");
            match &args[0] {
                StringKeyedValue::String(s) => {
                    Ok(StringKeyedValue::String(format!("mocked:{s}")))
                }
                _ => Err("expected string".into()),
            }
        }));

        let result = call_builtin_bridge(
            "getEnv",
            vec![StringKeyedValue::String("HOME".into())],
        );
        assert_eq!(
            result.unwrap().unwrap(),
            StringKeyedValue::String("mocked:HOME".into())
        );
    }

    #[test]
    fn bridge_error_propagates() {
        let _guard = set_builtin_bridge(Box::new(|_, _| {
            Err("bridge error".into())
        }));

        let result = call_builtin_bridge("anything", vec![]);
        assert_eq!(result.unwrap_err(), "bridge error");
    }

    #[test]
    fn guard_clears_bridge_on_drop() {
        {
            let _guard = set_builtin_bridge(Box::new(|_, _| {
                Ok(StringKeyedValue::Null)
            }));
            assert!(matches!(
                call_builtin_bridge("x", vec![]),
                Ok(Some(StringKeyedValue::Null))
            ));
        }
        // After guard drops, bridge should be cleared
        assert!(matches!(call_builtin_bridge("x", vec![]), Ok(None)));
    }

    // -- set_builtin_bridge installs callback --------------------------

    #[test]
    fn set_builtin_bridge_installs_callback() {
        let _guard = set_builtin_bridge(Box::new(|name, _| {
            Ok(StringKeyedValue::String(format!("handled:{name}")))
        }));
        let result = call_builtin_bridge("myBuiltin", vec![]);
        assert_eq!(
            result.unwrap().unwrap(),
            StringKeyedValue::String("handled:myBuiltin".into())
        );
    }

    // -- RAII guard clears callback on drop -----------------------------

    #[test]
    fn raii_guard_restores_previous_bridge() {
        // Install first bridge.
        let _outer = set_builtin_bridge(Box::new(|_, _| {
            Ok(StringKeyedValue::String("outer".into()))
        }));
        {
            // Install inner bridge (replaces outer temporarily).
            let _inner = set_builtin_bridge(Box::new(|_, _| {
                Ok(StringKeyedValue::String("inner".into()))
            }));
            let result = call_builtin_bridge("x", vec![]);
            assert_eq!(
                result.unwrap().unwrap(),
                StringKeyedValue::String("inner".into())
            );
        }
        // Inner guard dropped — outer bridge should be restored.
        let result = call_builtin_bridge("x", vec![]);
        assert_eq!(
            result.unwrap().unwrap(),
            StringKeyedValue::String("outer".into())
        );
    }

    // -- call_builtin_bridge returns None when no bridge ----------------

    #[test]
    fn call_builtin_bridge_returns_none_when_no_bridge() {
        // Ensure no bridge is installed (clean state after guard drop).
        {
            let _guard = set_builtin_bridge(Box::new(|_, _| Ok(StringKeyedValue::Null)));
        }
        let result = call_builtin_bridge("nonexistent", vec![]);
        assert!(matches!(result, Ok(None)));
    }

    // -- call_builtin_bridge returns Some when bridge set ---------------

    #[test]
    fn call_builtin_bridge_returns_some_when_bridge_set() {
        let _guard = set_builtin_bridge(Box::new(|_, _| {
            Ok(StringKeyedValue::Int(42))
        }));
        let result = call_builtin_bridge("anything", vec![]);
        assert!(result.is_ok());
        assert!(result.unwrap().is_some());
    }

    // -- bridge with simple string argument and return -----------------

    #[test]
    fn bridge_with_string_argument_and_return() {
        let _guard = set_builtin_bridge(Box::new(|name, args| {
            assert_eq!(name, "echo");
            match &args[0] {
                StringKeyedValue::String(s) => {
                    Ok(StringKeyedValue::String(format!("echo:{s}")))
                }
                _ => Err("expected string arg".into()),
            }
        }));
        let result = call_builtin_bridge(
            "echo",
            vec![StringKeyedValue::String("hello".into())],
        );
        assert_eq!(
            result.unwrap().unwrap(),
            StringKeyedValue::String("echo:hello".into())
        );
    }

    // -- bridge with attrset argument ----------------------------------

    #[test]
    fn bridge_with_attrset_argument() {
        let _guard = set_builtin_bridge(Box::new(|name, args| {
            assert_eq!(name, "inspect");
            match &args[0] {
                StringKeyedValue::Attrs(map) => {
                    let keys: Vec<&String> = map.keys().collect();
                    Ok(StringKeyedValue::Int(keys.len() as i64))
                }
                _ => Err("expected attrset".into()),
            }
        }));

        let mut attrs = std::collections::BTreeMap::new();
        attrs.insert("a".to_string(), StringKeyedValue::Int(1));
        attrs.insert("b".to_string(), StringKeyedValue::Int(2));
        attrs.insert("c".to_string(), StringKeyedValue::Int(3));

        let result = call_builtin_bridge(
            "inspect",
            vec![StringKeyedValue::Attrs(attrs)],
        );
        assert_eq!(result.unwrap().unwrap(), StringKeyedValue::Int(3));
    }
}