Skip to main content

vm/
lib.rs

1//使用 cranelift 作为后端 直接 jit 解释脚本
2mod binary;
3mod memory;
4mod native;
5pub use native::{ANY, STD, ZustCallback};
6
7mod fns;
8use anyhow::{Result, anyhow};
9pub use fns::{FnInfo, FnVariant};
10mod context;
11pub use context::BuildContext;
12
13mod rt;
14use cranelift::prelude::types;
15use dynamic::{Dynamic, Type};
16pub use rt::JITRunTime;
17#[cfg(feature = "db")]
18mod db_module;
19mod gpu_layout;
20#[cfg(feature = "gpu")]
21mod gpu_module;
22#[cfg(feature = "http")]
23mod http_module;
24#[cfg(feature = "llm")]
25mod llm_module;
26#[cfg(feature = "llm")]
27mod oss_module;
28mod root_module;
29pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
30pub use parking_lot::RwLock;
31
32use std::sync::{OnceLock, Weak};
33static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
34pub fn ptr_type() -> types::Type {
35    PTR_TYPE.get().cloned().unwrap()
36}
37
38pub fn get_type(ty: &Type) -> Result<types::Type> {
39    if ty.is_f64() {
40        Ok(types::F64)
41    } else if ty.is_f32() {
42        Ok(types::F32)
43    } else if ty.is_int() | ty.is_uint() {
44        match ty.width() {
45            1 => Ok(types::I8),
46            2 => Ok(types::I16),
47            4 => Ok(types::I32),
48            8 => Ok(types::I64),
49            _ => Err(anyhow!("非法类型 {:?}", ty)),
50        }
51    } else if let Type::Bool = ty {
52        Ok(types::I8)
53    } else {
54        Ok(ptr_type())
55    }
56}
57
58use compiler::Symbol;
59use cranelift::prelude::*;
60use cranelift_module::Module;
61
62pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
63    jit.add_all()?;
64    Ok(jit)
65}
66
67use std::sync::Arc;
68unsafe impl Send for JITRunTime {}
69unsafe impl Sync for JITRunTime {}
70
71pub(crate) fn with_vm_context<T>(context: *const Weak<RwLock<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
72    if context.is_null() {
73        return Err(anyhow!("VM context is null"));
74    }
75    let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
76    let vm = Vm { jit };
77    f(&vm)
78}
79
80fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
81    let def_id = jit.get_id(def)?;
82    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
83        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
84            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
85        }
86    }
87    Ok(())
88}
89
90fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
91    jit.add_module(module);
92    for (name, arg_tys, ret_ty, fn_ptr) in fns {
93        let full_name = format!("{}::{}", module, name);
94        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
95    }
96    jit.pop_module();
97    Ok(())
98}
99
100impl JITRunTime {
101    fn add_memory_runtime(&mut self) -> Result<()> {
102        self.native_symbols.write().insert("__vm_scope_enter".to_string(), memory::scope_enter as *const () as usize);
103        self.native_symbols.write().insert("__vm_scope_exit_void".to_string(), memory::scope_exit_void as *const () as usize);
104        self.native_symbols.write().insert("__vm_scope_exit_dynamic".to_string(), memory::scope_exit_dynamic as *const () as usize);
105        self.native_symbols.write().insert("__vm_scope_exit_bytes".to_string(), memory::scope_exit_bytes as *const () as usize);
106        self.native_symbols.write().insert("__vm_struct_alloc".to_string(), native::struct_alloc as *const () as usize);
107        self.native_symbols.write().insert("__vm_repeat_fill".to_string(), native::repeat_fill as *const () as usize);
108        self.native_symbols.write().insert("__vm_strcat".to_string(), native::strcat as *const () as usize);
109        self.native_symbols.write().insert("__vm_strcat_i64".to_string(), native::strcat_i64 as *const () as usize);
110        self.native_symbols.write().insert("__vm_strcat_assign".to_string(), native::strcat_assign as *const () as usize);
111        self.native_symbols.write().insert("__vm_callback_new".to_string(), native::callback_new as *const () as usize);
112        self.native_symbols.write().insert("__vm_spawn_ptr".to_string(), native::spawn_ptr as *const () as usize);
113        self.native_symbols.write().insert("__vm_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
114        self.native_symbols.write().insert("__vm_array_from_ptr".to_string(), native::array_from_ptr as *const () as usize);
115        self.native_symbols.write().insert("__vm_array_to_ptr".to_string(), native::array_to_ptr as *const () as usize);
116        self.native_symbols.write().insert("__vm_arith_fault".to_string(), memory::arith_fault as *const () as usize);
117
118        let void_sig = self.get_sig(&[], Type::Void)?;
119        self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
120        self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
121
122        let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
123        self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
124
125        let bytes_sig = self.get_sig(&[Type::Any, Type::I64, Type::I64], Type::Any)?;
126        self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
127
128        let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
129        self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
130
131        let repeat_fill_sig = self.get_sig(&[Type::Any, Type::I64, Type::I64, Type::I64], Type::Void)?;
132        self.repeat_fill_fn = Some(self.module.declare_function("__vm_repeat_fill", cranelift_module::Linkage::Import, &repeat_fill_sig)?);
133
134        let strcat_sig = self.get_sig(&[Type::Str, Type::Str], Type::Str)?;
135        self.strcat_fn = Some(self.module.declare_function("__vm_strcat", cranelift_module::Linkage::Import, &strcat_sig)?);
136
137        let strcat_i64_sig = self.get_sig(&[Type::Str, Type::I64], Type::Str)?;
138        self.strcat_i64_fn = Some(self.module.declare_function("__vm_strcat_i64", cranelift_module::Linkage::Import, &strcat_i64_sig)?);
139
140        let strcat_assign_sig = self.get_sig(&[Type::Any, Type::Any], Type::Any)?;
141        self.strcat_assign_fn = Some(self.module.declare_function("__vm_strcat_assign", cranelift_module::Linkage::Import, &strcat_assign_sig)?);
142
143        let callback_new_sig = self.get_sig(&[Type::I64, Type::I64, Type::I64, Type::Any], Type::Any)?;
144        self.callback_new_fn = Some(self.module.declare_function("__vm_callback_new", cranelift_module::Linkage::Import, &callback_new_sig)?);
145
146        let spawn_ptr_sig = self.get_sig(&[Type::I64, Type::I64, Type::Any], Type::Bool)?;
147        self.spawn_ptr_fn = Some(self.module.declare_function("__vm_spawn_ptr", cranelift_module::Linkage::Import, &spawn_ptr_sig)?);
148
149        let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
150        self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
151        self.array_from_ptr_fn = Some(self.module.declare_function("__vm_array_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
152        let array_to_ptr_sig = self.get_sig(&[Type::Any, Type::Any, Type::I64], Type::Void)?;
153        self.array_to_ptr_fn = Some(self.module.declare_function("__vm_array_to_ptr", cranelift_module::Linkage::Import, &array_to_ptr_sig)?);
154
155        self.arith_fault_fn = Some(self.module.declare_function("__vm_arith_fault", cranelift_module::Linkage::Import, &void_sig)?);
156        Ok(())
157    }
158
159    pub fn add_module(&mut self, name: &str) {
160        self.compiler.symbols.add_module(name.into());
161    }
162
163    pub fn pop_module(&mut self) {
164        self.compiler.symbols.pop_module();
165    }
166
167    pub fn add_native_const(&mut self, name: &str, value: impl Into<Dynamic>, ty: Type) -> u32 {
168        self.compiler.add_symbol(name, Symbol::Const { value: value.into(), ty, is_pub: true })
169    }
170
171    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
172        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
173    }
174
175    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
176        match self.get_id(name) {
177            Ok(id) => Ok(id),
178            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
179        }
180    }
181
182    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
183        self.add_module(module);
184        let full_name = format!("{}::{}", module, name);
185        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
186        self.pop_module();
187        result
188    }
189
190    pub(crate) fn add_native_module_context_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
191        self.add_module(module);
192        let full_name = format!("{}::{}", module, name);
193        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
194        self.pop_module();
195        result
196    }
197
198    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
199        self.add_empty_type(def)?;
200        let full_name = format!("{}::{}", def, method);
201        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
202        add_method_field(self, def, method, id)?;
203        Ok(id)
204    }
205
206    pub fn add_std(&mut self) -> Result<()> {
207        if self.compiler.symbols.get_id("std::print").is_ok() {
208            return Ok(());
209        }
210        self.add_module("std");
211        for (name, arg_tys, ret_ty, fn_ptr) in STD {
212            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
213        }
214        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
215        self.add_context_native_ptr("spawn", "spawn", &[Type::Any, Type::Any], Type::Bool, native::spawn_with_vm as *const u8)?;
216        Ok(())
217    }
218
219    pub fn add_any(&mut self) -> Result<()> {
220        if self.compiler.symbols.get_id("Any").is_ok() && self.compiler.symbols.get_id("Any::is_map").is_ok() {
221            return Ok(());
222        }
223        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
224            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
225            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
226        }
227        Ok(())
228    }
229
230    pub fn add_vec(&mut self) -> Result<()> {
231        if self.compiler.symbols.get_id("Vec::get_idx").is_ok() {
232            return Ok(());
233        }
234        self.add_empty_type("Vec")?;
235        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
236        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
237            if let Some(ctx) = ctx {
238                let width = ctx.builder.ins().iconst(types::I64, 4);
239                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
240                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
241                let dest = ctx.builder.ins().imul(args[2], width);
242                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
243                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
244                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
245                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
246                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
247            }
248            Err(anyhow!("无返回值"))
249        })?;
250
251        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
252            if let Some(ctx) = ctx {
253                let width = ctx.builder.ins().iconst(types::I64, 4);
254                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
255                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
256                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
257            } else {
258                Ok((None, Type::I32))
259            }
260        })?;
261        Ok(())
262    }
263
264    #[cfg(feature = "llm")]
265    pub fn add_llm(&mut self) -> Result<()> {
266        if self.compiler.symbols.get_id("llm::complete").is_ok() {
267            return Ok(());
268        }
269        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)?;
270        add_native_module_fns(self, "oss", &oss_module::OSS_NATIVE)
271    }
272
273    pub fn add_root(&mut self) -> Result<()> {
274        if self.compiler.symbols.get_id("root::get").is_ok() {
275            return Ok(());
276        }
277        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
278        self.add_native_module_context_ptr("root", "add_fn", &[Type::Any, Type::Any], Type::Bool, root_module::root_add_fn_with_vm as *const u8)?;
279        Ok(())
280    }
281
282    #[cfg(feature = "http")]
283    pub fn add_http(&mut self) -> Result<()> {
284        if self.compiler.symbols.get_id("http::request").is_ok() {
285            return Ok(());
286        }
287        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)?;
288        http_module::add_root_handlers()
289    }
290
291    #[cfg(feature = "db")]
292    pub fn add_db(&mut self) -> Result<()> {
293        if self.compiler.symbols.get_id("db::select").is_ok() {
294            return Ok(());
295        }
296        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
297    }
298
299    #[cfg(feature = "gpu")]
300    pub fn add_gpu(&mut self) -> Result<()> {
301        if self.compiler.symbols.get_id("gpu::spirv_check").is_ok() {
302            return Ok(());
303        }
304        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
305    }
306
307    pub fn add_all(&mut self) -> Result<()> {
308        self.add_std()?;
309        self.add_any()?;
310        self.add_vec()?;
311        self.add_root()?;
312        #[cfg(feature = "llm")]
313        self.add_llm()?;
314        #[cfg(feature = "http")]
315        self.add_http()?;
316        #[cfg(feature = "db")]
317        self.add_db()?;
318        #[cfg(feature = "gpu")]
319        self.add_gpu()?;
320        Ok(())
321    }
322}
323
324#[derive(Clone)]
325pub struct Vm {
326    pub jit: Arc<parking_lot::RwLock<JITRunTime>>,
327}
328
329impl Vm {
330    pub fn new() -> Self {
331        dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
332        let jit = Arc::new(RwLock::new(JITRunTime::new(|_| {})));
333        {
334            let mut guard = jit.write();
335            guard.set_owner(Arc::downgrade(&jit));
336            guard.add_memory_runtime().expect("register VM memory runtime");
337            guard.add_std().expect("register VM std runtime");
338            guard.add_any().expect("register VM Any runtime");
339            guard.add_vec().expect("register VM Vec runtime");
340            guard.add_root().expect("register VM root runtime");
341        }
342        Self { jit }
343    }
344
345    pub fn with_all() -> Result<Self> {
346        let vm = Self::new();
347        vm.jit.write().add_all()?;
348        Ok(vm)
349    }
350
351    pub fn import(&self, name: &str, path: &str) -> Result<()> {
352        // 之前用 contains + get 两步会因其他线程并发 add/remove 出现 race;
353        // 改用 if let Some 一次性持有,失败返回明确的错误而不是 host panic。
354        if let Ok(code) = root::get(path) {
355            if code.is_str() {
356                self.jit.write().import_code(name, code.as_str().as_bytes().to_vec())?;
357            } else {
358                self.jit.write().import_code(name, code.get_dynamic("code").ok_or_else(|| anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())?;
359            }
360            Ok(())
361        } else {
362            self.jit.write().compiler.import_file(name, path)?;
363            Ok(())
364        }
365    }
366}
367
368impl Default for Vm {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::{GpuStructLayout, Vm, ZustCallback};
377    use dynamic::{CustomProperty, Dynamic, ToJson, Type};
378    use std::collections::BTreeMap;
379
380    /// Test-only wrapper for a compiled function pointer + return type.
381    struct TestFn {
382        ptr: *const u8,
383        ret: Type,
384    }
385
386    impl TestFn {
387        fn ptr(&self) -> *const u8 {
388            self.ptr
389        }
390        fn ret_ty(&self) -> &Type {
391            &self.ret
392        }
393    }
394
395    /// Test-only convenience wrapping `vm.jit.write()` calls.
396    trait VmTestExt {
397        fn import_code(&self, name: &str, code: Vec<u8>) -> anyhow::Result<()>;
398        fn get_fn(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<TestFn>;
399        fn get_fn_with_params(&self, name: &str, arg_tys: &[Type], generic_args: &[Type]) -> anyhow::Result<TestFn>;
400        fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<(*const u8, Type)>;
401        fn infer(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<Type>;
402        fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32>;
403        fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32>;
404        fn add_empty_type(&self, name: &str) -> anyhow::Result<u32>;
405        fn add_std(&self) -> anyhow::Result<()>;
406        fn add_any(&self) -> anyhow::Result<()>;
407        fn get_symbol(&self, name: &str, params: Vec<Type>) -> anyhow::Result<Type>;
408        fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> anyhow::Result<GpuStructLayout>;
409        fn load(&self, code: Vec<u8>, arg_name: smol_str::SmolStr) -> anyhow::Result<(i64, Type)>;
410    }
411
412    impl VmTestExt for Vm {
413        fn import_code(&self, name: &str, code: Vec<u8>) -> anyhow::Result<()> {
414            self.jit.write().import_code(name, code)
415        }
416        fn get_fn(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<TestFn> {
417            let (ptr, ret) = self.jit.write().get_fn_ptr(name, arg_tys)?;
418            Ok(TestFn { ptr, ret })
419        }
420        fn get_fn_with_params(&self, name: &str, arg_tys: &[Type], generic_args: &[Type]) -> anyhow::Result<TestFn> {
421            let (ptr, ret) = self.jit.write().get_fn_ptr_with_params(name, arg_tys, generic_args)?;
422            Ok(TestFn { ptr, ret })
423        }
424        fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<(*const u8, Type)> {
425            self.jit.write().get_fn_ptr(name, arg_tys)
426        }
427        fn infer(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<Type> {
428            self.jit.write().get_type(name, arg_tys)
429        }
430        fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32> {
431            self.jit.write().add_native_module_ptr(module, name, arg_tys, ret_ty, ptr)
432        }
433        fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32> {
434            self.jit.write().add_native_method_ptr(def, method, arg_tys, ret_ty, ptr)
435        }
436        fn add_empty_type(&self, name: &str) -> anyhow::Result<u32> {
437            self.jit.write().add_empty_type(name)
438        }
439        fn add_std(&self) -> anyhow::Result<()> {
440            self.jit.write().add_std()
441        }
442        fn add_any(&self) -> anyhow::Result<()> {
443            self.jit.write().add_any()
444        }
445        fn get_symbol(&self, name: &str, params: Vec<Type>) -> anyhow::Result<Type> {
446            Ok(Type::Symbol { id: self.jit.write().get_id(name)?, params })
447        }
448        fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> anyhow::Result<GpuStructLayout> {
449            let jit = self.jit.write();
450            GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
451        }
452        fn load(&self, code: Vec<u8>, arg_name: smol_str::SmolStr) -> anyhow::Result<(i64, Type)> {
453            self.jit.write().load(code, arg_name)
454        }
455    }
456
457    extern "C" fn math_double(value: i64) -> i64 {
458        value * 2
459    }
460
461    #[test]
462    fn build_context_set_var_fills_sparse_none_slots() -> anyhow::Result<()> {
463        use crate::context::{BuildContext, LocalVar};
464        use cranelift::codegen::ir::{Function, Signature, UserFuncName};
465        use cranelift::codegen::isa::CallConv;
466        use cranelift::prelude::{FunctionBuilder, FunctionBuilderContext};
467
468        let mut function = Function::with_name_signature(UserFuncName::user(0, 0), Signature::new(CallConv::Fast));
469        let mut function_ctx = FunctionBuilderContext::new();
470        let builder = FunctionBuilder::new(&mut function, &mut function_ctx);
471        let mut ctx = BuildContext::new(builder, &[], Type::Void)?;
472
473        ctx.set_var(33, LocalVar::None)?;
474
475        assert!(matches!(ctx.get_var(32)?, LocalVar::None));
476        assert!(matches!(ctx.get_var(33)?, LocalVar::None));
477        assert!(ctx.get_var(34).is_err());
478        Ok(())
479    }
480
481    #[test]
482    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
483        let vm = Vm::new();
484        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
485        vm.import_code(
486            "vm_dynamic_native",
487            br#"
488            pub fn run(value: i64) {
489                math::double(value)
490            }
491            "#
492            .to_vec(),
493        )?;
494
495        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
496        assert_eq!(compiled.ret_ty(), &Type::I64);
497        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
498        assert_eq!(run(21), 42);
499        Ok(())
500    }
501
502    #[test]
503    fn vm_new_registers_std_and_any() -> anyhow::Result<()> {
504        let vm = Vm::new();
505        vm.add_std()?;
506        vm.add_any()?;
507        assert_eq!(vm.infer("std::print", &[Type::Any])?, Type::Void);
508        assert_eq!(vm.infer("std::sqrt", &[Type::F64])?, Type::F64);
509
510        vm.import_code(
511            "vm_new_default_any",
512            br#"
513            pub fn has_items(content) {
514                if content.is_map() {
515                    if content.contains("items") {
516                        return content.items.len() > 0;
517                    }
518                }
519                false
520            }
521            "#
522            .to_vec(),
523        )?;
524
525        assert_eq!(vm.infer("vm_new_default_any::has_items", &[Type::Any])?, Type::Bool);
526        let compiled = vm.get_fn("vm_new_default_any::has_items", &[Type::Any])?;
527        assert_eq!(compiled.ret_ty(), &Type::Bool);
528        Ok(())
529    }
530
531    #[test]
532    fn std_sqrt_is_available_as_top_level_function() -> anyhow::Result<()> {
533        let vm = Vm::with_all()?;
534        vm.import_code(
535            "vm_std_sqrt",
536            br#"
537            pub fn run() {
538                sqrt(9.0f64)
539            }
540            "#
541            .to_vec(),
542        )?;
543
544        let compiled = vm.get_fn("vm_std_sqrt::run", &[])?;
545        assert_eq!(compiled.ret_ty(), &Type::F64);
546        let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
547        assert_eq!(run(), 3.0);
548        Ok(())
549    }
550
551    #[test]
552    fn tuple_assignment_uses_simultaneous_scalar_temps() -> anyhow::Result<()> {
553        let vm = Vm::with_all()?;
554        vm.import_code(
555            "vm_tuple_assignment",
556            br#"
557            pub fn swap() {
558                let a = 1i64;
559                let b = 2i64;
560                (a, b) = (b, a);
561                a * 10i64 + b
562            }
563
564            pub fn fib(n: i64) {
565                let a = 0i64;
566                let b = 1i64;
567                for _ in 0..n {
568                    (a, b) = (b, (a + b) % 1000000007i64);
569                }
570                a
571            }
572            "#
573            .to_vec(),
574        )?;
575
576        let swap = vm.get_fn("vm_tuple_assignment::swap", &[])?;
577        let swap: extern "C" fn() -> i64 = unsafe { std::mem::transmute(swap.ptr()) };
578        assert_eq!(swap(), 21);
579
580        let fib = vm.get_fn("vm_tuple_assignment::fib", &[Type::I64])?;
581        let fib: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(fib.ptr()) };
582        assert_eq!(fib(10), 55);
583        Ok(())
584    }
585
586    #[test]
587    fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
588        let vm = Vm::with_all()?;
589        vm.import_code(
590            "vm_nested_struct_return_field",
591            br#"
592            pub struct Inner {
593                value: i64,
594            }
595
596            pub struct RoleMini {
597                inner: Inner,
598                hp: i64,
599            }
600
601            pub struct TeamMini {
602                role: RoleMini,
603            }
604
605            pub struct BigSummary {
606                winner: i64,
607                loser: i64,
608            }
609
610            pub fn make_big_with_team(team: TeamMini) {
611                let score = team.role.inner.value;
612                BigSummary{winner: score, loser: 0}
613            }
614
615            pub fn read_team_winner_direct() {
616                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
617                make_big_with_team(team).winner
618            }
619
620            pub fn read_team_winner_bound() {
621                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
622                let summary = make_big_with_team(team);
623                summary.winner
624            }
625            "#
626            .to_vec(),
627        )?;
628
629        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
630        assert_eq!(compiled.ret_ty(), &Type::I64);
631        let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
632        assert_eq!(direct(), 9);
633
634        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
635        assert_eq!(compiled.ret_ty(), &Type::I64);
636        let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
637        assert_eq!(bound(), 9);
638        Ok(())
639    }
640
641    #[test]
642    fn returned_nested_struct_dynamic_fields_are_read_inline() -> anyhow::Result<()> {
643        let vm = Vm::with_all()?;
644        vm.import_code(
645            "vm_returned_nested_struct_dynamic",
646            br#"
647            pub struct Inner {
648                value: i64,
649            }
650
651            pub struct Outer {
652                inner: Inner,
653                tag: i64,
654            }
655
656            pub fn make() {
657                Outer{inner: Inner{value: 17}, tag: 3}
658            }
659            "#
660            .to_vec(),
661        )?;
662
663        let compiled = vm.get_fn("vm_returned_nested_struct_dynamic::make", &[])?;
664        let make: extern "C" fn() -> *const u8 = unsafe { std::mem::transmute(compiled.ptr()) };
665        let ty = compiled.ret_ty().clone();
666        let value = Dynamic::struct_view(make() as usize, ty);
667        let inner = value.get_dynamic("inner").expect("inner field");
668        assert_eq!(inner.get_dynamic("value").and_then(|value| value.as_int()), Some(17));
669        assert_eq!(value.get_dynamic("tag").and_then(|value| value.as_int()), Some(3));
670        Ok(())
671    }
672
673    #[test]
674    fn returned_struct_with_dynamic_field_survives_scope_exit() -> anyhow::Result<()> {
675        let vm = Vm::with_all()?;
676        vm.import_code(
677            "vm_returned_struct_dynamic_field",
678            br#"
679            pub struct Bag {
680                name: string,
681                value: string,
682            }
683
684            pub fn make() {
685                Bag{name: "alpha", value: "omega"}
686            }
687            "#
688            .to_vec(),
689        )?;
690
691        let compiled = vm.get_fn("vm_returned_struct_dynamic_field::make", &[])?;
692        let make: extern "C" fn() -> *const u8 = unsafe { std::mem::transmute(compiled.ptr()) };
693        let value = Dynamic::struct_view(make() as usize, compiled.ret_ty().clone());
694        assert_eq!(value.get_dynamic("name").map(|value| value.as_str().to_string()), Some("alpha".to_string()));
695        assert_eq!(value.get_dynamic("value").map(|value| value.as_str().to_string()), Some("omega".to_string()));
696        Ok(())
697    }
698
699    #[test]
700    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
701        let vm = Vm::with_all()?;
702        vm.import_code(
703            "vm_any_push_reused_value",
704            br#"
705            pub fn run() {
706                let role_id = "acct_role_2";
707                let updated = [];
708                updated.push(role_id);
709                {
710                    ok: true,
711                    user_id: role_id,
712                    first: updated.get_idx(0)
713                }
714            }
715            "#
716            .to_vec(),
717        )?;
718
719        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
720        assert_eq!(compiled.ret_ty(), &Type::Any);
721        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
722        let result = unsafe { &*run() };
723        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
724        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
725        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
726        Ok(())
727    }
728
729    #[test]
730    fn negate_narrow_integers() -> anyhow::Result<()> {
731        let vm = Vm::with_all()?;
732        vm.import_code(
733            "vm_neg_narrow",
734            br#"
735            pub fn neg_i8(a: i8) { -a }
736            pub fn neg_i16(a: i16) { -a }
737            "#
738            .to_vec(),
739        )?;
740
741        let neg_i8 = vm.get_fn("vm_neg_narrow::neg_i8", &[Type::I8])?;
742        assert_eq!(neg_i8.ret_ty(), &Type::I8);
743        let neg_i8: extern "C" fn(i8) -> i8 = unsafe { std::mem::transmute(neg_i8.ptr()) };
744        assert_eq!(neg_i8(5), -5);
745        assert_eq!(neg_i8(-7), 7);
746
747        let neg_i16 = vm.get_fn("vm_neg_narrow::neg_i16", &[Type::I16])?;
748        assert_eq!(neg_i16.ret_ty(), &Type::I16);
749        let neg_i16: extern "C" fn(i16) -> i16 = unsafe { std::mem::transmute(neg_i16.ptr()) };
750        assert_eq!(neg_i16(5), -5);
751        assert_eq!(neg_i16(-300), 300);
752        Ok(())
753    }
754
755    #[test]
756    fn integer_divide_by_zero_does_not_crash() -> anyhow::Result<()> {
757        let vm = Vm::with_all()?;
758        vm.import_code(
759            "vm_div_by_zero",
760            br#"
761            pub fn divz(a: i64, b: i64) { a / b }
762            pub fn modz(a: i64, b: i64) { a % b }
763            pub fn overflow(a: i64, b: i64) { a / b }
764            "#
765            .to_vec(),
766        )?;
767
768        let divz = vm.get_fn("vm_div_by_zero::divz", &[Type::I64, Type::I64])?;
769        let modz = vm.get_fn("vm_div_by_zero::modz", &[Type::I64, Type::I64])?;
770        let overflow = vm.get_fn("vm_div_by_zero::overflow", &[Type::I64, Type::I64])?;
771        let divz: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(divz.ptr()) };
772        let modz: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(modz.ptr()) };
773        let overflow: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(overflow.ptr()) };
774
775        // 正常路径不受守卫影响
776        let _ = dynamic::take_fault();
777        assert_eq!(divz(7, 2), 3);
778        assert_eq!(modz(7, 2), 1);
779        assert!(dynamic::take_fault().is_none());
780
781        // 除零:返回 0 且置 fault,而不是 trap 杀进程
782        assert_eq!(divz(7, 0), 0);
783        assert!(dynamic::take_fault().is_some());
784        assert_eq!(modz(7, 0), 0);
785        assert!(dynamic::take_fault().is_some());
786
787        // INT_MIN / -1 溢出同样被守卫
788        assert_eq!(overflow(i64::MIN, -1), 0);
789        assert!(dynamic::take_fault().is_some());
790        Ok(())
791    }
792
793    #[test]
794    fn constant_divide_by_zero_does_not_crash() -> anyhow::Result<()> {
795        let vm = Vm::with_all()?;
796        vm.import_code(
797            "vm_const_div_zero",
798            br#"
799            pub fn divz(a: i64) { a / 0 }
800            pub fn modz(a: i64) { a % 0 }
801            pub fn divc(a: i64) { a / 7 }
802            "#
803            .to_vec(),
804        )?;
805        let divz: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::divz", &[Type::I64])?.ptr()) };
806        let modz: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::modz", &[Type::I64])?.ptr()) };
807        let divc: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::divc", &[Type::I64])?.ptr()) };
808
809        let _ = dynamic::take_fault();
810        // 常量除零:编译期判定 → 返回 0 + 置 fault,不 trap
811        assert_eq!(divz(42), 0);
812        assert!(dynamic::take_fault().is_some());
813        assert_eq!(modz(42), 0);
814        assert!(dynamic::take_fault().is_some());
815        // 非零常量除数:正常计算,不置 fault(走无守卫快路径)
816        assert_eq!(divc(42), 6);
817        assert!(dynamic::take_fault().is_none());
818        Ok(())
819    }
820
821    #[test]
822    fn dynamic_divide_by_zero_returns_null() -> anyhow::Result<()> {
823        let vm = Vm::with_all()?;
824        vm.import_code(
825            "vm_any_div_by_zero",
826            br#"
827            pub fn divz(a, b) { a / b }
828            "#
829            .to_vec(),
830        )?;
831
832        let divz = vm.get_fn("vm_any_div_by_zero::divz", &[Type::Any, Type::Any])?;
833        let divz: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(divz.ptr()) };
834        let a = Dynamic::from(7i64);
835        let zero = Dynamic::from(0i64);
836        let _ = dynamic::take_fault();
837        let result = unsafe { &*divz(&a, &zero) };
838        assert!(result.is_null());
839        assert!(dynamic::take_fault().is_some());
840        Ok(())
841    }
842
843    #[test]
844    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
845        let vm = Vm::with_all()?;
846        vm.import_code(
847            "vm_string_compare_any",
848            br#"
849            pub fn any_ne_empty(chat_path) {
850                chat_path != ""
851            }
852            "#
853            .to_vec(),
854        )?;
855
856        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
857        assert_eq!(compiled.ret_ty(), &Type::Bool);
858
859        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
860        let empty = Dynamic::from("");
861        let non_empty = Dynamic::from("chat");
862
863        assert!(!any_ne_empty(&empty));
864        assert!(any_ne_empty(&non_empty));
865        Ok(())
866    }
867
868    #[test]
869    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
870        let vm = Vm::with_all()?;
871        vm.import_code(
872            "vm_bool_compare",
873            br#"
874            pub fn eq_true(value: bool) {
875                value == true
876            }
877
878            pub fn ne_false(value: bool) {
879                value != false
880            }
881
882            pub fn literal_left(value: bool) {
883                true == value
884            }
885
886            pub fn eq_pair(left: bool, right: bool) {
887                left == right
888            }
889
890            pub fn logic_pair(left: bool, right: bool) {
891                (left && right) || (left == true && right != false)
892            }
893            "#
894            .to_vec(),
895        )?;
896
897        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
898        assert_eq!(compiled.ret_ty(), &Type::Bool);
899        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
900        assert!(eq_true(true));
901        assert!(!eq_true(false));
902
903        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
904        assert_eq!(compiled.ret_ty(), &Type::Bool);
905        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
906        assert!(ne_false(true));
907        assert!(!ne_false(false));
908
909        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
910        assert_eq!(compiled.ret_ty(), &Type::Bool);
911        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
912        assert!(literal_left(true));
913        assert!(!literal_left(false));
914
915        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
916        assert_eq!(compiled.ret_ty(), &Type::Bool);
917        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
918        assert!(eq_pair(true, true));
919        assert!(eq_pair(false, false));
920        assert!(!eq_pair(true, false));
921        assert!(!eq_pair(false, true));
922
923        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
924        assert_eq!(compiled.ret_ty(), &Type::Bool);
925        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
926        assert!(logic_pair(true, true));
927        assert!(!logic_pair(true, false));
928        assert!(!logic_pair(false, true));
929        assert!(!logic_pair(false, false));
930        Ok(())
931    }
932
933    #[test]
934    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
935        let vm = Vm::with_all()?;
936        vm.import_code(
937            "vm_parenthesized_method_call",
938            br#"
939            pub fn run(value) {
940                (value + 2).to_i64()
941            }
942            "#
943            .to_vec(),
944        )?;
945
946        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
947        assert_eq!(compiled.ret_ty(), &Type::I64);
948        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
949        let value = Dynamic::from(40i64);
950
951        assert_eq!(run(&value), 42);
952        Ok(())
953    }
954
955    #[test]
956    fn casts_any_float_to_i32_without_zeroing() -> anyhow::Result<()> {
957        let vm = Vm::with_all()?;
958        vm.import_code(
959            "vm_any_float_to_i32",
960            br#"
961            pub fn direct(value) {
962                value as i32
963            }
964
965            pub fn map_field(value) {
966                let field = value.v;
967                field as i32
968            }
969
970            pub fn damage(attacker, def_rate) {
971                let x = attacker.atk * (1.0 - def_rate);
972                x as i32
973            }
974            "#
975            .to_vec(),
976        )?;
977
978        let compiled = vm.get_fn("vm_any_float_to_i32::direct", &[Type::Any])?;
979        assert_eq!(compiled.ret_ty(), &Type::I32);
980        let direct: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
981        let value = Dynamic::from(9.5f64);
982        assert_eq!(direct(&value), 9);
983
984        let compiled = vm.get_fn("vm_any_float_to_i32::map_field", &[Type::Any])?;
985        assert_eq!(compiled.ret_ty(), &Type::I32);
986        let map_field: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
987        let value = dynamic::map!("v"=> 9.5f64);
988        assert_eq!(map_field(&value), 9);
989
990        let compiled = vm.get_fn("vm_any_float_to_i32::damage", &[Type::Any, Type::Any])?;
991        assert_eq!(compiled.ret_ty(), &Type::I32);
992        let damage: extern "C" fn(*const Dynamic, *const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
993        let attacker = dynamic::map!("atk"=> 64i64);
994        let def_rate = Dynamic::from(0.17f64);
995        assert_eq!(damage(&attacker, &def_rate), 53);
996        Ok(())
997    }
998
999    #[test]
1000    fn binary_imm_promotes_integer_literals_for_float_left_values() -> anyhow::Result<()> {
1001        let vm = Vm::with_all()?;
1002        vm.import_code(
1003            "vm_float_binary_imm",
1004            br#"
1005            pub fn add_f32(value: f32) {
1006                value + 1i32
1007            }
1008
1009            pub fn sub_f32(value: f32) {
1010                value - 1i32
1011            }
1012
1013            pub fn mul_f32(value: f32) {
1014                value * 2i32
1015            }
1016
1017            pub fn div_f32(value: f32) {
1018                value / 2i32
1019            }
1020
1021            pub fn gt_f32(value: f32) {
1022                value > 2i32
1023            }
1024            "#
1025            .to_vec(),
1026        )?;
1027
1028        let compiled = vm.get_fn("vm_float_binary_imm::add_f32", &[Type::F32])?;
1029        assert_eq!(compiled.ret_ty(), &Type::F32);
1030        let add_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1031        assert_eq!(add_f32(2.5), 3.5);
1032
1033        let compiled = vm.get_fn("vm_float_binary_imm::sub_f32", &[Type::F32])?;
1034        assert_eq!(compiled.ret_ty(), &Type::F32);
1035        let sub_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1036        assert_eq!(sub_f32(2.5), 1.5);
1037
1038        let compiled = vm.get_fn("vm_float_binary_imm::mul_f32", &[Type::F32])?;
1039        assert_eq!(compiled.ret_ty(), &Type::F32);
1040        let mul_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1041        assert_eq!(mul_f32(2.5), 5.0);
1042
1043        let compiled = vm.get_fn("vm_float_binary_imm::div_f32", &[Type::F32])?;
1044        assert_eq!(compiled.ret_ty(), &Type::F32);
1045        let div_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1046        assert_eq!(div_f32(5.0), 2.5);
1047
1048        let compiled = vm.get_fn("vm_float_binary_imm::gt_f32", &[Type::F32])?;
1049        assert_eq!(compiled.ret_ty(), &Type::Bool);
1050        let gt_f32: extern "C" fn(f32) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1051        assert!(gt_f32(2.5));
1052        assert!(!gt_f32(1.5));
1053        Ok(())
1054    }
1055
1056    #[test]
1057    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
1058        let vm = Vm::with_all()?;
1059        vm.import_code(
1060            "vm_any_keys",
1061            br#"
1062            pub fn map_keys(value) {
1063                let keys = value.keys();
1064                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
1065            }
1066
1067            pub fn non_map_keys(value) {
1068                value.keys().len() == 0
1069            }
1070            "#
1071            .to_vec(),
1072        )?;
1073
1074        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
1075        assert_eq!(compiled.ret_ty(), &Type::Bool);
1076        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1077        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
1078        assert!(map_keys(&value));
1079
1080        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
1081        assert_eq!(compiled.ret_ty(), &Type::Bool);
1082        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1083        let value = Dynamic::from("alpha");
1084        assert!(non_map_keys(&value));
1085        Ok(())
1086    }
1087
1088    #[test]
1089    fn any_logic_comparisons_use_bool_abi() -> anyhow::Result<()> {
1090        let vm = Vm::with_all()?;
1091        vm.import_code(
1092            "vm_any_logic_abi",
1093            br#"
1094            pub fn ne_empty(value) {
1095                value != ""
1096            }
1097
1098            pub fn eq_empty(value) {
1099                value == ""
1100            }
1101
1102            pub fn less_than_ten(value) {
1103                value < 10
1104            }
1105
1106            pub fn contains_key(value) {
1107                value.contains("alpha") == true
1108            }
1109            "#
1110            .to_vec(),
1111        )?;
1112
1113        let compiled = vm.get_fn("vm_any_logic_abi::ne_empty", &[Type::Any])?;
1114        assert_eq!(compiled.ret_ty(), &Type::Bool);
1115        let ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1116        assert!(ne_empty(&Dynamic::from("x")));
1117        assert!(!ne_empty(&Dynamic::from("")));
1118
1119        let compiled = vm.get_fn("vm_any_logic_abi::eq_empty", &[Type::Any])?;
1120        assert_eq!(compiled.ret_ty(), &Type::Bool);
1121        let eq_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1122        assert!(eq_empty(&Dynamic::from("")));
1123        assert!(!eq_empty(&Dynamic::from("x")));
1124
1125        let compiled = vm.get_fn("vm_any_logic_abi::less_than_ten", &[Type::Any])?;
1126        assert_eq!(compiled.ret_ty(), &Type::Bool);
1127        let less_than_ten: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1128        assert!(less_than_ten(&Dynamic::from(4i64)));
1129        assert!(!less_than_ten(&Dynamic::from(14i64)));
1130
1131        let compiled = vm.get_fn("vm_any_logic_abi::contains_key", &[Type::Any])?;
1132        assert_eq!(compiled.ret_ty(), &Type::Bool);
1133        let contains_key: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1134        assert!(contains_key(&dynamic::map!("alpha"=> 1i64)));
1135        assert!(!contains_key(&dynamic::map!("beta"=> 1i64)));
1136        Ok(())
1137    }
1138
1139    #[test]
1140    fn string_methods_work_on_static_string_and_any_string_values() -> anyhow::Result<()> {
1141        let vm = Vm::with_all()?;
1142        vm.import_code(
1143            "vm_string_methods",
1144            br#"
1145            pub fn static_string_methods(text: string) {
1146                let parts = text.split(",");
1147                text.starts_with("alpha")
1148                    && text.is_string()
1149                    && !text.is_null()
1150                    && parts.len() == 2
1151                    && parts.get_idx(0) == "alpha"
1152                    && parts.get_idx(1) == "beta"
1153            }
1154
1155            pub fn any_string_methods(value) {
1156                let parts = value.split(",");
1157                value.starts_with("alpha")
1158                    && value.is_string()
1159                    && !value.is_null()
1160                    && parts.len() == 2
1161                    && parts.get_idx(0) == "alpha"
1162                    && parts.get_idx(1) == "beta"
1163            }
1164
1165            pub fn any_null_methods(value) {
1166                value.is_null() && !value.is_string()
1167            }
1168            "#
1169            .to_vec(),
1170        )?;
1171
1172        let compiled = vm.get_fn("vm_string_methods::static_string_methods", &[Type::Str])?;
1173        assert_eq!(compiled.ret_ty(), &Type::Bool);
1174        let static_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1175        let text = Dynamic::from("alpha,beta");
1176        assert!(static_string_methods(&text));
1177
1178        let compiled = vm.get_fn("vm_string_methods::any_string_methods", &[Type::Any])?;
1179        assert_eq!(compiled.ret_ty(), &Type::Bool);
1180        let any_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1181        assert!(any_string_methods(&text));
1182
1183        let compiled = vm.get_fn("vm_string_methods::any_null_methods", &[Type::Any])?;
1184        assert_eq!(compiled.ret_ty(), &Type::Bool);
1185        let any_null_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1186        let value = Dynamic::Null;
1187        assert!(any_null_methods(&value));
1188        Ok(())
1189    }
1190
1191    #[test]
1192    fn static_string_add_uses_direct_strcat() -> anyhow::Result<()> {
1193        let vm = Vm::with_all()?;
1194        vm.import_code(
1195            "vm_static_strcat",
1196            br#"
1197            pub fn join(left: string, right: string) {
1198                left + right
1199            }
1200
1201            pub fn suffix(left: string) {
1202                left + "-tail"
1203            }
1204
1205            pub fn append_local() {
1206                let text: string = "alpha";
1207                text += "-beta";
1208                text += "-tail";
1209                text
1210            }
1211
1212            pub fn append_local_assign() {
1213                let text: string = "alpha";
1214                text = text + "-beta";
1215                text = text + "-tail";
1216                text
1217            }
1218
1219            pub fn append_arg(text: string) {
1220                text += "-tail";
1221                text
1222            }
1223
1224            pub fn append_arg_assign(text: string) {
1225                text = text + "-tail";
1226                text
1227            }
1228
1229            pub fn append_any(value) {
1230                value += "-tail";
1231                value
1232            }
1233
1234            pub fn add_sub_assign_form() {
1235                let x = 10i64;
1236                x = x + 1i64;
1237                x = x - 2i64;
1238                x
1239            }
1240            "#
1241            .to_vec(),
1242        )?;
1243
1244        let compiled = vm.get_fn("vm_static_strcat::join", &[Type::Str, Type::Str])?;
1245        assert_eq!(compiled.ret_ty(), &Type::Str);
1246        let join: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1247        let left = Dynamic::from("alpha");
1248        let right = Dynamic::from("-beta");
1249        let result = unsafe { &*join(&left, &right) };
1250        assert!(matches!(result, Dynamic::StringBuf(_)));
1251        assert_eq!(result.as_str(), "alpha-beta");
1252
1253        let compiled = vm.get_fn("vm_static_strcat::suffix", &[Type::Str])?;
1254        assert_eq!(compiled.ret_ty(), &Type::Str);
1255        let suffix: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1256        let result = unsafe { &*suffix(&left) };
1257        assert!(matches!(result, Dynamic::StringBuf(_)));
1258        assert_eq!(result.as_str(), "alpha-tail");
1259
1260        let compiled = vm.get_fn("vm_static_strcat::append_local", &[])?;
1261        assert_eq!(compiled.ret_ty(), &Type::Str);
1262        let append_local: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1263        let result = unsafe { &*append_local() };
1264        assert!(matches!(result, Dynamic::StringBuf(_)));
1265        assert_eq!(result.as_str(), "alpha-beta-tail");
1266
1267        let compiled = vm.get_fn("vm_static_strcat::append_local_assign", &[])?;
1268        assert_eq!(compiled.ret_ty(), &Type::Str);
1269        let append_local_assign: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1270        let result = unsafe { &*append_local_assign() };
1271        assert!(matches!(result, Dynamic::StringBuf(_)));
1272        assert_eq!(result.as_str(), "alpha-beta-tail");
1273
1274        let compiled = vm.get_fn("vm_static_strcat::append_arg", &[Type::Str])?;
1275        assert_eq!(compiled.ret_ty(), &Type::Str);
1276        let append_arg: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1277        let input = Dynamic::from("alpha");
1278        let result = unsafe { &*append_arg(&input) };
1279        assert_eq!(result.as_str(), "alpha-tail");
1280        assert_eq!(input.as_str(), "alpha");
1281
1282        let compiled = vm.get_fn("vm_static_strcat::append_arg_assign", &[Type::Str])?;
1283        assert_eq!(compiled.ret_ty(), &Type::Str);
1284        let append_arg_assign: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1285        let input = Dynamic::from("alpha");
1286        let result = unsafe { &*append_arg_assign(&input) };
1287        assert_eq!(result.as_str(), "alpha-tail");
1288        assert_eq!(input.as_str(), "alpha");
1289
1290        let compiled = vm.get_fn("vm_static_strcat::append_any", &[Type::Any])?;
1291        assert_eq!(compiled.ret_ty(), &Type::Str);
1292        let append_any: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1293        let input = Dynamic::from("alpha");
1294        let result = unsafe { &*append_any(&input) };
1295        assert_eq!(result.as_str(), "alpha-tail");
1296        assert_eq!(input.as_str(), "alpha");
1297
1298        let compiled = vm.get_fn("vm_static_strcat::add_sub_assign_form", &[])?;
1299        assert_eq!(compiled.ret_ty(), &Type::I64);
1300        let add_sub_assign_form: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1301        assert_eq!(add_sub_assign_form(), 9);
1302        Ok(())
1303    }
1304
1305    #[test]
1306    fn primitive_type_check_methods_call_any_runtime() -> anyhow::Result<()> {
1307        let vm = Vm::with_all()?;
1308        vm.import_code(
1309            "vm_primitive_type_check_methods",
1310            br#"
1311            pub fn int_checks() {
1312                !42i64.is_list()
1313                    && !42i64.is_map()
1314                    && !42i64.is_string()
1315                    && !42i64.is_null()
1316            }
1317
1318            pub fn bool_checks() {
1319                !true.is_list() && !true.is_map() && !true.is_null()
1320            }
1321            "#
1322            .to_vec(),
1323        )?;
1324
1325        let compiled = vm.get_fn("vm_primitive_type_check_methods::int_checks", &[])?;
1326        assert_eq!(compiled.ret_ty(), &Type::Bool);
1327        let int_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1328        assert!(int_checks());
1329
1330        let compiled = vm.get_fn("vm_primitive_type_check_methods::bool_checks", &[])?;
1331        assert_eq!(compiled.ret_ty(), &Type::Bool);
1332        let bool_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1333        assert!(bool_checks());
1334        Ok(())
1335    }
1336
1337    #[test]
1338    fn for_loop_iterates_any_list_and_map_values() -> anyhow::Result<()> {
1339        let vm = Vm::with_all()?;
1340        vm.import_code(
1341            "vm_for_any_collections",
1342            br#"
1343            pub fn list_sum(items) {
1344                let total = 0i64;
1345                for item in items {
1346                    total += item;
1347                }
1348                total
1349            }
1350
1351            pub fn map_sum(data) {
1352                let total = 0i64;
1353                for (key, value) in data {
1354                    total += value;
1355                }
1356                total
1357            }
1358            "#
1359            .to_vec(),
1360        )?;
1361
1362        let compiled = vm.get_fn("vm_for_any_collections::list_sum", &[Type::Any])?;
1363        assert_eq!(compiled.ret_ty(), &Type::I64);
1364        let list_sum: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1365        let items = Dynamic::list(vec![1i64.into(), 2i64.into(), 3i64.into()]);
1366        assert_eq!(list_sum(&items), 6);
1367
1368        let compiled = vm.get_fn("vm_for_any_collections::map_sum", &[Type::Any])?;
1369        assert_eq!(compiled.ret_ty(), &Type::I64);
1370        let map_sum: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1371        let data = dynamic::map!("a"=> 4i64, "b"=> 5i64);
1372        assert_eq!(map_sum(&data), 9);
1373        Ok(())
1374    }
1375
1376    #[test]
1377    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
1378        let vm = Vm::with_all()?;
1379        vm.import_code(
1380            "vm_string_compare_imm",
1381            br#"
1382            pub fn int_eq_str(value: i64) {
1383                value == "42"
1384            }
1385
1386            pub fn int_to_str(value: i64) {
1387                value + ""
1388            }
1389            "#
1390            .to_vec(),
1391        )?;
1392
1393        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
1394        assert_eq!(compiled.ret_ty(), &Type::Bool);
1395
1396        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1397
1398        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
1399        assert_eq!(compiled.ret_ty(), &Type::Str);
1400        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1401        let text = int_to_str(42);
1402        assert_eq!(unsafe { &*text }.as_str(), "42");
1403
1404        assert!(int_eq_str(42));
1405        assert!(!int_eq_str(7));
1406        Ok(())
1407    }
1408
1409    #[test]
1410    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
1411        let vm = Vm::with_all()?;
1412        vm.import_code(
1413            "vm_string_concat_integer",
1414            br#"
1415            pub fn idx_key(idx: i64) {
1416                "" + idx
1417            }
1418
1419            pub fn level_text(level: i64) {
1420                "" + level + " level"
1421            }
1422
1423            pub fn gold_text(currency) {
1424                "" + currency.gold
1425            }
1426            "#
1427            .to_vec(),
1428        )?;
1429
1430        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
1431        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1432        let result = unsafe { &*idx_key(7) };
1433        assert!(matches!(result, Dynamic::StringBuf(_)));
1434        assert_eq!(result.as_str(), "7");
1435
1436        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
1437        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1438        let result = unsafe { &*level_text(12) };
1439        assert_eq!(result.as_str(), "12 level");
1440
1441        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
1442        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1443        let currency = dynamic::map!("gold"=> 345i64);
1444        let result = unsafe { &*gold_text(&currency) };
1445        assert_eq!(result.as_str(), "345");
1446        Ok(())
1447    }
1448
1449    #[test]
1450    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
1451        let vm = Vm::with_all()?;
1452        vm.import_code(
1453            "vm_string_concat_to_i64",
1454            br#"
1455            pub fn run(idx: i64) {
1456                ("" + idx) as i64
1457            }
1458            "#
1459            .to_vec(),
1460        )?;
1461
1462        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
1463        assert_eq!(compiled.ret_ty(), &Type::I64);
1464        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1465        assert_eq!(run(7), 7);
1466        Ok(())
1467    }
1468
1469    #[test]
1470    fn casts_dynamic_string_numbers_to_ints_and_floats() -> anyhow::Result<()> {
1471        let vm = Vm::with_all()?;
1472        vm.import_code(
1473            "vm_string_number_casts",
1474            br#"
1475            pub fn limit_i64(req) {
1476                req["@query"].limit as i64
1477            }
1478
1479            pub fn limit_i32(req) {
1480                req["@query"].limit as i32
1481            }
1482
1483            pub fn price_f64(req) {
1484                req["@query"].price as f64
1485            }
1486
1487            pub fn price_f32(req) {
1488                req["@query"].price as f32
1489            }
1490
1491            pub fn literal_i64() {
1492                "42" as i64
1493            }
1494
1495            pub fn literal_f64() {
1496                "3.5" as f64
1497            }
1498
1499            pub fn bad_number(req) {
1500                req["@query"].bad as i64
1501            }
1502            "#
1503            .to_vec(),
1504        )?;
1505
1506        let req = dynamic::map!("@query"=> dynamic::map!("limit"=> "50", "price"=> "3.5", "bad"=> "nope"));
1507
1508        let limit_i64 = vm.get_fn("vm_string_number_casts::limit_i64", &[Type::Any])?;
1509        assert_eq!(limit_i64.ret_ty(), &Type::I64);
1510        let limit_i64: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(limit_i64.ptr()) };
1511        assert_eq!(limit_i64(&req), 50);
1512
1513        let limit_i32 = vm.get_fn("vm_string_number_casts::limit_i32", &[Type::Any])?;
1514        assert_eq!(limit_i32.ret_ty(), &Type::I32);
1515        let limit_i32: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(limit_i32.ptr()) };
1516        assert_eq!(limit_i32(&req), 50);
1517
1518        let price_f64 = vm.get_fn("vm_string_number_casts::price_f64", &[Type::Any])?;
1519        assert_eq!(price_f64.ret_ty(), &Type::F64);
1520        let price_f64: extern "C" fn(*const Dynamic) -> f64 = unsafe { std::mem::transmute(price_f64.ptr()) };
1521        assert_eq!(price_f64(&req), 3.5);
1522
1523        let price_f32 = vm.get_fn("vm_string_number_casts::price_f32", &[Type::Any])?;
1524        assert_eq!(price_f32.ret_ty(), &Type::F32);
1525        let price_f32: extern "C" fn(*const Dynamic) -> f32 = unsafe { std::mem::transmute(price_f32.ptr()) };
1526        assert_eq!(price_f32(&req), 3.5);
1527
1528        let literal_i64 = vm.get_fn("vm_string_number_casts::literal_i64", &[])?;
1529        assert_eq!(literal_i64.ret_ty(), &Type::I64);
1530        let literal_i64: extern "C" fn() -> i64 = unsafe { std::mem::transmute(literal_i64.ptr()) };
1531        assert_eq!(literal_i64(), 42);
1532
1533        let literal_f64 = vm.get_fn("vm_string_number_casts::literal_f64", &[])?;
1534        assert_eq!(literal_f64.ret_ty(), &Type::F64);
1535        let literal_f64: extern "C" fn() -> f64 = unsafe { std::mem::transmute(literal_f64.ptr()) };
1536        assert_eq!(literal_f64(), 3.5);
1537
1538        let bad_number = vm.get_fn("vm_string_number_casts::bad_number", &[Type::Any])?;
1539        assert_eq!(bad_number.ret_ty(), &Type::I64);
1540        let bad_number: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(bad_number.ptr()) };
1541        assert_eq!(bad_number(&req), 0);
1542        Ok(())
1543    }
1544
1545    #[test]
1546    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
1547        let vm = Vm::with_all()?;
1548        vm.import_code(
1549            "vm_return_integer_widths",
1550            br#"
1551            pub fn selected(flag, slot) {
1552                if flag {
1553                    return slot;
1554                }
1555                0
1556            }
1557            "#
1558            .to_vec(),
1559        )?;
1560
1561        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
1562        assert_eq!(compiled.ret_ty(), &Type::I64);
1563        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1564
1565        assert_eq!(selected(true, 7), 7);
1566        assert_eq!(selected(false, 7), 0);
1567        Ok(())
1568    }
1569
1570    #[test]
1571    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
1572        let vm = Vm::with_all()?;
1573        vm.import_code(
1574            "vm_root_contains_condition",
1575            br#"
1576            pub fn exists(user_id) {
1577                if root::contains("redis/user/" + user_id) {
1578                    return 1;
1579                }
1580                0
1581            }
1582            "#
1583            .to_vec(),
1584        )?;
1585
1586        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
1587        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
1588        assert_eq!(compiled.ret_ty(), &Type::I64);
1589        Ok(())
1590    }
1591
1592    #[test]
1593    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
1594        let vm = Vm::with_all()?;
1595        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
1596        vm.import_code(
1597            "vm_root_add_map_print",
1598            br#"
1599            pub fn run() {
1600                print(root::add_map("local/world_handlers/til_map_novicevillage"));
1601            }
1602            "#
1603            .to_vec(),
1604        )?;
1605
1606        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
1607        assert!(compiled.ret_ty().is_void());
1608        Ok(())
1609    }
1610
1611    #[test]
1612    fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
1613        let vm = Vm::with_all()?;
1614        vm.import_code(
1615            "vm_std_log",
1616            br#"
1617            pub fn run(value) {
1618                log({ ok: true, value: value });
1619            }
1620            "#
1621            .to_vec(),
1622        )?;
1623
1624        let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
1625        assert!(compiled.ret_ty().is_void());
1626        let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
1627        let value = Dynamic::from(7i64);
1628        run(&value);
1629        Ok(())
1630    }
1631
1632    #[test]
1633    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
1634        let vm = Vm::with_all()?;
1635        vm.import_code(
1636            "vm_unary_not_any_loop_var",
1637            br#"
1638            pub fn count_missing(flags) {
1639                let missing = 0;
1640                for exists in flags {
1641                    if !exists {
1642                        missing = missing + 1;
1643                    }
1644                }
1645                missing
1646            }
1647            "#
1648            .to_vec(),
1649        )?;
1650
1651        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
1652        assert_eq!(compiled.ret_ty(), &Type::I64);
1653        Ok(())
1654    }
1655
1656    #[test]
1657    fn closure_literal_can_be_called_immediately() -> anyhow::Result<()> {
1658        let vm = Vm::with_all()?;
1659        vm.import_code(
1660            "vm_closure_immediate_call",
1661            br#"
1662            pub fn no_args() {
1663                let r = || { 1i32 }();
1664                r
1665            }
1666
1667            pub fn with_arg() {
1668                |value: i32| { value + 1i32 }(2i32)
1669            }
1670            "#
1671            .to_vec(),
1672        )?;
1673
1674        let compiled = vm.get_fn("vm_closure_immediate_call::no_args", &[])?;
1675        assert_eq!(compiled.ret_ty(), &Type::I32);
1676        let no_args: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1677        assert_eq!(no_args(), 1);
1678
1679        let compiled = vm.get_fn("vm_closure_immediate_call::with_arg", &[])?;
1680        assert_eq!(compiled.ret_ty(), &Type::I32);
1681        let with_arg: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1682        assert_eq!(with_arg(), 3);
1683        Ok(())
1684    }
1685
1686    #[test]
1687    fn small_expression_calls_keep_direct_semantics() -> anyhow::Result<()> {
1688        let vm = Vm::with_all()?;
1689        vm.import_code(
1690            "vm_small_expression_inline",
1691            br#"
1692            pub fn add_i64(left: i64, right: i64) {
1693                left + right
1694            }
1695
1696            pub fn normal_caller() {
1697                add_i64(1i64, 2i64)
1698            }
1699
1700            pub fn closure_caller() {
1701                let add = |left: i64, right: i64| { left + right };
1702                add(add_i64(1i64, 2i64), 4i64)
1703            }
1704
1705            pub fn closure_assignment() {
1706                let acc = 0i64;
1707                let add = |left: i64, right: i64| { left + right };
1708                acc = add(acc, 4i64);
1709                acc
1710            }
1711            "#
1712            .to_vec(),
1713        )?;
1714
1715        let compiled = vm.get_fn("vm_small_expression_inline::normal_caller", &[])?;
1716        assert_eq!(compiled.ret_ty(), &Type::I64);
1717        let normal_caller: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1718        assert_eq!(normal_caller(), 3);
1719
1720        let compiled = vm.get_fn("vm_small_expression_inline::closure_caller", &[])?;
1721        assert_eq!(compiled.ret_ty(), &Type::Any);
1722        let closure_caller: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1723        let result = unsafe { &*closure_caller() };
1724        assert_eq!(result.as_int(), Some(7));
1725
1726        let compiled = vm.get_fn("vm_small_expression_inline::closure_assignment", &[])?;
1727        assert_eq!(compiled.ret_ty(), &Type::I64);
1728        let closure_assignment: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1729        assert_eq!(closure_assignment(), 4);
1730        Ok(())
1731    }
1732
1733    #[test]
1734    fn nested_closure_captures_outer_closure_arg() -> anyhow::Result<()> {
1735        let vm = Vm::with_all()?;
1736        vm.import_code(
1737            "vm_nested_closure_capture",
1738            br#"
1739            pub fn run() {
1740                let reference_label = "reference";
1741                |path: string| {
1742                    let upload_done = |uploaded: bool| {
1743                        if uploaded {
1744                            reference_label + ":" + path
1745                        } else {
1746                            "missing"
1747                        }
1748                    };
1749                    upload_done(true)
1750                }("reference.png")
1751            }
1752            "#
1753            .to_vec(),
1754        )?;
1755
1756        let compiled = vm.get_fn("vm_nested_closure_capture::run", &[])?;
1757        assert_eq!(compiled.ret_ty(), &Type::Any);
1758        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1759        let result = unsafe { &*run() };
1760        assert_eq!(result.as_str(), "reference:reference.png");
1761        Ok(())
1762    }
1763
1764    #[test]
1765    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1766        let vm = Vm::with_all()?;
1767        vm.import_code(
1768            "vm_semicolon_tail_void",
1769            br#"
1770            pub fn send_role_select(idx, account_id, selected_slot) {
1771                root::send("local/ui/send_dialog", {
1772                    idx: idx,
1773                    account_id: account_id,
1774                    selected_slot: selected_slot
1775                });
1776            }
1777            "#
1778            .to_vec(),
1779        )?;
1780
1781        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1782        assert_eq!(compiled.ret_ty(), &Type::Void);
1783        Ok(())
1784    }
1785
1786    #[test]
1787    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1788        let vm = Vm::with_all()?;
1789        vm.import_code(
1790            "vm_bare_return_conflict",
1791            br#"
1792            pub fn run(flag) {
1793                if flag {
1794                    return;
1795                }
1796                1
1797            }
1798            "#
1799            .to_vec(),
1800        )?;
1801
1802        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1803            Ok(_) => panic!("expected mismatched return types to fail"),
1804            Err(err) => err,
1805        };
1806        assert!(format!("{err:#}").contains("返回类型不一致"));
1807        Ok(())
1808    }
1809
1810    #[test]
1811    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1812        let vm = Vm::with_all()?;
1813        vm.import_code(
1814            "vm_root_get_dynamic_concat",
1815            br#"
1816            pub fn get_action(req) {
1817                root::get("local/game/panel_actions/" + req.idx)
1818            }
1819            "#
1820            .to_vec(),
1821        )?;
1822
1823        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1824        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1825        assert_eq!(compiled.ret_ty(), &Type::Any);
1826        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1827        let req = dynamic::map!("idx"=> 7i64);
1828        let result = unsafe { &*get_action(&req) };
1829
1830        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1831        Ok(())
1832    }
1833
1834    #[test]
1835    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1836        let vm = Vm::with_all()?;
1837        vm.import_code(
1838            "vm_registered_panel_action",
1839            br#"
1840            pub fn panel_action(req) {
1841                root::get("local/game/panel_actions/" + req.idx)
1842            }
1843
1844            pub fn register() {
1845                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1846            }
1847            "#
1848            .to_vec(),
1849        )?;
1850
1851        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1852        assert_eq!(compiled.ret_ty(), &Type::Bool);
1853        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1854        assert!(register());
1855        Ok(())
1856    }
1857
1858    #[test]
1859    fn std_spawn_runs_named_function_with_tuple_args() -> anyhow::Result<()> {
1860        let zero_path = "local/vm_std_spawn/zero";
1861        let sum_path = "local/vm_std_spawn/sum";
1862        let closure_path = "local/vm_std_spawn/closure";
1863        let closure_vars_path = "local/vm_std_spawn/closure_vars";
1864        let _ = root::remove(zero_path);
1865        let _ = root::remove(sum_path);
1866        let _ = root::remove(closure_path);
1867        let _ = root::remove(closure_vars_path);
1868        let vm = Vm::with_all()?;
1869        vm.import_code(
1870            "vm_std_spawn",
1871            br#"
1872            pub fn zero() {
1873                root::add("local/vm_std_spawn/zero", 1);
1874            }
1875
1876            pub fn job(left, right) {
1877                root::add("local/vm_std_spawn/sum", left + right);
1878            }
1879
1880            pub fn start_zero() {
1881                spawn("vm_std_spawn::zero", ())
1882            }
1883
1884            pub fn start_sum() {
1885                spawn("vm_std_spawn::job", (10, 20))
1886            }
1887
1888            pub fn start_closure() {
1889                spawn(|x, y| {
1890                    root::add("local/vm_std_spawn/closure", x + y);
1891                }, (3, 4))
1892            }
1893
1894            pub fn start_closure_vars() {
1895                let x = 5;
1896                let y = 6;
1897                spawn(|left, right| {
1898                    root::add("local/vm_std_spawn/closure_vars", left + right);
1899                }, (x, y))
1900            }
1901            "#
1902            .to_vec(),
1903        )?;
1904
1905        let compiled = vm.get_fn("vm_std_spawn::start_zero", &[])?;
1906        assert_eq!(compiled.ret_ty(), &Type::Bool);
1907        let start_zero: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1908        assert!(start_zero());
1909
1910        let compiled = vm.get_fn("vm_std_spawn::start_sum", &[])?;
1911        assert_eq!(compiled.ret_ty(), &Type::Bool);
1912        let start_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1913        assert!(start_sum());
1914
1915        let compiled = vm.get_fn("vm_std_spawn::start_closure", &[])?;
1916        assert_eq!(compiled.ret_ty(), &Type::Bool);
1917        let start_closure: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1918        assert!(start_closure());
1919
1920        let compiled = vm.get_fn("vm_std_spawn::start_closure_vars", &[])?;
1921        assert_eq!(compiled.ret_ty(), &Type::Bool);
1922        let start_closure_vars: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1923        assert!(start_closure_vars());
1924
1925        for _ in 0..50 {
1926            let zero_done = root::get(zero_path).ok().and_then(|value| value.as_int()) == Some(1);
1927            let sum_done = root::get(sum_path).ok().and_then(|value| value.as_int()) == Some(30);
1928            let closure_done = root::get(closure_path).ok().and_then(|value| value.as_int()) == Some(7);
1929            let closure_vars_done = root::get(closure_vars_path).ok().and_then(|value| value.as_int()) == Some(11);
1930            if zero_done && sum_done && closure_done && closure_vars_done {
1931                return Ok(());
1932            }
1933            std::thread::sleep(std::time::Duration::from_millis(10));
1934        }
1935
1936        anyhow::bail!("spawned jobs did not write expected results");
1937    }
1938
1939    #[test]
1940    fn native_can_save_and_later_call_closure_callback() -> anyhow::Result<()> {
1941        static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
1942
1943        extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1944            if callback.is_null() {
1945                return false;
1946            }
1947            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1948                return false;
1949            };
1950            *SAVED_CALLBACK.lock() = Some(callback);
1951            true
1952        }
1953
1954        let path = "local/vm_callback/result";
1955        let _ = root::remove(path);
1956        *SAVED_CALLBACK.lock() = None;
1957
1958        let vm = Vm::with_all()?;
1959        vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
1960        vm.import_code(
1961            "vm_callback",
1962            br#"
1963            pub fn register() {
1964                let n = 41;
1965                callback_test::save(|| {
1966                    root::add("local/vm_callback/result", n + 1);
1967                    true
1968                })
1969            }
1970            "#
1971            .to_vec(),
1972        )?;
1973
1974        let compiled = vm.get_fn("vm_callback::register", &[])?;
1975        assert_eq!(compiled.ret_ty(), &Type::Bool);
1976        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1977        assert!(register());
1978        assert!(root::get(path).is_err());
1979
1980        let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
1981        let result = callback.call0()?;
1982        assert_eq!(result.as_bool(), Some(true));
1983        assert_eq!(root::get(path)?.as_int(), Some(42));
1984        Ok(())
1985    }
1986
1987    #[test]
1988    fn closure_captures_share_state_between_callbacks() -> anyhow::Result<()> {
1989        static SAVED_CALLBACKS: parking_lot::Mutex<Vec<ZustCallback>> = parking_lot::Mutex::new(Vec::new());
1990
1991        extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1992            if callback.is_null() {
1993                return false;
1994            }
1995            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1996                return false;
1997            };
1998            SAVED_CALLBACKS.lock().push(callback);
1999            true
2000        }
2001
2002        SAVED_CALLBACKS.lock().clear();
2003
2004        let vm = Vm::with_all()?;
2005        vm.add_native_module_ptr("capture_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
2006        vm.import_code(
2007            "vm_shared_capture",
2008            br#"
2009            pub fn register() {
2010                let state = {};
2011                state.drag_kind = 0;
2012                capture_test::save(|| {
2013                    state.drag_kind = 2;
2014                    true
2015                });
2016                capture_test::save(|| {
2017                    state.drag_kind
2018                })
2019            }
2020            "#
2021            .to_vec(),
2022        )?;
2023
2024        let register = vm.get_fn("vm_shared_capture::register", &[])?;
2025        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2026        assert!(register());
2027
2028        let (writer, reader) = {
2029            let saved = SAVED_CALLBACKS.lock();
2030            assert_eq!(saved.len(), 2);
2031            (saved[0].clone(), saved[1].clone())
2032        };
2033        assert_eq!(reader.call0()?.as_int(), Some(0));
2034        assert_eq!(writer.call0()?.as_bool(), Some(true));
2035        assert_eq!(reader.call0()?.as_int(), Some(2));
2036        Ok(())
2037    }
2038
2039    #[test]
2040    fn native_can_save_and_later_call_named_function_callback() -> anyhow::Result<()> {
2041        static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2042
2043        extern "C" fn save_callback(callback: *const Dynamic) -> bool {
2044            if callback.is_null() {
2045                return false;
2046            }
2047            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2048                return false;
2049            };
2050            *SAVED_CALLBACK.lock() = Some(callback);
2051            true
2052        }
2053
2054        let path = "local/vm_named_callback/result";
2055        let _ = root::remove(path);
2056        *SAVED_CALLBACK.lock() = None;
2057
2058        let vm = Vm::with_all()?;
2059        vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
2060        vm.import_code(
2061            "vm_named_callback",
2062            br#"
2063            pub fn on_result() {
2064                root::add("local/vm_named_callback/result", "done");
2065                true
2066            }
2067
2068            pub fn register() {
2069                callback_test::save(on_result)
2070            }
2071            "#
2072            .to_vec(),
2073        )?;
2074
2075        let register = vm.get_fn("vm_named_callback::register", &[])?;
2076        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2077        assert!(register());
2078        assert!(root::get(path).is_err());
2079
2080        let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
2081        assert_eq!(callback.call1(dynamic::map!("text"=> "done"))?.as_bool(), Some(true));
2082        assert_eq!(root::get(path)?.as_str(), "done");
2083        Ok(())
2084    }
2085
2086    #[test]
2087    fn native_callback_can_receive_later_dynamic_args() -> anyhow::Result<()> {
2088        static SAVED_PATH_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2089        static SAVED_SUM_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2090
2091        extern "C" fn save_path_callback(callback: *const Dynamic) -> bool {
2092            if callback.is_null() {
2093                return false;
2094            }
2095            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2096                return false;
2097            };
2098            *SAVED_PATH_CALLBACK.lock() = Some(callback);
2099            true
2100        }
2101
2102        extern "C" fn save_sum_callback(callback: *const Dynamic) -> bool {
2103            if callback.is_null() {
2104                return false;
2105            }
2106            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2107                return false;
2108            };
2109            *SAVED_SUM_CALLBACK.lock() = Some(callback);
2110            true
2111        }
2112
2113        let path_result = "local/vm_callback/path";
2114        let sum_result = "local/vm_callback/sum8";
2115        let _ = root::remove(path_result);
2116        let _ = root::remove(sum_result);
2117        *SAVED_PATH_CALLBACK.lock() = None;
2118        *SAVED_SUM_CALLBACK.lock() = None;
2119
2120        let vm = Vm::with_all()?;
2121        vm.add_native_module_ptr("callback_test", "save_path", &[Type::Any], Type::Bool, save_path_callback as *const u8)?;
2122        vm.add_native_module_ptr("callback_test", "save_sum", &[Type::Any], Type::Bool, save_sum_callback as *const u8)?;
2123        vm.import_code(
2124            "vm_callback_args",
2125            br#"
2126            pub fn register_path() {
2127                let key = "local/vm_callback/path";
2128                callback_test::save_path(|path| {
2129                    root::add(key, path);
2130                    true
2131                })
2132            }
2133
2134            pub fn register_sum() {
2135                callback_test::save_sum(|a, b, c, d, e, f, g, h| {
2136                    root::add("local/vm_callback/sum8", a + b + c + d + e + f + g + h);
2137                    true
2138                })
2139            }
2140            "#
2141            .to_vec(),
2142        )?;
2143
2144        let register_path = vm.get_fn("vm_callback_args::register_path", &[])?;
2145        let register_path: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_path.ptr()) };
2146        assert!(register_path());
2147
2148        let register_sum = vm.get_fn("vm_callback_args::register_sum", &[])?;
2149        let register_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_sum.ptr()) };
2150        assert!(register_sum());
2151
2152        let path_callback = SAVED_PATH_CALLBACK.lock().clone().expect("path callback should be saved");
2153        assert_eq!(path_callback.call1(Dynamic::from("picked.txt"))?.as_bool(), Some(true));
2154        assert_eq!(root::get(path_result)?.as_str(), "picked.txt");
2155
2156        let sum_callback = SAVED_SUM_CALLBACK.lock().clone().expect("sum callback should be saved");
2157        let sum_args = (1i64..=8).map(Dynamic::from).collect();
2158        assert_eq!(sum_callback.call(sum_args)?.as_bool(), Some(true));
2159        assert_eq!(root::get(sum_result)?.as_int(), Some(36));
2160        Ok(())
2161    }
2162
2163    #[test]
2164    fn callback_with_16_explicit_args_and_captures() -> anyhow::Result<()> {
2165        static SAVED_SUM16: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2166
2167        extern "C" fn save_sum16(callback: *const Dynamic) -> bool {
2168            if callback.is_null() {
2169                return false;
2170            }
2171            let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2172                return false;
2173            };
2174            *SAVED_SUM16.lock() = Some(callback);
2175            true
2176        }
2177
2178        let sum16_path = "local/vm_callback/sum16";
2179        let _ = root::remove(sum16_path);
2180        *SAVED_SUM16.lock() = None;
2181
2182        let vm = Vm::with_all()?;
2183        vm.add_native_module_ptr("callback_test", "save_sum16", &[Type::Any], Type::Bool, save_sum16 as *const u8)?;
2184        vm.import_code(
2185            "vm_callback_16_args",
2186            br#"
2187            pub fn register_sum16() {
2188                let prefix = "sum=";
2189                callback_test::save_sum16(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2190                    let total = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p;
2191                    root::add("local/vm_callback/sum16", prefix + total);
2192                    true
2193                })
2194            }
2195            "#
2196            .to_vec(),
2197        )?;
2198
2199        let register = vm.get_fn("vm_callback_16_args::register_sum16", &[])?;
2200        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2201        assert!(register());
2202
2203        let callback = SAVED_SUM16.lock().clone().expect("sum16 callback saved");
2204        let args: Vec<Dynamic> = (1i64..=16).map(Dynamic::from).collect();
2205        assert_eq!(callback.call(args)?.as_bool(), Some(true));
2206        assert_eq!(root::get(sum16_path)?.as_str(), "sum=136");
2207        Ok(())
2208    }
2209
2210    #[test]
2211    fn spawn_closure_with_16_args() -> anyhow::Result<()> {
2212        let spawn16_path = "local/vm_spawn/spawn16";
2213        let _ = root::remove(spawn16_path);
2214
2215        let vm = Vm::with_all()?;
2216        vm.import_code(
2217            "vm_spawn_16_args",
2218            br#"
2219            pub fn start_spawn16() {
2220                spawn(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2221                    root::add("local/vm_spawn/spawn16", a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p);
2222                }, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
2223            }
2224            "#
2225            .to_vec(),
2226        )?;
2227
2228        let compiled = vm.get_fn("vm_spawn_16_args::start_spawn16", &[])?;
2229        assert_eq!(compiled.ret_ty(), &Type::Bool);
2230        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2231        assert!(start());
2232
2233        for _ in 0..50 {
2234            if root::get(spawn16_path).ok().and_then(|v| v.as_int()) == Some(136) {
2235                return Ok(());
2236            }
2237            std::thread::sleep(std::time::Duration::from_millis(10));
2238        }
2239        anyhow::bail!("spawned job did not write expected result");
2240    }
2241
2242    #[test]
2243    fn spawn_native_closure_avoids_any_boxing() -> anyhow::Result<()> {
2244        let nat_path = "local/vm_spawn_native/result";
2245        let _ = root::remove(nat_path);
2246        let vm = Vm::with_all()?;
2247        vm.import_code(
2248            "vm_spawn_native",
2249            br#"
2250            pub fn start() {
2251                spawn(|x: i64, y: i64| {
2252                    root::add("local/vm_spawn_native/result", x + y);
2253                }, (10i64, 20i64))
2254            }
2255            "#
2256            .to_vec(),
2257        )?;
2258        let compiled = vm.get_fn("vm_spawn_native::start", &[])?;
2259        assert_eq!(compiled.ret_ty(), &Type::Bool);
2260        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2261        assert!(start());
2262        for _ in 0..50 {
2263            if root::get(nat_path).ok().and_then(|v| v.as_int()) == Some(30) {
2264                return Ok(());
2265            }
2266            std::thread::sleep(std::time::Duration::from_millis(10));
2267        }
2268        anyhow::bail!("spawned native closure did not write expected result");
2269    }
2270
2271    #[test]
2272    fn multi_level_nested_closure_captures() -> anyhow::Result<()> {
2273        let vm = Vm::with_all()?;
2274        vm.import_code(
2275            "vm_multi_level_captures",
2276            br#"
2277            pub fn run() {
2278                let level1 = "L1";
2279                let level2 = "L2";
2280                |path: string| {
2281                    let level3 = "L3";
2282                    let inner = |suffix: string| {
2283                        let level4 = "L4";
2284                        |flag: bool| {
2285                            if flag {
2286                                level1 + "." + level2 + "." + level3 + "." + level4 + "." + path + suffix
2287                            } else {
2288                                "off"
2289                            }
2290                        }(true)
2291                    };
2292                    inner(".ext")
2293                }("file.txt")
2294            }
2295            "#
2296            .to_vec(),
2297        )?;
2298
2299        let compiled = vm.get_fn("vm_multi_level_captures::run", &[])?;
2300        assert_eq!(compiled.ret_ty(), &Type::Any);
2301        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2302        let result = unsafe { &*run() };
2303        assert_eq!(result.as_str(), "L1.L2.L3.L4.file.txt.ext");
2304        Ok(())
2305    }
2306
2307    #[test]
2308    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
2309        let vm = Vm::with_all()?;
2310        vm.import_code(
2311            "vm_registered_string_concat",
2312            br#"
2313            pub fn send_panel(idx: i64) {
2314                let idx_key = "" + idx;
2315                idx_key
2316            }
2317            "#
2318            .to_vec(),
2319        )?;
2320
2321        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
2322        Ok(())
2323    }
2324
2325    #[test]
2326    fn dynamic_method_error_reports_source_location() -> anyhow::Result<()> {
2327        let vm = Vm::with_all()?;
2328        vm.import_code(
2329            "vm_bad_dynamic_method",
2330            br#"
2331            pub fn main(value) {
2332                let out = "";
2333                out = out + value.fetch("name");
2334            }
2335            "#
2336            .to_vec(),
2337        )?;
2338
2339        let err = vm.get_fn_ptr("vm_bad_dynamic_method::main", &[Type::Any]).expect_err("bad dynamic method should fail to compile");
2340        let msg = format!("{err:#}");
2341        assert!(msg.contains("vm_bad_dynamic_method:4:"), "{msg}");
2342        assert!(msg.contains("`Any.fetch` 不是成员函数"), "{msg}");
2343        assert!(msg.contains(r#"out = out + value.fetch("name");"#), "{msg}");
2344        Ok(())
2345    }
2346
2347    #[test]
2348    fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
2349        fn echo_handler(msg: Dynamic) -> Dynamic {
2350            dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
2351        }
2352
2353        let vm = Vm::with_all()?;
2354        vm.import_code(
2355            "vm_root_send_idx_return",
2356            br#"
2357            pub fn call(req) {
2358                root::send_idx("local/send_idx_return_handlers", 0, req)
2359            }
2360            "#
2361            .to_vec(),
2362        )?;
2363
2364        root::add_list("local/send_idx_return_handlers")?;
2365        let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
2366        mount.push(name, root::Object::Native(echo_handler))?;
2367
2368        assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
2369        let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
2370        assert_eq!(compiled.ret_ty(), &Type::Any);
2371        let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2372        let req = dynamic::map!("id"=> 42i64);
2373        let result = unsafe { &*call(&req) };
2374
2375        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
2376        assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
2377        Ok(())
2378    }
2379
2380    #[test]
2381    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
2382        let vm = Vm::with_all()?;
2383        vm.import_code(
2384            "vm_public_hotspots",
2385            br#"
2386            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2387                {
2388                    path: action_map_path,
2389                    panel_id: panel_id,
2390                    action_id: action_id,
2391                    id: hotspot.id
2392                }
2393            }
2394
2395            pub fn public_hotspots(idx, panel_id, hotspots) {
2396                let idx_key = "" + idx;
2397                let action_map_path = "local/game/panel_actions/" + idx_key;
2398
2399                let existing_action_map = root::get(action_map_path);
2400                if !existing_action_map.is_map() {
2401                    root::add_map(action_map_path);
2402                }
2403
2404                if hotspots.is_map() {
2405                    let public_items = {};
2406                    for action_id in hotspots.keys() {
2407                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2408                    }
2409                    return public_items;
2410                }
2411
2412                let public_items = [];
2413                let i = 0;
2414                while i < hotspots.len() {
2415                    let hotspot = hotspots.get_idx(i);
2416                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2417                    public_items.push(item);
2418                    i = i + 1;
2419                }
2420
2421                public_items
2422            }
2423            "#
2424            .to_vec(),
2425        )?;
2426
2427        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
2428        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
2429        Ok(())
2430    }
2431
2432    #[test]
2433    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
2434        let vm = Vm::with_all()?;
2435        vm.import_code(
2436            "vm_send_panel_public_hotspots",
2437            br#"
2438            pub fn ok(value) {
2439                value
2440            }
2441
2442            pub fn panel_from_node(req) {
2443                {
2444                    panel_id: req.panel_id,
2445                    hotspots: req.hotspots
2446                }
2447            }
2448
2449            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2450                {
2451                    path: action_map_path,
2452                    panel_id: panel_id,
2453                    action_id: action_id,
2454                    id: hotspot.id
2455                }
2456            }
2457
2458            pub fn public_hotspots(idx, panel_id, hotspots) {
2459                let idx_key = "" + idx;
2460                let action_map_path = "local/game/panel_actions/" + idx_key;
2461
2462                let existing_action_map = root::get(action_map_path);
2463                if !existing_action_map.is_map() {
2464                    root::add_map(action_map_path);
2465                }
2466
2467                if hotspots.is_map() {
2468                    let public_items = {};
2469                    for action_id in hotspots.keys() {
2470                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2471                    }
2472                    return public_items;
2473                }
2474
2475                let public_items = [];
2476                let i = 0;
2477                while i < hotspots.len() {
2478                    let hotspot = hotspots.get_idx(i);
2479                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2480                    public_items.push(item);
2481                    i = i + 1;
2482                }
2483
2484                public_items
2485            }
2486
2487            pub fn send_panel(req) {
2488                let panel = req.panel;
2489                if !panel.is_map() {
2490                    panel = panel_from_node(req);
2491                }
2492                if !panel.is_map() {
2493                    return ok({
2494                        id: 4,
2495                        type: "panel_rejected",
2496                        reason: "invalid panel"
2497                    });
2498                }
2499                panel.id = 4;
2500                panel.idx = req.idx;
2501                if !panel.contains("type") {
2502                    panel.type = "panel";
2503                }
2504                if panel.contains("hotspots") {
2505                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
2506                }
2507                root::send_idx("local/ws", req.idx, panel);
2508                ok({
2509                    id: 4,
2510                    type: "panel",
2511                    panel_id: panel.panel_id
2512                })
2513            }
2514            "#
2515            .to_vec(),
2516        )?;
2517
2518        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
2519        assert_eq!(compiled.ret_ty(), &Type::Any);
2520        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2521        let req = dynamic::map!(
2522            "idx"=> 7i64,
2523            "panel"=> dynamic::map!(
2524                "panel_id"=> "main",
2525                "hotspots"=> dynamic::map!(
2526                    "open"=> dynamic::map!("id"=> "open")
2527                )
2528            )
2529        );
2530        let result = unsafe { &*send_panel(&req) };
2531
2532        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
2533        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
2534        Ok(())
2535    }
2536
2537    #[test]
2538    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
2539        let vm = Vm::with_all()?;
2540        vm.import_code(
2541            "vm_string_concat_map_key",
2542            br##"
2543            pub fn write_action(action_map, panel_id, action_id, action) {
2544                action_map[panel_id + "#" + action_id] = action;
2545                action_map[panel_id + "#" + action_id]
2546            }
2547            "##
2548            .to_vec(),
2549        )?;
2550
2551        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
2552        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2553        let action_map = dynamic::map!();
2554        let panel_id: Dynamic = "panel".into();
2555        let action_id: Dynamic = "open".into();
2556        let action = dynamic::map!("id"=> "open");
2557
2558        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
2559
2560        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2561        assert_eq!(action_map.get_dynamic("panel#open").and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
2562        Ok(())
2563    }
2564
2565    #[test]
2566    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
2567        let vm = Vm::with_all()?;
2568        vm.import_code(
2569            "vm_get_key_string_concat_key",
2570            br##"
2571            pub fn read_action(action_map, panel_id, action_id) {
2572                let action_key = panel_id + "#" + action_id;
2573                action_map.get_key(action_key)
2574            }
2575            "##
2576            .to_vec(),
2577        )?;
2578
2579        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2580        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2581        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2582        let panel_id: Dynamic = "panel".into();
2583        let action_id: Dynamic = "open".into();
2584
2585        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2586
2587        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2588        Ok(())
2589    }
2590
2591    #[test]
2592    fn map_get_alias_matches_get_key() -> anyhow::Result<()> {
2593        let vm = Vm::with_all()?;
2594        vm.import_code(
2595            "vm_map_get_alias",
2596            br#"
2597            pub fn read_name(data) {
2598                data.get("name")
2599            }
2600
2601            pub fn read_missing(data) {
2602                data.get("missing")
2603            }
2604            "#
2605            .to_vec(),
2606        )?;
2607
2608        let data = dynamic::map!("name"=> "zust");
2609
2610        let compiled = vm.get_fn("vm_map_get_alias::read_name", &[Type::Any])?;
2611        assert_eq!(compiled.ret_ty(), &Type::Any);
2612        let read_name: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2613        let result = unsafe { &*read_name(&data) };
2614        assert_eq!(result.as_str(), "zust");
2615
2616        let compiled = vm.get_fn("vm_map_get_alias::read_missing", &[Type::Any])?;
2617        assert_eq!(compiled.ret_ty(), &Type::Any);
2618        let read_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2619        let result = unsafe { &*read_missing(&data) };
2620        assert!(result.is_null());
2621        Ok(())
2622    }
2623
2624    #[test]
2625    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
2626        let vm = Vm::with_all()?;
2627        vm.import_code(
2628            "vm_get_key_helper_string_key",
2629            br##"
2630            pub fn make_action_key(panel_id, action_id) {
2631                panel_id + "#" + action_id
2632            }
2633
2634            pub fn read_action(action_map, panel_id, action_id) {
2635                let action_key = make_action_key(panel_id, action_id);
2636                let action = action_map.get_key(action_key);
2637                action
2638            }
2639            "##
2640            .to_vec(),
2641        )?;
2642
2643        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2644        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2645        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2646        let panel_id: Dynamic = "panel".into();
2647        let action_id: Dynamic = "open".into();
2648
2649        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2650
2651        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2652        Ok(())
2653    }
2654
2655    #[test]
2656    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
2657        let vm = Vm::with_all()?;
2658        vm.import_code(
2659            "vm_del_key_string_key",
2660            br##"
2661            pub fn remove_action(action_map, panel_id, action_id) {
2662                let action_key = panel_id + "#" + action_id;
2663                let removed = action_map.del_key(action_key);
2664                [removed, action_map.get_key(action_key)]
2665            }
2666            "##
2667            .to_vec(),
2668        )?;
2669
2670        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
2671        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2672        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2673        let panel_id: Dynamic = "panel".into();
2674        let action_id: Dynamic = "open".into();
2675
2676        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
2677
2678        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
2679        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
2680        assert!(action_map.get_dynamic("panel#open").is_none());
2681        Ok(())
2682    }
2683
2684    #[test]
2685    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
2686        let vm = Vm::with_all()?;
2687        vm.import_code(
2688            "vm_dynamic_field_or",
2689            r#"
2690            pub fn direct_next() {
2691                let choice = {
2692                    label: "颜色",
2693                    next: "color"
2694                };
2695                choice.next
2696            }
2697
2698            pub fn bracket_next() {
2699                let choice = {
2700                    label: "颜色",
2701                    next: "color"
2702                };
2703                choice["next"]
2704            }
2705            "#
2706            .as_bytes()
2707            .to_vec(),
2708        )?;
2709
2710        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
2711        assert_eq!(compiled.ret_ty(), &Type::Any);
2712        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2713        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
2714
2715        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
2716        assert_eq!(compiled.ret_ty(), &Type::Any);
2717        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2718        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
2719        Ok(())
2720    }
2721
2722    #[test]
2723    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
2724        let vm = Vm::with_all()?;
2725        vm.import_code(
2726            "vm_if_empty_object_branch",
2727            r#"
2728            pub fn first_note(steps) {
2729                let first = if steps.len() > 0 { steps[0] } else { {} };
2730                let first_note = if first.contains("note") { first.note } else { "fallback" };
2731                first_note
2732            }
2733
2734            pub fn first_ja(steps) {
2735                let first = if steps.len() > 0 { steps[0] } else { {} };
2736                if first.contains("ja") { first.ja } else { "すみません" }
2737            }
2738
2739            pub fn assign_first_note(steps) {
2740                let first = {};
2741                first = if steps.len() > 0 { steps[0] } else { {} };
2742                if first.contains("note") { first.note } else { "fallback" }
2743            }
2744            "#
2745            .as_bytes()
2746            .to_vec(),
2747        )?;
2748
2749        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
2750        assert_eq!(compiled.ret_ty(), &Type::Str);
2751        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2752
2753        let empty_steps = Dynamic::list(Vec::new());
2754        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
2755
2756        let mut step = std::collections::BTreeMap::new();
2757        step.insert("note".into(), "hello".into());
2758        let steps = Dynamic::list(vec![Dynamic::map(step)]);
2759        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
2760
2761        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
2762        assert_eq!(compiled.ret_ty(), &Type::Any);
2763        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2764        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
2765
2766        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
2767        assert_eq!(compiled.ret_ty(), &Type::Any);
2768        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2769        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
2770        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
2771        Ok(())
2772    }
2773
2774    #[test]
2775    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
2776        let vm = Vm::with_all()?;
2777        vm.import_code(
2778            "vm_tail_list_literal",
2779            r#"
2780            pub fn numbers() {
2781                [1, 2, 3]
2782            }
2783
2784            pub fn maps() {
2785                [
2786                    {note: "first"},
2787                    {note: "second"}
2788                ]
2789            }
2790
2791            pub fn object_with_maps() {
2792                {
2793                    steps: [
2794                        {note: "first"},
2795                        {note: "second"}
2796                    ]
2797                }
2798            }
2799
2800            pub fn return_maps() {
2801                return [
2802                    {note: "first"},
2803                    {note: "second"}
2804                ];
2805            }
2806
2807            pub fn return_maps_without_semicolon() {
2808                return [
2809                    {note: "first"},
2810                    {note: "second"}
2811                ]
2812            }
2813
2814            pub fn tail_bare_variable() {
2815                let value = [
2816                    {note: "first"},
2817                    {note: "second"}
2818                ];
2819                value
2820            }
2821
2822            pub fn return_bare_variable_without_semicolon() {
2823                let value = [
2824                    {note: "first"},
2825                    {note: "second"}
2826                ];
2827                return value
2828            }
2829
2830            pub fn tail_object_variable() {
2831                let result = {
2832                    steps: [
2833                        {note: "first"},
2834                        {note: "second"}
2835                    ]
2836                };
2837                result
2838            }
2839            "#
2840            .as_bytes()
2841            .to_vec(),
2842        )?;
2843
2844        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
2845        assert_eq!(compiled.ret_ty(), &Type::Any);
2846        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2847        let result = unsafe { &*numbers() };
2848        assert_eq!(result.len(), 3);
2849        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
2850
2851        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
2852        assert_eq!(compiled.ret_ty(), &Type::Any);
2853        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2854        let result = unsafe { &*maps() };
2855        assert_eq!(result.len(), 2);
2856        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2857
2858        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
2859        assert_eq!(compiled.ret_ty(), &Type::Any);
2860        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2861        let result = unsafe { &*object_with_maps() };
2862        let steps = result.get_dynamic("steps").expect("steps");
2863        assert_eq!(steps.len(), 2);
2864        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2865
2866        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
2867        assert_eq!(compiled.ret_ty(), &Type::Any);
2868        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2869        let result = unsafe { &*return_maps() };
2870        assert_eq!(result.len(), 2);
2871        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2872
2873        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
2874        assert_eq!(compiled.ret_ty(), &Type::Any);
2875        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2876        let result = unsafe { &*return_maps_without_semicolon() };
2877        assert_eq!(result.len(), 2);
2878        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2879
2880        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
2881        assert_eq!(compiled.ret_ty(), &Type::Any);
2882        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2883        let result = unsafe { &*tail_bare_variable() };
2884        assert_eq!(result.len(), 2);
2885        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2886
2887        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
2888        assert_eq!(compiled.ret_ty(), &Type::Any);
2889        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2890        let result = unsafe { &*return_bare_variable_without_semicolon() };
2891        assert_eq!(result.len(), 2);
2892        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2893
2894        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
2895        assert_eq!(compiled.ret_ty(), &Type::Any);
2896        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2897        let result = unsafe { &*tail_object_variable() };
2898        let steps = result.get_dynamic("steps").expect("steps");
2899        assert_eq!(steps.len(), 2);
2900        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2901        Ok(())
2902    }
2903
2904    #[test]
2905    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
2906        let vm = Vm::with_all()?;
2907        vm.import_code(
2908            "vm_returned_list_get_idx",
2909            r#"
2910            pub fn ids() {
2911                [
2912                    "base",
2913                    "2",
2914                    "3"
2915                ]
2916            }
2917
2918            pub fn combinations() {
2919                let result = [];
2920                let values = ids();
2921                let idx = 0;
2922                while idx < values.len() {
2923                    result.push(values.get_idx(idx));
2924                    idx = idx + 1;
2925                }
2926                result
2927            }
2928            "#
2929            .as_bytes()
2930            .to_vec(),
2931        )?;
2932
2933        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
2934        assert_eq!(compiled.ret_ty(), &Type::Any);
2935        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2936        let result = unsafe { &*combinations() };
2937
2938        assert_eq!(result.len(), 3);
2939        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
2940        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
2941        Ok(())
2942    }
2943
2944    #[test]
2945    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
2946        fn extra_page_literal(depth: usize) -> String {
2947            let mut value = "{leaf: \"done\"}".to_string();
2948            for idx in 0..depth {
2949                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
2950            }
2951            value
2952        }
2953
2954        let extra = extra_page_literal(48);
2955        let code = format!(
2956            r#"
2957            pub fn script() {{
2958                return [
2959                    {{ja: "一つ目", note: "first", extra: {extra}}},
2960                    {{ja: "二つ目", note: "second", extra: {extra}}},
2961                    {{ja: "三つ目", note: "third", extra: {extra}}}
2962                ]
2963            }}
2964            "#
2965        );
2966
2967        let vm = Vm::with_all()?;
2968        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
2969        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
2970        assert_eq!(compiled.ret_ty(), &Type::Any);
2971        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2972        let result = unsafe { &*script() };
2973        assert_eq!(result.len(), 3);
2974        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
2975        Ok(())
2976    }
2977
2978    #[test]
2979    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
2980        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
2981        std::fs::write(&module_path, "pub fn value() { 41 }")?;
2982        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
2983
2984        let vm1 = Vm::with_all()?;
2985        vm1.import_code(
2986            "vm_import_owner",
2987            format!(
2988                r#"
2989                pub fn run() {{
2990                    import("vm_imported_owner", "{module_path}");
2991                }}
2992                "#
2993            )
2994            .into_bytes(),
2995        )?;
2996        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
2997
2998        let vm2 = Vm::with_all()?;
2999        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
3000        let _ = vm2.get_fn("vm_import_other::run", &[])?;
3001
3002        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
3003        run();
3004
3005        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
3006        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
3007        Ok(())
3008    }
3009
3010    #[test]
3011    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
3012        let vm = Vm::with_all()?;
3013        vm.import_code(
3014            "vm_object_last_call_field",
3015            r#"
3016            pub fn extra_page() {
3017                {
3018                    title: "extra",
3019                    pages: [
3020                        {note: "nested"}
3021                    ]
3022                }
3023            }
3024
3025            pub fn data() {
3026                return [
3027                    {
3028                        note: "first",
3029                        choices: ["a", "b"],
3030                        extras: extra_page()
3031                    },
3032                    {
3033                        note: "second",
3034                        choices: ["c"],
3035                        extras: extra_page()
3036                    }
3037                ]
3038            }
3039            "#
3040            .as_bytes()
3041            .to_vec(),
3042        )?;
3043
3044        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
3045        assert_eq!(compiled.ret_ty(), &Type::Any);
3046        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3047        let result = unsafe { &*data() };
3048        assert_eq!(result.len(), 2);
3049        let first = result.get_idx(0).expect("first step");
3050        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
3051        Ok(())
3052    }
3053
3054    #[test]
3055    fn string_return_survives_scope_exit() -> anyhow::Result<()> {
3056        let vm = Vm::with_all()?;
3057        vm.import_code(
3058            "vm_string_return_scope",
3059            r#"
3060            pub fn source_root() {
3061                "../assets/character/男主角换装"
3062            }
3063
3064            pub fn binary_root() {
3065                "character_binary/男主角换装"
3066            }
3067
3068            pub fn runtime_binary_url() {
3069                "/" + binary_root()
3070            }
3071
3072            pub fn action_groups() {
3073                let root = source_root();
3074                let binary_url = runtime_binary_url();
3075                let binary_root = binary_root();
3076                [
3077                    {
3078                        id: "field_bottom",
3079                        source_spine: root + "/战斗外/boy_b.spine",
3080                        skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
3081                        export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
3082                    }
3083                ]
3084            }
3085            "#
3086            .as_bytes()
3087            .to_vec(),
3088        )?;
3089
3090        let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
3091        assert_eq!(compiled.ret_ty(), &Type::Str);
3092        let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3093        let source_root = unsafe { &*source_root() };
3094        assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
3095
3096        let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
3097        assert_eq!(compiled.ret_ty(), &Type::Any);
3098        let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3099        let groups = unsafe { &*action_groups() };
3100        let first = groups.get_idx(0).expect("first action group");
3101        assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
3102        assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
3103        Ok(())
3104    }
3105
3106    #[test]
3107    fn dynamic_string_add_uses_any_binary_fast_path() -> anyhow::Result<()> {
3108        let vm = Vm::with_all()?;
3109        vm.import_code(
3110            "vm_dynamic_string_add",
3111            br#"
3112            pub fn concat(left, right) {
3113                left + right
3114            }
3115            "#
3116            .to_vec(),
3117        )?;
3118
3119        let compiled = vm.get_fn("vm_dynamic_string_add::concat", &[Type::Any, Type::Any])?;
3120        assert_eq!(compiled.ret_ty(), &Type::Any);
3121        let concat: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3122        let left = Dynamic::from("hello");
3123        let right = Dynamic::from(" world");
3124        let result = unsafe { &*concat(&left, &right) };
3125        assert_eq!(result.as_str(), "hello world");
3126        Ok(())
3127    }
3128
3129    #[test]
3130    fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
3131        let vm = Vm::with_all()?;
3132        let model_count = 180;
3133        let combination_count = 90;
3134        let models = (0..model_count)
3135            .map(|idx| {
3136                format!(
3137                    r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
3138                        {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
3139                        {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
3140                        {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
3141                    ]}}"#
3142                )
3143            })
3144            .collect::<Vec<_>>()
3145            .join(",\n");
3146        let combinations = (0..combination_count).map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#)).collect::<Vec<_>>().join(",\n");
3147        let code = format!(
3148            r#"
3149            pub fn source_root() {{
3150                "/美术资源/角色/少年/默认"
3151            }}
3152
3153            pub fn runtime_boy_url() {{
3154                "/cdn/runtime/角色/少年/少年.model.json"
3155            }}
3156
3157            pub fn parts() {{
3158                [
3159                    {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
3160                    {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
3161                    {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
3162                ]
3163            }}
3164
3165            pub fn action_groups() {{
3166                {{
3167                    idle: [
3168                        {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
3169                        {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
3170                    ],
3171                    move: [
3172                        {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
3173                        {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
3174                    ]
3175                }}
3176            }}
3177
3178            pub fn default_model() {{
3179                {{
3180                    id: "runtime_boy",
3181                    name: "运行时少年",
3182                    skins: [
3183                        {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
3184                        {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
3185                    ],
3186                    models: [
3187                        {models}
3188                    ]
3189                }}
3190            }}
3191
3192            pub fn first_nine_combinations() {{
3193                [
3194                    {combinations}
3195                ]
3196            }}
3197
3198            pub fn config() {{
3199                {{
3200                    source_root: source_root(),
3201                    runtime_boy_url: runtime_boy_url(),
3202                    parts: parts(),
3203                    action_groups: action_groups(),
3204                    default_model: default_model(),
3205                    first_nine_combinations: first_nine_combinations()
3206                }}
3207            }}
3208
3209            pub fn start() {{
3210                root::add("local/vm_large_inline_call_object/config", {{
3211                    source_root: source_root(),
3212                    runtime_boy_url: runtime_boy_url(),
3213                    parts: parts(),
3214                    action_groups: action_groups(),
3215                    default_model: default_model(),
3216                    first_nine_combinations: first_nine_combinations()
3217                }})
3218            }}
3219            "#
3220        );
3221        vm.import_code("vm_large_inline_call_object", code.into_bytes())?;
3222
3223        let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
3224        assert_eq!(compiled.ret_ty(), &Type::Any);
3225        let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3226        let result = unsafe { &*config() };
3227        assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
3228        assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3229
3230        let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
3231        assert_eq!(compiled.ret_ty(), &Type::Bool);
3232        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3233        assert!(start());
3234        let saved = root::get("local/vm_large_inline_call_object/config")?;
3235        assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3236        Ok(())
3237    }
3238
3239    #[cfg(feature = "http")]
3240    #[test]
3241    fn http_serve_accepts_inline_config_map() -> anyhow::Result<()> {
3242        let vm = Vm::with_all()?;
3243        vm.import_code(
3244            "vm_http_serve_inline_config",
3245            br#"
3246            pub fn start() {
3247                let server = http::serve({host: "127.0.0.1:5192"});
3248                server
3249            }
3250            "#
3251            .to_vec(),
3252        )?;
3253
3254        let compiled = vm.get_fn("vm_http_serve_inline_config::start", &[])?;
3255        assert_eq!(compiled.ret_ty(), &Type::Any);
3256        Ok(())
3257    }
3258
3259    #[cfg(feature = "http")]
3260    #[test]
3261    fn http_serve_accepts_variable_and_quoted_static_key() -> anyhow::Result<()> {
3262        let vm = Vm::with_all()?;
3263        vm.import_code(
3264            "vm_http_serve_quoted_static",
3265            br#"
3266            pub fn start(server_addr) {
3267                let http_server = http::serve({
3268                    host: server_addr,
3269                    ws: true,
3270                    upload: "upload",
3271                    "static": {
3272                        path: "/",
3273                        dir: "public/local"
3274                    }
3275                });
3276                http_server
3277            }
3278            "#
3279            .to_vec(),
3280        )?;
3281
3282        let compiled = vm.get_fn("vm_http_serve_quoted_static::start", &[Type::Any])?;
3283        assert_eq!(compiled.ret_ty(), &Type::Any);
3284        Ok(())
3285    }
3286
3287    #[cfg(all(feature = "http", feature = "llm"))]
3288    #[test]
3289    fn oss_helpers_accept_explicit_config() -> anyhow::Result<()> {
3290        let vm = Vm::with_all()?;
3291        vm.import_code(
3292            "vm_oss_explicit_config",
3293            br#"
3294            pub fn upload(oss, bytes) {
3295                oss::upload(oss, "llm/input/audio.wav", bytes)
3296            }
3297
3298            pub fn http_upload(oss, bytes) {
3299                http::upload(oss, "uploads/input.bin", bytes)
3300            }
3301
3302            pub fn link(oss, uploaded) {
3303                oss::signed_url(oss, {oss_url: uploaded, expires: 3600})
3304            }
3305            "#
3306            .to_vec(),
3307        )?;
3308
3309        assert_eq!(vm.get_fn("vm_oss_explicit_config::upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3310        assert_eq!(vm.get_fn("vm_oss_explicit_config::http_upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3311        assert_eq!(vm.get_fn("vm_oss_explicit_config::link", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3312        Ok(())
3313    }
3314
3315    #[cfg(feature = "http")]
3316    #[test]
3317    fn load_script_accepts_http_serve_inline_config() -> anyhow::Result<()> {
3318        let vm = Vm::with_all()?;
3319        let (_fn_ptr, ty) = vm.load(
3320            br#"
3321            let server_addr = "127.0.0.1:5192";
3322            let http_server = http::serve({
3323                host: server_addr,
3324                ws: true,
3325                upload: "upload",
3326                "static": {
3327                    path: "/",
3328                    dir: "public/local"
3329                }
3330            });
3331            http_server
3332            "#
3333            .to_vec(),
3334            "arg".into(),
3335        )?;
3336
3337        assert_eq!(ty, Type::Any);
3338        Ok(())
3339    }
3340
3341    #[test]
3342    fn load_script_resolves_import_before_compile() -> anyhow::Result<()> {
3343        let module_path = std::env::temp_dir().join(format!("zust_vm_load_import_{}.zs", std::process::id()));
3344        std::fs::write(&module_path, "pub fn init() { return {ok: true}; }")?;
3345        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
3346
3347        let vm = Vm::with_all()?;
3348        let (_fn_ptr, ty) = vm.load(
3349            format!(
3350                r#"
3351                import("create_scene", "{module_path}");
3352                create_scene::init();
3353                "#
3354            )
3355            .into_bytes(),
3356            "req".into(),
3357        )?;
3358
3359        assert_eq!(ty, Type::Void);
3360        Ok(())
3361    }
3362
3363    #[test]
3364    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
3365        let vm = Vm::with_all()?;
3366        vm.import_code(
3367            "vm_gpu_layout",
3368            br#"
3369            pub struct Params {
3370                a: u32,
3371                b: u32,
3372                c: u32,
3373            }
3374            "#
3375            .to_vec(),
3376        )?;
3377
3378        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
3379        assert_eq!(layout.size, 16);
3380        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
3381
3382        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
3383        let bytes = layout.pack_map(&value)?;
3384        assert_eq!(bytes.len(), 16);
3385        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
3386        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
3387        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
3388
3389        let read = layout.unpack_map(&bytes)?;
3390        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
3391        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
3392        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
3393        Ok(())
3394    }
3395
3396    #[test]
3397    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
3398        let vm = Vm::with_all()?;
3399        vm.import_code(
3400            "vm_root_clone_bridge",
3401            br#"
3402            pub fn add_then_reuse(arg) {
3403                let user = {
3404                    address: "test-wallet",
3405                    points: 20
3406                };
3407                root::add("local/root-clone-bridge-user", user);
3408                user.points = user.points - 7;
3409                root::add("local/root-clone-bridge-user", user);
3410                {
3411                    user: user,
3412                    points: user.points
3413                }
3414            }
3415
3416            pub fn clone_then_mutate(arg) {
3417                let user = {
3418                    profile: {
3419                        points: 20
3420                    }
3421                };
3422                let copied = user.clone();
3423                copied.profile.points = 13;
3424                user
3425            }
3426            "#
3427            .to_vec(),
3428        )?;
3429
3430        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
3431        assert_eq!(compiled.ret_ty(), &Type::Any);
3432        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3433        let arg = Dynamic::Null;
3434        let result = add_then_reuse(&arg);
3435        let result = unsafe { &*result };
3436
3437        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
3438        let mut json = String::new();
3439        result.to_json(&mut json);
3440        assert!(json.contains("\"points\": 13"));
3441
3442        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
3443        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
3444        let result = clone_then_mutate(&arg);
3445        let result = unsafe { &*result };
3446        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
3447        Ok(())
3448    }
3449
3450    struct CounterForTypedReceiver {
3451        value: i64,
3452    }
3453
3454    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
3455        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
3456    }
3457
3458    struct NavMapForFunctionArg;
3459
3460    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
3461        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
3462    }
3463
3464    #[derive(Debug, Default)]
3465    struct PropertyForwardingObject {
3466        values: parking_lot::RwLock<BTreeMap<String, Dynamic>>,
3467    }
3468
3469    impl CustomProperty for PropertyForwardingObject {
3470        fn get_key(&self, key: &str) -> Option<Dynamic> {
3471            self.values.read().get(key).cloned()
3472        }
3473
3474        fn set_key(&self, key: &str, value: Dynamic) -> bool {
3475            self.values.write().insert(key.to_string(), value);
3476            true
3477        }
3478    }
3479
3480    extern "C" fn property_forwarding_object_new() -> *const Dynamic {
3481        Box::into_raw(Box::new(Dynamic::custom_with_properties(PropertyForwardingObject::default())))
3482    }
3483
3484    #[test]
3485    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
3486        let vm = Vm::with_all()?;
3487        vm.add_empty_type("Counter")?;
3488        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
3489        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
3490        vm.import_code(
3491            "vm_typed_receiver_method",
3492            br#"
3493            pub fn run(value) {
3494                value::<Counter>::get()
3495            }
3496            "#
3497            .to_vec(),
3498        )?;
3499
3500        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
3501        assert_eq!(compiled.ret_ty(), &Type::I64);
3502        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3503        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
3504
3505        assert_eq!(run(&value), 42);
3506        Ok(())
3507    }
3508
3509    #[test]
3510    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3511        let vm = Vm::with_all()?;
3512        vm.add_empty_type("NavMap")?;
3513        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3514        vm.import_code(
3515            "vm_native_custom_arg",
3516            br#"
3517            pub fn add_nav_spawns(world, navmap) {
3518                navmap
3519            }
3520
3521            pub fn run(world) {
3522                let navmap = NavMap::new();
3523                let with_spawns = add_nav_spawns(world, navmap);
3524                with_spawns
3525            }
3526            "#
3527            .to_vec(),
3528        )?;
3529
3530        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
3531        assert_eq!(compiled.ret_ty(), &Type::Any);
3532        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3533        let world = Dynamic::Null;
3534        let result = run(&world);
3535        let result = unsafe { &*result };
3536
3537        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3538        Ok(())
3539    }
3540
3541    #[test]
3542    fn any_field_assignment_forwards_to_custom_properties() -> anyhow::Result<()> {
3543        let vm = Vm::with_all()?;
3544        vm.add_empty_type("Dialog")?;
3545        vm.add_native_method_ptr("Dialog", "new", &[], Type::Any, property_forwarding_object_new as *const u8)?;
3546        vm.import_code(
3547            "vm_custom_property_forwarding",
3548            br#"
3549            pub fn run() {
3550                let dialog = Dialog::new();
3551                dialog.file_mode = 3;
3552                dialog.file_mode
3553            }
3554            "#
3555            .to_vec(),
3556        )?;
3557
3558        let compiled = vm.get_fn("vm_custom_property_forwarding::run", &[])?;
3559        assert_eq!(compiled.ret_ty(), &Type::Any);
3560        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3561        let result = unsafe { &*run() };
3562
3563        assert_eq!(result.as_int(), Some(3));
3564        Ok(())
3565    }
3566
3567    #[test]
3568    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3569        let vm = Vm::with_all()?;
3570        vm.add_empty_type("NavMap")?;
3571        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
3572        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3573        vm.import_code(
3574            "vm_native_custom_typed_arg",
3575            br#"
3576            pub fn add_nav_spawns(world, navmap) {
3577                navmap
3578            }
3579
3580            pub fn run(world) {
3581                let navmap: NavMap = NavMap::new();
3582                let with_spawns = add_nav_spawns(world, navmap);
3583                with_spawns
3584            }
3585            "#
3586            .to_vec(),
3587        )?;
3588
3589        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
3590        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3591        let world = Dynamic::Null;
3592        let result = run(&world);
3593        let result = unsafe { &*result };
3594
3595        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3596        Ok(())
3597    }
3598
3599    // ---- 新增边界条件测试 ----
3600
3601    #[test]
3602    fn dynamic_type_checks_on_null_and_primitive_values() -> anyhow::Result<()> {
3603        let vm = Vm::with_all()?;
3604        vm.import_code(
3605            "vm_dynamic_type_checks",
3606            br#"
3607            pub fn is_list_on_int() {
3608                let x = 42i64;
3609                x.is_list()
3610            }
3611
3612            pub fn is_map_on_int() {
3613                let x = 42i64;
3614                x.is_map()
3615            }
3616
3617            pub fn is_null_on_int() {
3618                let x = 42i64;
3619                x.is_null()
3620            }
3621            "#
3622            .to_vec(),
3623        )?;
3624
3625        let compiled = vm.get_fn("vm_dynamic_type_checks::is_list_on_int", &[])?;
3626        assert_eq!(compiled.ret_ty(), &Type::Bool);
3627        let is_list_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3628        assert!(!is_list_on_int());
3629
3630        let compiled = vm.get_fn("vm_dynamic_type_checks::is_map_on_int", &[])?;
3631        assert_eq!(compiled.ret_ty(), &Type::Bool);
3632        let is_map_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3633        assert!(!is_map_on_int());
3634
3635        let compiled = vm.get_fn("vm_dynamic_type_checks::is_null_on_int", &[])?;
3636        assert_eq!(compiled.ret_ty(), &Type::Bool);
3637        let is_null_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3638        assert!(!is_null_on_int());
3639        Ok(())
3640    }
3641
3642    #[test]
3643    fn void_and_null_are_false_in_boolean_context() -> anyhow::Result<()> {
3644        let vm = Vm::with_all()?;
3645        vm.import_code(
3646            "vm_void_bool_context",
3647            br#"
3648            pub fn run() {
3649                let items = [1i32, 2i32];
3650                let ok1 = !(items.push(3i32) && false);
3651                let ok2 = !(true && items.push(4i32));
3652                let ok3 = null || true;
3653                let ok4 = null || items.len() == 4;
3654                ok1 && ok2 && ok3 && ok4
3655            }
3656            "#
3657            .to_vec(),
3658        )?;
3659
3660        let compiled = vm.get_fn("vm_void_bool_context::run", &[])?;
3661        assert_eq!(compiled.ret_ty(), &Type::Bool);
3662        let run: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3663        assert!(run());
3664        Ok(())
3665    }
3666
3667    #[test]
3668    fn empty_for_loop_range_has_zero_iterations() -> anyhow::Result<()> {
3669        let vm = Vm::with_all()?;
3670        vm.import_code(
3671            "vm_empty_for_range",
3672            br#"
3673            pub fn empty_exclusive() {
3674                let count = 0i32;
3675                for i in 0..0 {
3676                    count += i;
3677                }
3678                count
3679            }
3680
3681            pub fn single_inclusive_iteration() {
3682                let count = 0i32;
3683                for i in 5..=5 {
3684                    count += i;
3685                }
3686                count
3687            }
3688            "#
3689            .to_vec(),
3690        )?;
3691
3692        // 无后缀 range 字面量(0..0 / 5..=5)默认 I64,累加器随复合赋值提升为 I64
3693        let compiled = vm.get_fn("vm_empty_for_range::empty_exclusive", &[])?;
3694        assert_eq!(compiled.ret_ty(), &Type::I64);
3695        let empty_exclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3696        assert_eq!(empty_exclusive(), 0);
3697
3698        let compiled = vm.get_fn("vm_empty_for_range::single_inclusive_iteration", &[])?;
3699        assert_eq!(compiled.ret_ty(), &Type::I64);
3700        let single_inclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3701        assert_eq!(single_inclusive(), 5);
3702        Ok(())
3703    }
3704
3705    #[test]
3706    fn for_loop_range_accepts_dynamic_i64_bounds() -> anyhow::Result<()> {
3707        let vm = Vm::with_all()?;
3708        vm.import_code(
3709            "vm_dynamic_for_range",
3710            br#"
3711            pub fn main() {
3712                let view = {};
3713                view.grid_min_x = -2i64;
3714                view.grid_max_x = 2i64;
3715
3716                let end_x = view.grid_max_x + 1i64;
3717                let count = 0i64;
3718
3719                for x in view.grid_min_x..end_x {
3720                    count += 1i64;
3721                }
3722
3723                count
3724            }
3725            "#
3726            .to_vec(),
3727        )?;
3728
3729        let compiled = vm.get_fn("vm_dynamic_for_range::main", &[])?;
3730        assert_eq!(compiled.ret_ty(), &Type::I64);
3731        let main: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3732        assert_eq!(main(), 5);
3733        Ok(())
3734    }
3735
3736    #[test]
3737    fn map_contains_key_on_non_existent_and_nested_keys() -> anyhow::Result<()> {
3738        let vm = Vm::with_all()?;
3739        vm.import_code(
3740            "vm_map_contains",
3741            br#"
3742            pub fn contains_existing(data) {
3743                data.contains("name")
3744            }
3745
3746            pub fn contains_missing(data) {
3747                data.contains("nothing")
3748            }
3749            "#
3750            .to_vec(),
3751        )?;
3752
3753        let compiled = vm.get_fn("vm_map_contains::contains_existing", &[Type::Any])?;
3754        assert_eq!(compiled.ret_ty(), &Type::Bool);
3755        let contains_existing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3756        let data = dynamic::map!("name"=> "test");
3757        assert!(contains_existing(&data));
3758
3759        let compiled = vm.get_fn("vm_map_contains::contains_missing", &[Type::Any])?;
3760        assert_eq!(compiled.ret_ty(), &Type::Bool);
3761        let contains_missing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3762        assert!(!contains_missing(&data));
3763        Ok(())
3764    }
3765
3766    #[test]
3767    fn list_pop_on_empty_list_returns_null() -> anyhow::Result<()> {
3768        let vm = Vm::with_all()?;
3769        vm.import_code(
3770            "vm_pop_empty",
3771            br#"
3772            pub fn pop_new_list() {
3773                let items = [];
3774                let value = items.pop();
3775                let still_empty = items.len() == 0;
3776                {value: value, empty: still_empty}
3777            }
3778
3779            pub fn pop_until_empty() {
3780                let items = [1i64, 2i64];
3781                items.pop();
3782                let last = items.pop();
3783                let drained = items.pop();
3784                {last: last, drained: drained}
3785            }
3786            "#
3787            .to_vec(),
3788        )?;
3789
3790        let compiled = vm.get_fn("vm_pop_empty::pop_new_list", &[])?;
3791        assert_eq!(compiled.ret_ty(), &Type::Any);
3792        let pop_new_list: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3793        let result = unsafe { &*pop_new_list() };
3794        assert!(result.get_dynamic("value").is_some_and(|v| v.is_null()));
3795        assert_eq!(result.get_dynamic("empty").and_then(|v| v.as_bool()), Some(true));
3796
3797        let compiled = vm.get_fn("vm_pop_empty::pop_until_empty", &[])?;
3798        assert_eq!(compiled.ret_ty(), &Type::Any);
3799        let pop_until_empty: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3800        let result = unsafe { &*pop_until_empty() };
3801        assert_eq!(result.get_dynamic("last").and_then(|v| v.as_int()), Some(1));
3802        assert!(result.get_dynamic("drained").is_some_and(|v| v.is_null()));
3803        Ok(())
3804    }
3805
3806    #[test]
3807    fn void_function_with_multiple_code_paths() -> anyhow::Result<()> {
3808        let vm = Vm::with_all()?;
3809        vm.import_code(
3810            "vm_void_multi_path",
3811            br#"
3812            pub fn log_if_positive(value: i64) {
3813                if value > 0 {
3814                    print(value);
3815                    return;
3816                }
3817                if value < 0 {
3818                    print(-value);
3819                    return;
3820                }
3821                print(0);
3822            }
3823            "#
3824            .to_vec(),
3825        )?;
3826
3827        let compiled = vm.get_fn("vm_void_multi_path::log_if_positive", &[Type::I64])?;
3828        assert!(compiled.ret_ty().is_void());
3829        Ok(())
3830    }
3831
3832    #[test]
3833    fn any_method_call_chain_on_returned_dynamic_value() -> anyhow::Result<()> {
3834        let vm = Vm::with_all()?;
3835        vm.import_code(
3836            "vm_any_method_chain",
3837            br#"
3838            pub fn get_tags(data) {
3839                let tags = data.tags;
3840                if tags.is_list() {
3841                    return tags.len();
3842                }
3843                0
3844            }
3845            "#
3846            .to_vec(),
3847        )?;
3848
3849        let compiled = vm.get_fn("vm_any_method_chain::get_tags", &[Type::Any])?;
3850        assert_eq!(compiled.ret_ty(), &Type::I64);
3851        let get_tags: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3852        let data = dynamic::map!("tags"=> Dynamic::list(vec!["a".into(), "b".into(), "c".into()]));
3853        assert_eq!(get_tags(&data), 3);
3854
3855        let empty_data = Dynamic::Null;
3856        assert_eq!(get_tags(&empty_data), 0);
3857        Ok(())
3858    }
3859
3860    #[test]
3861    fn infers_any_arg_function_return_before_body_compile() -> anyhow::Result<()> {
3862        let vm = Vm::with_all()?;
3863        vm.import_code(
3864            "vm_infer_any_arg_return",
3865            br#"
3866            pub fn caller(candidate) {
3867                let center = polygon_center(candidate.visualPolygon);
3868                center[0]
3869            }
3870
3871            pub fn polygon_center(point_list) {
3872                let total_x = 0;
3873                let total_y = 0;
3874                let count = 0;
3875                if point_list.is_list() {
3876                    for point in point_list {
3877                        if point.is_list() && point.len() >= 2 {
3878                            total_x += point[0];
3879                            total_y += point[1];
3880                            count += 1;
3881                        }
3882                    }
3883                }
3884                if count == 0 {
3885                    return [0, 0];
3886                }
3887                [total_x / count, total_y / count]
3888            }
3889            "#
3890            .to_vec(),
3891        )?;
3892
3893        let compiled = vm.get_fn("vm_infer_any_arg_return::caller", &[Type::Any])?;
3894        assert_eq!(compiled.ret_ty(), &Type::Any);
3895        let caller: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3896        let candidate = dynamic::map!(
3897            "visualPolygon"=> Dynamic::list(vec![
3898                Dynamic::list(vec![2i64.into(), 4i64.into()]),
3899                Dynamic::list(vec![6i64.into(), 8i64.into()]),
3900            ])
3901        );
3902        let result = unsafe { &*caller(&candidate) };
3903        assert_eq!(result.as_int(), Some(4));
3904        Ok(())
3905    }
3906
3907    #[test]
3908    fn recursive_factorial_keeps_static_return_type() -> anyhow::Result<()> {
3909        let vm = Vm::with_all()?;
3910        vm.import_code(
3911            "vm_recursive_factorial",
3912            br#"
3913            fn factorial(n: i64) {
3914                if n <= 1 {
3915                    return 1;
3916                }
3917                n * factorial(n - 1)
3918            }
3919
3920            pub fn run(n: i64) {
3921                factorial(n)
3922            }
3923            "#
3924            .to_vec(),
3925        )?;
3926
3927        let compiled = vm.get_fn("vm_recursive_factorial::run", &[Type::I64])?;
3928        assert_eq!(compiled.ret_ty(), &Type::I64);
3929        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3930        assert_eq!(run(5), 120);
3931        Ok(())
3932    }
3933
3934    #[test]
3935    fn explicit_const_generic_function_calls_generate_distinct_variants() -> anyhow::Result<()> {
3936        let vm = Vm::with_all()?;
3937        vm.import_code(
3938            "vm_generic_const_variants",
3939            br#"
3940            fn value<N>() {
3941                N
3942            }
3943
3944            pub fn two() {
3945                value::<2>()
3946            }
3947
3948            pub fn three() {
3949                value::<3>()
3950            }
3951            "#
3952            .to_vec(),
3953        )?;
3954
3955        let compiled = vm.get_fn("vm_generic_const_variants::two", &[])?;
3956        assert_eq!(compiled.ret_ty(), &Type::I32);
3957        let two: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3958        assert_eq!(two(), 2);
3959
3960        let compiled = vm.get_fn("vm_generic_const_variants::three", &[])?;
3961        assert_eq!(compiled.ret_ty(), &Type::I32);
3962        let three: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3963        assert_eq!(three(), 3);
3964        Ok(())
3965    }
3966
3967    #[test]
3968    fn generic_function_body_resolves_private_generic_helper_after_import() -> anyhow::Result<()> {
3969        let vm = Vm::with_all()?;
3970        vm.import_code(
3971            "vm_generic_private_helper",
3972            br#"
3973            fn helper<N>() {
3974                N
3975            }
3976
3977            pub fn bench<N>() {
3978                helper::<N>()
3979            }
3980            "#
3981            .to_vec(),
3982        )?;
3983
3984        let compiled = vm.get_fn_with_params("vm_generic_private_helper::bench", &[], &[Type::ConstInt(7)])?;
3985        assert_eq!(compiled.ret_ty(), &Type::I32);
3986        let run: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3987        assert_eq!(run(), 7);
3988        Ok(())
3989    }
3990
3991    #[test]
3992    fn const_generic_repeat_array_initializes_all_items() -> anyhow::Result<()> {
3993        let vm = Vm::with_all()?;
3994        vm.import_code(
3995            "vm_generic_repeat_array",
3996            br#"
3997            fn bench<N>() {
3998                let is_prime = [true; N];
3999                is_prime[0] = false;
4000                is_prime[1] = false;
4001                let count = 0i64;
4002                for p in 2i64..N {
4003                    if is_prime[p] == true {
4004                        count = count + 1;
4005                        let step = p;
4006                        let j = p * p;
4007                        while j < N {
4008                            is_prime[j] = false;
4009                            j = j + step;
4010                        }
4011                    }
4012                }
4013                count
4014            }
4015
4016            pub fn run() {
4017                bench::<10>()
4018            }
4019
4020            pub fn run_1000() {
4021                bench::<1000>()
4022            }
4023
4024            pub fn run_100000() {
4025                bench::<100000>()
4026            }
4027            "#
4028            .to_vec(),
4029        )?;
4030
4031        let compiled = vm.get_fn("vm_generic_repeat_array::run", &[])?;
4032        assert_eq!(compiled.ret_ty(), &Type::I64);
4033        let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4034        assert_eq!(run(), 4);
4035
4036        let compiled = vm.get_fn("vm_generic_repeat_array::run_1000", &[])?;
4037        assert_eq!(compiled.ret_ty(), &Type::I64);
4038        let run_1000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4039        assert_eq!(run_1000(), 168);
4040
4041        let compiled = vm.get_fn("vm_generic_repeat_array::run_100000", &[])?;
4042        assert_eq!(compiled.ret_ty(), &Type::I64);
4043        let run_100000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4044        assert_eq!(run_100000(), 9592);
4045        Ok(())
4046    }
4047
4048    #[test]
4049    fn repeat_array_initializes_scalar_patterns() -> anyhow::Result<()> {
4050        let vm = Vm::with_all()?;
4051        vm.import_code(
4052            "vm_repeat_scalar_patterns",
4053            br#"
4054            pub fn count_true() {
4055                let items = [true; 100000];
4056                let count = 0i64;
4057                for idx in 0i64..100000 {
4058                    if items[idx] == true {
4059                        count = count + 1;
4060                    }
4061                }
4062                count
4063            }
4064
4065            pub fn i32_pair() {
4066                let items = [-7i32; 1000];
4067                items[0i64] + items[999i64]
4068            }
4069
4070            pub fn i64_pair() {
4071                let items = [1234567890123i64; 1000];
4072                items[0i64] + items[999i64]
4073            }
4074
4075            pub fn f64_pair() {
4076                let items = [1.5f64; 1000];
4077                items[0i64] + items[999i64]
4078            }
4079            "#
4080            .to_vec(),
4081        )?;
4082
4083        let compiled = vm.get_fn("vm_repeat_scalar_patterns::count_true", &[])?;
4084        assert_eq!(compiled.ret_ty(), &Type::I64);
4085        let count_true: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4086        assert_eq!(count_true(), 100000);
4087
4088        let compiled = vm.get_fn("vm_repeat_scalar_patterns::i32_pair", &[])?;
4089        assert_eq!(compiled.ret_ty(), &Type::I32);
4090        let i32_pair: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4091        assert_eq!(i32_pair(), -14);
4092
4093        let compiled = vm.get_fn("vm_repeat_scalar_patterns::i64_pair", &[])?;
4094        assert_eq!(compiled.ret_ty(), &Type::I64);
4095        let i64_pair: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4096        assert_eq!(i64_pair(), 2469135780246);
4097
4098        let compiled = vm.get_fn("vm_repeat_scalar_patterns::f64_pair", &[])?;
4099        assert_eq!(compiled.ret_ty(), &Type::F64);
4100        let f64_pair: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4101        assert_eq!(f64_pair(), 3.0);
4102        Ok(())
4103    }
4104
4105    #[test]
4106    fn bool_array_store_normalizes_condition_values() -> anyhow::Result<()> {
4107        let vm = Vm::with_all()?;
4108        vm.import_code(
4109            "vm_bool_array_store",
4110            br#"
4111            pub fn run() {
4112                let items = [false; 4];
4113                items[1] = 3i64 > 2i64;
4114                items[2] = 3i64 < 2i64;
4115                if items[1] == true && items[2] == false {
4116                    1i64
4117                } else {
4118                    0i64
4119                }
4120            }
4121            "#
4122            .to_vec(),
4123        )?;
4124
4125        let compiled = vm.get_fn("vm_bool_array_store::run", &[])?;
4126        assert_eq!(compiled.ret_ty(), &Type::I64);
4127        let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4128        assert_eq!(run(), 1);
4129        Ok(())
4130    }
4131
4132    #[test]
4133    fn bool_array_large_sequential_writes() -> anyhow::Result<()> {
4134        let vm = Vm::with_all()?;
4135        vm.import_code(
4136            "vm_bool_array_large_writes",
4137            br#"
4138            pub fn run() {
4139                let items = [true; 100000];
4140                for idx in 0i64..100000 {
4141                    items[idx] = false;
4142                }
4143                let count = 0i64;
4144                for idx in 0i64..100000 {
4145                    if items[idx] == false {
4146                        count = count + 1;
4147                    }
4148                }
4149                count
4150            }
4151            "#
4152            .to_vec(),
4153        )?;
4154
4155        let compiled = vm.get_fn("vm_bool_array_large_writes::run", &[])?;
4156        assert_eq!(compiled.ret_ty(), &Type::I64);
4157        let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4158        assert_eq!(run(), 100000);
4159        Ok(())
4160    }
4161
4162    #[test]
4163    fn bool_array_sieve_style_indices_stay_in_bounds() -> anyhow::Result<()> {
4164        let vm = Vm::with_all()?;
4165        vm.import_code(
4166            "vm_bool_array_sieve_indices",
4167            br#"
4168            pub fn run() {
4169                let items = [true; 100000];
4170                let writes = 0i64;
4171                for p in 2i64..100000 {
4172                    let step = p;
4173                    let j = p * p;
4174                    while j < 100000 {
4175                        items[j] = false;
4176                        writes = writes + 1;
4177                        j = j + step;
4178                    }
4179                }
4180                writes
4181            }
4182            "#
4183            .to_vec(),
4184        )?;
4185
4186        let compiled = vm.get_fn("vm_bool_array_sieve_indices::run", &[])?;
4187        assert_eq!(compiled.ret_ty(), &Type::I64);
4188        let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4189        assert!(run() > 0);
4190        Ok(())
4191    }
4192
4193    #[test]
4194    fn sieve_style_indices_compute_in_bounds_without_array_write() -> anyhow::Result<()> {
4195        let vm = Vm::with_all()?;
4196        vm.import_code(
4197            "vm_sieve_indices_no_write",
4198            br#"
4199            pub fn run() {
4200                let max_j = 0i64;
4201                for p in 2i64..100000 {
4202                    let step = p;
4203                    let j = p * p;
4204                    while j < 100000 {
4205                        if j < 0i64 {
4206                            return -1i64;
4207                        }
4208                        if j > max_j {
4209                            max_j = j;
4210                        }
4211                        j = j + step;
4212                    }
4213                }
4214                max_j
4215            }
4216            "#
4217            .to_vec(),
4218        )?;
4219
4220        let compiled = vm.get_fn("vm_sieve_indices_no_write::run", &[])?;
4221        assert_eq!(compiled.ret_ty(), &Type::I64);
4222        let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4223        assert_eq!(run(), 99999);
4224        Ok(())
4225    }
4226
4227    #[test]
4228    fn dynamic_list_index_sum_uses_static_accumulator_type() -> anyhow::Result<()> {
4229        let vm = Vm::with_all()?;
4230        vm.import_code(
4231            "vm_dynamic_index_sum",
4232            br#"
4233            pub fn sum_list(n: i64) {
4234                let l = [];
4235                for i in 0..n {
4236                    l.push(i);
4237                }
4238                let sum = 0i64;
4239                for j in 0..n {
4240                    sum = sum + l[j];
4241                }
4242                sum
4243            }
4244            "#
4245            .to_vec(),
4246        )?;
4247
4248        let compiled = vm.get_fn("vm_dynamic_index_sum::sum_list", &[Type::I64])?;
4249        let sum_list_id = vm.jit.write().compiler.symbols.get_id("vm_dynamic_index_sum::sum_list")?;
4250        let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_list_id, &[], &[Type::I64]);
4251        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I64)), "local type hints: {:?}", hints);
4252        assert_eq!(compiled.ret_ty(), &Type::I64);
4253        let sum_list: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4254        assert_eq!(sum_list(1000), 499500);
4255        Ok(())
4256    }
4257
4258    #[test]
4259    fn loop_pushed_list_is_typed_vector() -> anyhow::Result<()> {
4260        let vm = Vm::with_all()?;
4261        vm.import_code(
4262            "vm_loop_pushed_list",
4263            br#"
4264            pub fn make(n: i64) {
4265                let l = [];
4266                for i in 0..n {
4267                    l.push(i);
4268                }
4269                l
4270            }
4271            "#
4272            .to_vec(),
4273        )?;
4274        let compiled = vm.get_fn("vm_loop_pushed_list::make", &[Type::I64])?;
4275        let make: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4276        let result = unsafe { &*make(3) };
4277        assert!(matches!(result, Dynamic::VecI64(v) if v == &vec![0, 1, 2]), "expected flat VecI64, got: {:?}", result);
4278        Ok(())
4279    }
4280
4281    #[test]
4282    fn inferred_empty_list_uses_typed_dynamic_vector() -> anyhow::Result<()> {
4283        let vm = Vm::with_all()?;
4284        vm.import_code(
4285            "vm_inferred_typed_list",
4286            br#"
4287            pub fn make() {
4288                let l = [];
4289                l.push(1i64);
4290                l
4291            }
4292            "#
4293            .to_vec(),
4294        )?;
4295
4296        let compiled = vm.get_fn("vm_inferred_typed_list::make", &[])?;
4297        assert_eq!(compiled.ret_ty(), &Type::Any);
4298        let make: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4299        let result = unsafe { &*make() };
4300        assert!(matches!(result, Dynamic::VecI64(values) if values == &vec![1]), "result: {:?}", result);
4301        Ok(())
4302    }
4303
4304    #[test]
4305    fn for_in_iterates_list_filled_in_same_function() -> anyhow::Result<()> {
4306        let vm = Vm::with_all()?;
4307        vm.import_code(
4308            "vm_for_in_local_pushed_list",
4309            br#"
4310            pub fn sum_i32_items() {
4311                let items = [];
4312                items.push(6000i32);
4313                items.push(4000i32);
4314                let total = 0i32;
4315                for item in items {
4316                    total += item;
4317                }
4318                total
4319            }
4320
4321            pub fn sum_split_bps() {
4322                let splits = [];
4323                splits.push({ bps: "6000" });
4324                splits.push({ bps: 4000 });
4325                let total = 0i32;
4326                let count = 0i32;
4327                for split in splits {
4328                    total += split.bps as i32;
4329                    count += 1i32;
4330                }
4331                total + count
4332            }
4333            "#
4334            .to_vec(),
4335        )?;
4336
4337        let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_i32_items", &[])?;
4338        assert_eq!(compiled.ret_ty(), &Type::I32);
4339        let sum_i32_items: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4340        assert_eq!(sum_i32_items(), 10000);
4341
4342        let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_split_bps", &[])?;
4343        assert_eq!(compiled.ret_ty(), &Type::I32);
4344        let sum_split_bps: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4345        assert_eq!(sum_split_bps(), 10002);
4346        Ok(())
4347    }
4348
4349    #[test]
4350    fn inferred_list_shortcuts_cover_scalar_types() -> anyhow::Result<()> {
4351        let vm = Vm::with_all()?;
4352        vm.import_code(
4353            "vm_inferred_list_shortcuts",
4354            br#"
4355            pub fn second_bool() {
4356                let l = [];
4357                l.push(true);
4358                l.push(false);
4359                l[1]
4360            }
4361
4362            pub fn first_u8() {
4363                let l = [];
4364                l.push(7u8);
4365                l[0]
4366            }
4367
4368            pub fn sum_u8_for_in() {
4369                let l = [];
4370                l.push(7u8);
4371                l.push(8u8);
4372                let sum = 0i64;
4373                for item in l {
4374                    sum = sum + item as i64;
4375                }
4376                sum
4377            }
4378
4379            pub fn count_bool_for_in() {
4380                let l = [];
4381                l.push(true);
4382                l.push(false);
4383                l.push(true);
4384                let count = 0i64;
4385                for item in l {
4386                    if item {
4387                        count += 1i64;
4388                    }
4389                }
4390                count
4391            }
4392
4393            pub fn sum_i32(n: i64) {
4394                let l = [];
4395                for i in 0..n {
4396                    l.push(i as i32);
4397                }
4398                let sum = 0i32;
4399                for j in 0..n {
4400                    sum = sum + l[j];
4401                }
4402                sum
4403            }
4404
4405            pub fn sum_f32(n: i64) {
4406                let l = [];
4407                for i in 0..n {
4408                    l.push(i as f32);
4409                }
4410                let sum = 0f32;
4411                for j in 0..n {
4412                    sum = sum + l[j];
4413                }
4414                sum
4415            }
4416
4417            pub fn second_str() {
4418                let l = [];
4419                l.push("first");
4420                l.push("second");
4421                l[1]
4422            }
4423            "#
4424            .to_vec(),
4425        )?;
4426
4427        let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_bool", &[])?;
4428        let second_bool_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_bool")?;
4429        let hints = vm.jit.write().compiler.inferred_local_type_hints(second_bool_id, &[], &[]);
4430        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Bool)), "bool local type hints: {:?}", hints);
4431        assert_eq!(compiled.ret_ty(), &Type::Bool);
4432        let second_bool: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4433        assert!(!second_bool());
4434
4435        let compiled = vm.get_fn("vm_inferred_list_shortcuts::first_u8", &[])?;
4436        let first_u8_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::first_u8")?;
4437        let hints = vm.jit.write().compiler.inferred_local_type_hints(first_u8_id, &[], &[]);
4438        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::U8)), "u8 local type hints: {:?}", hints);
4439        assert_eq!(compiled.ret_ty(), &Type::U8);
4440        let first_u8: extern "C" fn() -> u8 = unsafe { std::mem::transmute(compiled.ptr()) };
4441        assert_eq!(first_u8(), 7);
4442
4443        let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_u8_for_in", &[])?;
4444        assert_eq!(compiled.ret_ty(), &Type::I64);
4445        let sum_u8_for_in: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4446        assert_eq!(sum_u8_for_in(), 15);
4447
4448        let compiled = vm.get_fn("vm_inferred_list_shortcuts::count_bool_for_in", &[])?;
4449        assert_eq!(compiled.ret_ty(), &Type::I64);
4450        let count_bool_for_in: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4451        assert_eq!(count_bool_for_in(), 2);
4452
4453        let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_i32", &[Type::I64])?;
4454        let sum_i32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_i32")?;
4455        let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_i32_id, &[], &[Type::I64]);
4456        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I32)), "i32 local type hints: {:?}", hints);
4457        assert_eq!(compiled.ret_ty(), &Type::I32);
4458        let sum_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4459        assert_eq!(sum_i32(100), 4950);
4460
4461        let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_f32", &[Type::I64])?;
4462        let sum_f32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_f32")?;
4463        let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_f32_id, &[], &[Type::I64]);
4464        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::F32)), "f32 local type hints: {:?}", hints);
4465        assert_eq!(compiled.ret_ty(), &Type::F32);
4466        let sum_f32: extern "C" fn(i64) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
4467        assert_eq!(sum_f32(10), 45.0);
4468
4469        let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_str", &[])?;
4470        let second_str_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_str")?;
4471        let hints = vm.jit.write().compiler.inferred_local_type_hints(second_str_id, &[], &[]);
4472        assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Str)), "str local type hints: {:?}", hints);
4473        assert_eq!(compiled.ret_ty(), &Type::Str);
4474        let second_str: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4475        let result = unsafe { &*second_str() };
4476        assert_eq!(result.as_str(), "second");
4477        Ok(())
4478    }
4479
4480    #[test]
4481    fn inferred_list_supports_bracket_set_idx() -> anyhow::Result<()> {
4482        let vm = Vm::with_all()?;
4483        vm.import_code(
4484            "vm_inferred_list_set_idx",
4485            br#"
4486            pub fn swap_first_two() {
4487                let items = [];
4488                items.push(1i64);
4489                items.push(2i64);
4490                let j = 0i64;
4491                let a = items[j];
4492                let b = items[j + 1];
4493                items[j] = b;
4494                items[j + 1] = a;
4495                items[0] * 10i64 + items[1]
4496            }
4497
4498            pub fn replace_string() {
4499                let items = [];
4500                items.push("old");
4501                items[0] = "new";
4502                items[0]
4503            }
4504            "#
4505            .to_vec(),
4506        )?;
4507
4508        let compiled = vm.get_fn("vm_inferred_list_set_idx::swap_first_two", &[])?;
4509        assert_eq!(compiled.ret_ty(), &Type::I64);
4510        let swap_first_two: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4511        assert_eq!(swap_first_two(), 21);
4512
4513        let compiled = vm.get_fn("vm_inferred_list_set_idx::replace_string", &[])?;
4514        assert_eq!(compiled.ret_ty(), &Type::Str);
4515        let replace_string: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4516        let result = unsafe { &*replace_string() };
4517        assert_eq!(result.as_str(), "new");
4518        Ok(())
4519    }
4520
4521    #[test]
4522    fn root_get_returns_null_for_missing_key_which_compares_correctly() -> anyhow::Result<()> {
4523        let vm = Vm::with_all()?;
4524        vm.import_code(
4525            "vm_root_get_missing",
4526            br#"
4527            pub fn check_missing() {
4528                let existing = root::get("local/vm_root_get_missing_test");
4529                if existing.is_map() {
4530                    return false;
4531                }
4532                true
4533            }
4534            "#
4535            .to_vec(),
4536        )?;
4537
4538        let compiled = vm.get_fn("vm_root_get_missing::check_missing", &[])?;
4539        assert_eq!(compiled.ret_ty(), &Type::Bool);
4540        let check_missing: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4541        assert!(check_missing());
4542        Ok(())
4543    }
4544
4545    #[test]
4546    fn map_get_key_on_null_map_returns_null() -> anyhow::Result<()> {
4547        let vm = Vm::with_all()?;
4548        vm.import_code(
4549            "vm_get_key_null_map",
4550            br#"
4551            pub fn get_key_null(data) {
4552                data.get_key("missing")
4553            }
4554            "#
4555            .to_vec(),
4556        )?;
4557
4558        let compiled = vm.get_fn("vm_get_key_null_map::get_key_null", &[Type::Any])?;
4559        assert_eq!(compiled.ret_ty(), &Type::Any);
4560        let get_key_null: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4561
4562        let data_map = dynamic::map!("exists"=> 1i64);
4563        let missing = unsafe { &*get_key_null(&data_map) };
4564        assert!(missing.is_null());
4565
4566        let null = Dynamic::Null;
4567        let result = unsafe { &*get_key_null(&null) };
4568        assert!(result.is_null());
4569        Ok(())
4570    }
4571
4572    #[test]
4573    fn keys_on_empty_map_returns_empty_list() -> anyhow::Result<()> {
4574        let vm = Vm::with_all()?;
4575        vm.import_code(
4576            "vm_keys_empty_map",
4577            br#"
4578            pub fn empty_map_keys() {
4579                let data = {};
4580                data.keys().len()
4581            }
4582            "#
4583            .to_vec(),
4584        )?;
4585
4586        let compiled = vm.get_fn("vm_keys_empty_map::empty_map_keys", &[])?;
4587        assert_eq!(compiled.ret_ty(), &Type::I32);
4588        let empty_map_keys: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4589        assert_eq!(empty_map_keys(), 0);
4590        Ok(())
4591    }
4592
4593    #[test]
4594    fn cast_between_all_integer_widths() -> anyhow::Result<()> {
4595        let vm = Vm::with_all()?;
4596        vm.import_code(
4597            "vm_cast_integer_widths",
4598            br#"
4599            pub fn i64_to_i32(value: i64) {
4600                value as i32
4601            }
4602
4603            pub fn i32_to_i64(value: i32) {
4604                value as i64
4605            }
4606
4607            pub fn u32_to_i64(value: u32) {
4608                value as i64
4609            }
4610            "#
4611            .to_vec(),
4612        )?;
4613
4614        let compiled = vm.get_fn("vm_cast_integer_widths::i64_to_i32", &[Type::I64])?;
4615        assert_eq!(compiled.ret_ty(), &Type::I32);
4616        let i64_to_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4617        assert_eq!(i64_to_i32(42), 42);
4618
4619        let compiled = vm.get_fn("vm_cast_integer_widths::i32_to_i64", &[Type::I32])?;
4620        assert_eq!(compiled.ret_ty(), &Type::I64);
4621        let i32_to_i64: extern "C" fn(i32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4622        assert_eq!(i32_to_i64(-1), -1);
4623
4624        let compiled = vm.get_fn("vm_cast_integer_widths::u32_to_i64", &[Type::U32])?;
4625        assert_eq!(compiled.ret_ty(), &Type::I64);
4626        let u32_to_i64: extern "C" fn(u32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4627        assert_eq!(u32_to_i64(42), 42);
4628        Ok(())
4629    }
4630
4631    #[test]
4632    fn boolean_literals_in_complex_expression_trees() -> anyhow::Result<()> {
4633        let vm = Vm::with_all()?;
4634        vm.import_code(
4635            "vm_complex_boolean",
4636            br#"
4637            pub fn exclusive_or(a: bool, b: bool) {
4638                (a && !b) || (!a && b)
4639            }
4640
4641            pub fn implies(a: bool, b: bool) {
4642                !a || b
4643            }
4644            "#
4645            .to_vec(),
4646        )?;
4647
4648        let compiled = vm.get_fn("vm_complex_boolean::exclusive_or", &[Type::Bool, Type::Bool])?;
4649        assert_eq!(compiled.ret_ty(), &Type::Bool);
4650        let exclusive_or: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4651        assert!(exclusive_or(true, false));
4652        assert!(exclusive_or(false, true));
4653        assert!(!exclusive_or(true, true));
4654        assert!(!exclusive_or(false, false));
4655
4656        let compiled = vm.get_fn("vm_complex_boolean::implies", &[Type::Bool, Type::Bool])?;
4657        assert_eq!(compiled.ret_ty(), &Type::Bool);
4658        let implies: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4659        assert!(implies(false, true));
4660        assert!(implies(false, false));
4661        assert!(implies(true, true));
4662        assert!(!implies(true, false));
4663        Ok(())
4664    }
4665
4666    #[test]
4667    fn concrete_struct_method_returning_self_type() -> anyhow::Result<()> {
4668        let vm = Vm::with_all()?;
4669        vm.import_code(
4670            "vm_struct_method_self",
4671            br#"
4672            pub struct Vec3 {
4673                x: f64,
4674                y: f64,
4675                z: f64,
4676            }
4677
4678            impl Vec3 {
4679                pub fn add(self: Vec3, other: Vec3) {
4680                    Vec3{x: self.x + other.x, y: self.y + other.y, z: self.z + other.z}
4681                }
4682            }
4683
4684            pub fn run() {
4685                let v1 = Vec3{x: 1.0f64, y: 2.0f64, z: 3.0f64};
4686                let v2 = Vec3{x: 4.0f64, y: 5.0f64, z: 6.0f64};
4687                let sum = v1.add(v2);
4688                sum.x + sum.y + sum.z
4689            }
4690            "#
4691            .to_vec(),
4692        )?;
4693
4694        let compiled = vm.get_fn("vm_struct_method_self::run", &[])?;
4695        assert_eq!(compiled.ret_ty(), &Type::F64);
4696        let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4697        assert_eq!(run(), 21.0);
4698        Ok(())
4699    }
4700
4701    #[test]
4702    fn deep_nested_struct_access_with_multiple_field_levels() -> anyhow::Result<()> {
4703        let vm = Vm::with_all()?;
4704        vm.import_code(
4705            "vm_deep_nested_struct",
4706            br#"
4707            pub struct A {
4708                value: i64,
4709            }
4710
4711            pub struct B {
4712                a: A,
4713            }
4714
4715            pub struct C {
4716                b: B,
4717            }
4718
4719            pub fn direct_access() {
4720                let c = C{b: B{a: A{value: 99}}};
4721                c.b.a.value
4722            }
4723
4724            pub fn via_variable() {
4725                let c = C{b: B{a: A{value: 77}}};
4726                let b = c.b;
4727                let a = b.a;
4728                a.value
4729            }
4730            "#
4731            .to_vec(),
4732        )?;
4733
4734        let compiled = vm.get_fn("vm_deep_nested_struct::direct_access", &[])?;
4735        assert_eq!(compiled.ret_ty(), &Type::I64);
4736        let direct_access: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4737        assert_eq!(direct_access(), 99);
4738
4739        let compiled = vm.get_fn("vm_deep_nested_struct::via_variable", &[])?;
4740        assert_eq!(compiled.ret_ty(), &Type::I64);
4741        let via_variable: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4742        assert_eq!(via_variable(), 77);
4743        Ok(())
4744    }
4745
4746    #[test]
4747    fn array_index_with_dynamic_value_via_method() -> anyhow::Result<()> {
4748        let vm = Vm::with_all()?;
4749        vm.import_code(
4750            "vm_array_idx_dynamic",
4751            br#"
4752            pub fn get_by_idx(list, idx) {
4753                list.get_idx(idx)
4754            }
4755            "#
4756            .to_vec(),
4757        )?;
4758
4759        let compiled = vm.get_fn("vm_array_idx_dynamic::get_by_idx", &[Type::Any, Type::I64])?;
4760        assert_eq!(compiled.ret_ty(), &Type::Any);
4761        let get_by_idx: extern "C" fn(*const Dynamic, i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4762
4763        let list = Dynamic::list(vec!["a".into(), "b".into()]);
4764        let first = unsafe { &*get_by_idx(&list, 0) };
4765        assert_eq!(first.as_str(), "a");
4766
4767        let out = unsafe { &*get_by_idx(&list, 10) };
4768        assert!(out.is_null());
4769        Ok(())
4770    }
4771
4772    #[test]
4773    fn dynamic_field_access_with_optional_or_fallback() -> anyhow::Result<()> {
4774        let vm = Vm::with_all()?;
4775        vm.import_code(
4776            "vm_dynamic_or_fallback",
4777            br#"
4778            pub fn with_fallback(data) {
4779                if data.contains("name") { data.name } else { "unknown" }
4780            }
4781
4782            pub fn with_fallback_missing(data) {
4783                if data.contains("nickname") { data.nickname } else { "unnamed" }
4784            }
4785            "#
4786            .to_vec(),
4787        )?;
4788
4789        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback", &[Type::Any])?;
4790        assert_eq!(compiled.ret_ty(), &Type::Any);
4791        let with_fallback: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4792        let data = dynamic::map!("name"=> "Alice");
4793        let result = unsafe { &*with_fallback(&data) };
4794        assert_eq!(result.as_str(), "Alice");
4795
4796        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback_missing", &[Type::Any])?;
4797        let with_fallback_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4798        let result = unsafe { &*with_fallback_missing(&data) };
4799        assert_eq!(result.as_str(), "unnamed");
4800        Ok(())
4801    }
4802
4803    #[test]
4804    fn for_in_loop_iterates_over_list_and_map_directly() -> anyhow::Result<()> {
4805        let vm = Vm::with_all()?;
4806        vm.import_code(
4807            "vm_for_in_collection",
4808            br#"
4809            pub fn sum_list(items) {
4810                let total = 0i64;
4811                for item in items {
4812                    total = total + 1;
4813                }
4814                total
4815            }
4816
4817            pub fn count_map_keys(data) {
4818                let count = 0i64;
4819                for key in data.keys() {
4820                    count = count + 1;
4821                }
4822                count
4823            }
4824
4825            pub fn for_in_list_works(items) {
4826                let exists = false;
4827                for item in items {
4828                    exists = true;
4829                }
4830                exists
4831            }
4832
4833            pub fn for_in_map_values_works(data) {
4834                let exists = false;
4835                for value in data {
4836                    exists = true;
4837                }
4838                exists
4839            }
4840            "#
4841            .to_vec(),
4842        )?;
4843
4844        let compiled = vm.get_fn("vm_for_in_collection::sum_list", &[Type::Any])?;
4845        assert_eq!(compiled.ret_ty(), &Type::I64);
4846        let sum_list: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4847        let items = Dynamic::list(vec![Dynamic::from(1i64), Dynamic::from(2i64), Dynamic::from(3i64)]);
4848        assert_eq!(sum_list(&items), 3);
4849
4850        let data = dynamic::map!("x"=> 1i64, "y"=> 2i64);
4851        let compiled = vm.get_fn("vm_for_in_collection::count_map_keys", &[Type::Any])?;
4852        assert_eq!(compiled.ret_ty(), &Type::I64);
4853        let count_map_keys: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4854        assert_eq!(count_map_keys(&data), 2);
4855
4856        let compiled = vm.get_fn("vm_for_in_collection::for_in_list_works", &[Type::Any])?;
4857        assert_eq!(compiled.ret_ty(), &Type::Bool);
4858        let for_in_list_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4859        let empty = Dynamic::list(Vec::new());
4860        assert!(!for_in_list_works(&empty));
4861        assert!(for_in_list_works(&items));
4862
4863        let compiled = vm.get_fn("vm_for_in_collection::for_in_map_values_works", &[Type::Any])?;
4864        assert_eq!(compiled.ret_ty(), &Type::Bool);
4865        let for_in_map_values_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4866        let empty_map = dynamic::map!();
4867        assert!(!for_in_map_values_works(&empty_map));
4868        assert!(for_in_map_values_works(&data));
4869
4870        Ok(())
4871    }
4872
4873    #[test]
4874    fn concurrent_100_threads_no_memory_leak() -> anyhow::Result<()> {
4875        let vm = Vm::with_all()?;
4876        vm.import_code(
4877            "vm_stress",
4878            br#"
4879            pub fn heavy_alloc(idx: i64) {
4880                let items = [];
4881                let i = 0;
4882                while i < 50 {
4883                    items.push({
4884                        id: i + idx,
4885                        name: "item-" + i,
4886                        tags: ["tag-a", "tag-b", "tag-c"],
4887                        meta: {
4888                            created: 1234567890i64,
4889                            score: (i * 3.14f64) as i64,
4890                            extra: "prefix/" + i + "/" + idx
4891                        }
4892                    });
4893                    i = i + 1;
4894                }
4895                items
4896            }
4897
4898            pub fn string_concat_stress() {
4899                let i = 0;
4900                let result = "";
4901                while i < 200 {
4902                    result = result + "data-" + i + ",";
4903                    i = i + 1;
4904                }
4905                result
4906            }
4907            "#
4908            .to_vec(),
4909        )?;
4910
4911        let (heavy_ptr, _) = vm.get_fn_ptr("vm_stress::heavy_alloc", &[Type::I64])?;
4912        let (concat_ptr, _) = vm.get_fn_ptr("vm_stress::string_concat_stress", &[])?;
4913
4914        let threads: usize = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4).max(100);
4915        let iters_per_thread = 200;
4916        let total_calls = threads * iters_per_thread * 2;
4917
4918        let before = current_rss_kb();
4919        eprintln!("threads={threads} iters_per_thread={iters_per_thread} total_calls={total_calls} rss_before={before}KB");
4920
4921        // Round 1: first concurrent execution (arena warm-up)
4922        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4923        let r1 = current_rss_kb();
4924        eprintln!("rss_after_round1={r1}KB");
4925
4926        // Round 2: should stabilize (no unbounded growth)
4927        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4928        let r2 = current_rss_kb();
4929        eprintln!("rss_after_round2={r2}KB");
4930
4931        // Round 3: final check
4932        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4933        let r3 = current_rss_kb();
4934        eprintln!("rss_after_round3={r3}KB");
4935
4936        // Round 4: confirm that any one-time allocator growth has settled.
4937        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4938        let r4 = current_rss_kb();
4939        eprintln!("rss_after_round4={r4}KB");
4940
4941        // Allocator/arena growth is allowed during warm-up, but it must settle.
4942        let d12 = r2.saturating_sub(r1);
4943        let d23 = r3.saturating_sub(r2);
4944        let d34 = r4.saturating_sub(r3);
4945        eprintln!("delta_r1→r2={d12}KB delta_r2→r3={d23}KB delta_r3→r4={d34}KB");
4946
4947        // The last interval must be small to prove the growth is not continuing.
4948        let max_growth_kb = 20 * 1024;
4949        assert!(d34 < max_growth_kb, "memory keeps growing after allocator warm-up: round1={r1} round2={r2} round3={r3} round4={r4} delta12={d12}KB delta23={d23}KB delta34={d34}KB (max stable growth={max_growth_kb}KB)");
4950
4951        Ok(())
4952    }
4953
4954    fn run_stress_round(threads: usize, iters: usize, heavy_ptr: usize, concat_ptr: usize) {
4955        std::thread::scope(|scope| {
4956            let mut handles = Vec::with_capacity(threads);
4957            for t in 0..threads {
4958                let heavy_ptr = heavy_ptr;
4959                let concat_ptr = concat_ptr;
4960                handles.push(scope.spawn(move || {
4961                    let heavy_fn: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(heavy_ptr as *const u8) };
4962                    let concat_fn: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(concat_ptr as *const u8) };
4963                    for i in 0..iters {
4964                        // heavy_alloc: drop returned value to free heap allocation
4965                        let r_ptr = heavy_fn((t * iters + i) as i64);
4966                        assert!(!r_ptr.is_null());
4967                        unsafe {
4968                            let r = &*r_ptr;
4969                            assert!(r.len() > 0, "heavy_alloc returned empty list");
4970                            drop(Box::from_raw(r_ptr as *mut Dynamic));
4971                        }
4972
4973                        // concat: same, drop returned value
4974                        let s_ptr = concat_fn();
4975                        assert!(!s_ptr.is_null());
4976                        unsafe {
4977                            let s = &*s_ptr;
4978                            assert!(s.len() > 0, "string_concat_stress returned empty");
4979                            drop(Box::from_raw(s_ptr as *mut Dynamic));
4980                        }
4981                    }
4982                }));
4983            }
4984            for h in handles {
4985                h.join().unwrap();
4986            }
4987        });
4988    }
4989
4990    fn current_rss_kb() -> u64 {
4991        // macOS: use ps
4992        let pid = std::process::id();
4993        if let Ok(output) = std::process::Command::new("ps").args(["-p", &pid.to_string(), "-o", "rss="]).output() {
4994            if let Ok(s) = String::from_utf8(output.stdout) {
4995                if let Some(kb) = s.trim().parse::<u64>().ok() {
4996                    return kb;
4997                }
4998            }
4999        }
5000        // Linux fallback: /proc/self/statm
5001        if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
5002            let parts: Vec<&str> = statm.split_whitespace().collect();
5003            if let Some(rss_pages) = parts.get(1).and_then(|s| s.parse::<u64>().ok()) {
5004                return rss_pages * 4; // pages (4KB) → KB
5005            }
5006        }
5007        0
5008    }
5009}