Skip to main content

sui_bytecode/
bridge.rs

1//! Builtin bridge: allows the bytecode VM to call tree-walker builtins.
2//!
3//! The VM has native implementations for ~103 builtins, but nixpkgs needs
4//! ~119+. Instead of reimplementing every builtin in the VM's value system,
5//! this module provides a callback mechanism that delegates to the
6//! tree-walker's builtins for any builtin the VM doesn't handle natively.
7//!
8//! # Architecture
9//!
10//! Follows the same pattern as `set_flake_resolver` in `vm.rs`:
11//! a thread-local callback with an RAII guard for cleanup.
12//!
13//! ```text
14//! sui-eval (BytecodeEvaluator)
15//!   │
16//!   ├─ set_builtin_bridge(callback)  ← installs bridge before VM eval
17//!   │
18//!   └─ VM execution
19//!       │
20//!       └─ CallBuiltin for "getEnv" etc.
21//!           │
22//!           └─ BuiltinRegistry stub → call_builtin_bridge("getEnv", args)
23//!               │
24//!               └─ callback converts VMValue↔Value, calls tree-walker builtin
25//! ```
26
27use std::cell::RefCell;
28
29use crate::value::StringKeyedValue;
30
31/// Callback type for bridging tree-walker builtins into the VM.
32///
33/// Takes: builtin name, args as `StringKeyedValue` (interner-free).
34/// Returns: `StringKeyedValue` result or error string.
35///
36/// We use `StringKeyedValue` instead of `VMValue` because:
37/// - It doesn't require an interner for key resolution
38/// - `sui-eval` already has `string_keyed_to_eval` / `eval_to_string_keyed`
39///   conversion functions
40/// - The bridge callback runs in `sui-eval` context where the tree-walker
41///   value types are available
42pub type BuiltinBridgeFn = Box<dyn Fn(&str, Vec<StringKeyedValue>) -> Result<StringKeyedValue, String>>;
43
44thread_local! {
45    static BUILTIN_BRIDGE: RefCell<Option<BuiltinBridgeFn>> = const { RefCell::new(None) };
46}
47
48/// Install a builtin bridge callback for the current thread.
49///
50/// Returns an RAII guard that restores the previous bridge on drop.
51/// This ensures the bridge is always properly cleaned up even when
52/// evaluation errors occur.
53pub fn set_builtin_bridge(bridge: BuiltinBridgeFn) -> BuiltinBridgeGuard {
54    let prev = BUILTIN_BRIDGE.with(|b| b.borrow_mut().replace(bridge));
55    BuiltinBridgeGuard { _prev: prev }
56}
57
58/// RAII guard that restores the previous builtin bridge on drop.
59pub struct BuiltinBridgeGuard {
60    _prev: Option<BuiltinBridgeFn>,
61}
62
63impl Drop for BuiltinBridgeGuard {
64    fn drop(&mut self) {
65        let prev = self._prev.take();
66        BUILTIN_BRIDGE.with(|b| *b.borrow_mut() = prev);
67    }
68}
69
70/// Call the builtin bridge for a named builtin.
71///
72/// Returns:
73/// - `Ok(Some(result))` if the bridge handled the builtin
74/// - `Ok(None)` if no bridge is installed (caller should error)
75/// - `Err(msg)` if the bridge returned an error
76pub fn call_builtin_bridge(
77    name: &str,
78    args: Vec<StringKeyedValue>,
79) -> Result<Option<StringKeyedValue>, String> {
80    BUILTIN_BRIDGE.with(|b| {
81        let borrow = b.borrow();
82        if let Some(ref bridge) = *borrow {
83            bridge(name, args).map(Some)
84        } else {
85            Ok(None) // No bridge set
86        }
87    })
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn no_bridge_returns_none() {
96        let result = call_builtin_bridge("getEnv", vec![StringKeyedValue::String("HOME".into())]);
97        assert!(matches!(result, Ok(None)));
98    }
99
100    #[test]
101    fn bridge_handles_call() {
102        let _guard = set_builtin_bridge(Box::new(|name, args| {
103            assert_eq!(name, "getEnv");
104            match &args[0] {
105                StringKeyedValue::String(s) => {
106                    Ok(StringKeyedValue::String(format!("mocked:{s}")))
107                }
108                _ => Err("expected string".into()),
109            }
110        }));
111
112        let result = call_builtin_bridge(
113            "getEnv",
114            vec![StringKeyedValue::String("HOME".into())],
115        );
116        assert_eq!(
117            result.unwrap().unwrap(),
118            StringKeyedValue::String("mocked:HOME".into())
119        );
120    }
121
122    #[test]
123    fn bridge_error_propagates() {
124        let _guard = set_builtin_bridge(Box::new(|_, _| {
125            Err("bridge error".into())
126        }));
127
128        let result = call_builtin_bridge("anything", vec![]);
129        assert_eq!(result.unwrap_err(), "bridge error");
130    }
131
132    #[test]
133    fn guard_clears_bridge_on_drop() {
134        {
135            let _guard = set_builtin_bridge(Box::new(|_, _| {
136                Ok(StringKeyedValue::Null)
137            }));
138            assert!(matches!(
139                call_builtin_bridge("x", vec![]),
140                Ok(Some(StringKeyedValue::Null))
141            ));
142        }
143        // After guard drops, bridge should be cleared
144        assert!(matches!(call_builtin_bridge("x", vec![]), Ok(None)));
145    }
146
147    // -- set_builtin_bridge installs callback --------------------------
148
149    #[test]
150    fn set_builtin_bridge_installs_callback() {
151        let _guard = set_builtin_bridge(Box::new(|name, _| {
152            Ok(StringKeyedValue::String(format!("handled:{name}")))
153        }));
154        let result = call_builtin_bridge("myBuiltin", vec![]);
155        assert_eq!(
156            result.unwrap().unwrap(),
157            StringKeyedValue::String("handled:myBuiltin".into())
158        );
159    }
160
161    // -- RAII guard clears callback on drop -----------------------------
162
163    #[test]
164    fn raii_guard_restores_previous_bridge() {
165        // Install first bridge.
166        let _outer = set_builtin_bridge(Box::new(|_, _| {
167            Ok(StringKeyedValue::String("outer".into()))
168        }));
169        {
170            // Install inner bridge (replaces outer temporarily).
171            let _inner = set_builtin_bridge(Box::new(|_, _| {
172                Ok(StringKeyedValue::String("inner".into()))
173            }));
174            let result = call_builtin_bridge("x", vec![]);
175            assert_eq!(
176                result.unwrap().unwrap(),
177                StringKeyedValue::String("inner".into())
178            );
179        }
180        // Inner guard dropped — outer bridge should be restored.
181        let result = call_builtin_bridge("x", vec![]);
182        assert_eq!(
183            result.unwrap().unwrap(),
184            StringKeyedValue::String("outer".into())
185        );
186    }
187
188    // -- call_builtin_bridge returns None when no bridge ----------------
189
190    #[test]
191    fn call_builtin_bridge_returns_none_when_no_bridge() {
192        // Ensure no bridge is installed (clean state after guard drop).
193        {
194            let _guard = set_builtin_bridge(Box::new(|_, _| Ok(StringKeyedValue::Null)));
195        }
196        let result = call_builtin_bridge("nonexistent", vec![]);
197        assert!(matches!(result, Ok(None)));
198    }
199
200    // -- call_builtin_bridge returns Some when bridge set ---------------
201
202    #[test]
203    fn call_builtin_bridge_returns_some_when_bridge_set() {
204        let _guard = set_builtin_bridge(Box::new(|_, _| {
205            Ok(StringKeyedValue::Int(42))
206        }));
207        let result = call_builtin_bridge("anything", vec![]);
208        assert!(result.is_ok());
209        assert!(result.unwrap().is_some());
210    }
211
212    // -- bridge with simple string argument and return -----------------
213
214    #[test]
215    fn bridge_with_string_argument_and_return() {
216        let _guard = set_builtin_bridge(Box::new(|name, args| {
217            assert_eq!(name, "echo");
218            match &args[0] {
219                StringKeyedValue::String(s) => {
220                    Ok(StringKeyedValue::String(format!("echo:{s}")))
221                }
222                _ => Err("expected string arg".into()),
223            }
224        }));
225        let result = call_builtin_bridge(
226            "echo",
227            vec![StringKeyedValue::String("hello".into())],
228        );
229        assert_eq!(
230            result.unwrap().unwrap(),
231            StringKeyedValue::String("echo:hello".into())
232        );
233    }
234
235    // -- bridge with attrset argument ----------------------------------
236
237    #[test]
238    fn bridge_with_attrset_argument() {
239        let _guard = set_builtin_bridge(Box::new(|name, args| {
240            assert_eq!(name, "inspect");
241            match &args[0] {
242                StringKeyedValue::Attrs(map) => {
243                    let keys: Vec<&String> = map.keys().collect();
244                    Ok(StringKeyedValue::Int(keys.len() as i64))
245                }
246                _ => Err("expected attrset".into()),
247            }
248        }));
249
250        let mut attrs = std::collections::BTreeMap::new();
251        attrs.insert("a".to_string(), StringKeyedValue::Int(1));
252        attrs.insert("b".to_string(), StringKeyedValue::Int(2));
253        attrs.insert("c".to_string(), StringKeyedValue::Int(3));
254
255        let result = call_builtin_bridge(
256            "inspect",
257            vec![StringKeyedValue::Attrs(attrs)],
258        );
259        assert_eq!(result.unwrap().unwrap(), StringKeyedValue::Int(3));
260    }
261}