Skip to main content

vm/
lib.rs

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