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