Skip to main content

vm/
lib.rs

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