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_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
103
104        let void_sig = self.get_sig(&[], Type::Void)?;
105        self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
106        self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
107
108        let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
109        self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
110
111        let bytes_sig = self.get_sig(&[Type::Any, Type::I64], Type::Any)?;
112        self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
113
114        let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
115        self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
116
117        let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
118        self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
119        Ok(())
120    }
121
122    pub fn add_module(&mut self, name: &str) {
123        self.compiler.symbols.add_module(name.into());
124    }
125
126    pub fn pop_module(&mut self) {
127        self.compiler.symbols.pop_module();
128    }
129
130    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
131        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
132    }
133
134    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
135        match self.get_id(name) {
136            Ok(id) => Ok(id),
137            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
138        }
139    }
140
141    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
142        self.add_module(module);
143        let full_name = format!("{}::{}", module, name);
144        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
145        self.pop_module();
146        result
147    }
148
149    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> {
150        self.add_module(module);
151        let full_name = format!("{}::{}", module, name);
152        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
153        self.pop_module();
154        result
155    }
156
157    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
158        self.add_empty_type(def)?;
159        let full_name = format!("{}::{}", def, method);
160        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
161        add_method_field(self, def, method, id)?;
162        Ok(id)
163    }
164
165    pub fn add_std(&mut self) -> Result<()> {
166        if self.compiler.symbols.get_id("std::print").is_ok() {
167            return Ok(());
168        }
169        self.add_module("std");
170        for (name, arg_tys, ret_ty, fn_ptr) in STD {
171            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
172        }
173        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
174        Ok(())
175    }
176
177    pub fn add_any(&mut self) -> Result<()> {
178        if self.compiler.symbols.get_id("Any").is_ok() && self.compiler.symbols.get_id("Any::is_map").is_ok() {
179            return Ok(());
180        }
181        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
182            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
183            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
184        }
185        Ok(())
186    }
187
188    pub fn add_vec(&mut self) -> Result<()> {
189        self.add_empty_type("Vec")?;
190        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
191        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
192            if let Some(ctx) = ctx {
193                let width = ctx.builder.ins().iconst(types::I64, 4);
194                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
195                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
196                let dest = ctx.builder.ins().imul(args[2], width);
197                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
198                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
199                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
200                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
201                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
202            }
203            Err(anyhow!("无返回值"))
204        })?;
205
206        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
207            if let Some(ctx) = ctx {
208                let width = ctx.builder.ins().iconst(types::I64, 4);
209                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
210                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
211                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
212            } else {
213                Ok((None, Type::I32))
214            }
215        })?;
216        Ok(())
217    }
218
219    pub fn add_llm(&mut self) -> Result<()> {
220        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
221    }
222
223    pub fn add_root(&mut self) -> Result<()> {
224        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
225        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)?;
226        Ok(())
227    }
228
229    pub fn add_http(&mut self) -> Result<()> {
230        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)?;
231        http_module::add_root_handlers()
232    }
233
234    pub fn add_oss(&mut self) -> Result<()> {
235        add_native_module_fns(self, "oss", &oss_module::OSS_NATIVE)
236    }
237
238    pub fn add_db(&mut self) -> Result<()> {
239        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
240    }
241
242    pub fn add_gpu(&mut self) -> Result<()> {
243        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
244    }
245
246    pub fn add_all(&mut self) -> Result<()> {
247        self.add_std()?;
248        self.add_any()?;
249        self.add_vec()?;
250        self.add_llm()?;
251        self.add_root()?;
252        self.add_http()?;
253        self.add_oss()?;
254        self.add_db()?;
255        self.add_gpu()?;
256        Ok(())
257    }
258}
259
260#[derive(Clone)]
261pub struct Vm {
262    jit: Arc<Mutex<JITRunTime>>,
263}
264
265#[derive(Clone)]
266pub struct CompiledFn {
267    ptr: usize,
268    ret: Type,
269    owner: Vm,
270}
271
272impl CompiledFn {
273    pub fn ptr(&self) -> *const u8 {
274        self.ptr as *const u8
275    }
276
277    pub fn ret_ty(&self) -> &Type {
278        &self.ret
279    }
280
281    pub fn owner(&self) -> &Vm {
282        &self.owner
283    }
284}
285
286impl Vm {
287    pub fn new() -> Self {
288        dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
289        let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
290        {
291            let mut guard = jit.lock().unwrap();
292            guard.set_owner(Arc::downgrade(&jit));
293            guard.add_memory_runtime().expect("register VM memory runtime");
294            guard.add_std().expect("register VM std runtime");
295            guard.add_any().expect("register VM Any runtime");
296        }
297        Self { jit }
298    }
299
300    pub fn with_all() -> Result<Self> {
301        let vm = Self::new();
302        vm.add_all()?;
303        Ok(vm)
304    }
305
306    pub fn add_module(&self, name: &str) {
307        self.jit.lock().unwrap().add_module(name)
308    }
309
310    pub fn pop_module(&self) {
311        self.jit.lock().unwrap().pop_module()
312    }
313
314    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
315        self.jit.lock().unwrap().add_type(name, ty, is_pub)
316    }
317
318    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
319        self.jit.lock().unwrap().add_empty_type(name)
320    }
321
322    pub fn add_std(&self) -> Result<()> {
323        self.jit.lock().unwrap().add_std()
324    }
325
326    pub fn add_any(&self) -> Result<()> {
327        self.jit.lock().unwrap().add_any()
328    }
329
330    pub fn add_vec(&self) -> Result<()> {
331        self.jit.lock().unwrap().add_vec()
332    }
333
334    pub fn add_llm(&self) -> Result<()> {
335        self.jit.lock().unwrap().add_llm()
336    }
337
338    pub fn add_root(&self) -> Result<()> {
339        self.jit.lock().unwrap().add_root()
340    }
341
342    pub fn add_http(&self) -> Result<()> {
343        self.jit.lock().unwrap().add_http()
344    }
345
346    pub fn add_oss(&self) -> Result<()> {
347        self.jit.lock().unwrap().add_oss()
348    }
349
350    pub fn add_db(&self) -> Result<()> {
351        self.jit.lock().unwrap().add_db()
352    }
353
354    pub fn add_gpu(&self) -> Result<()> {
355        self.jit.lock().unwrap().add_gpu()
356    }
357
358    pub fn add_all(&self) -> Result<()> {
359        self.jit.lock().unwrap().add_all()
360    }
361
362    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
363        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
364    }
365
366    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
367        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
368    }
369
370    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
371        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
372    }
373
374    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> {
375        self.jit.lock().unwrap().add_inline(name, args, ret, f)
376    }
377
378    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
379        self.jit.lock().unwrap().import_code(name, code)
380    }
381
382    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
383        self.jit.lock().unwrap().compiler.import_file(name, path)?;
384        Ok(())
385    }
386
387    pub fn import(&self, name: &str, path: &str) -> Result<()> {
388        if root::contains(path) {
389            let code = root::get(path).unwrap();
390            if code.is_str() {
391                self.import_code(name, code.as_str().as_bytes().to_vec())
392            } else {
393                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
394            }
395        } else {
396            self.import_file(name, path)
397        }
398    }
399
400    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
401        self.jit.lock().unwrap().get_type(name, arg_tys)
402    }
403
404    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
405        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
406    }
407
408    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
409        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
410        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
411    }
412
413    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
414        self.jit.lock().unwrap().load(code, arg_name)
415    }
416
417    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
418        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
419    }
420
421    pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
422        let jit = self.jit.lock().unwrap();
423        GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
424    }
425
426    pub fn disassemble(&self, name: &str) -> Result<String> {
427        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
428    }
429
430    #[cfg(feature = "ir-disassembly")]
431    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
432        self.jit.lock().unwrap().disassemble_ir(name)
433    }
434}
435
436impl Default for Vm {
437    fn default() -> Self {
438        Self::new()
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::Vm;
445    use dynamic::{Dynamic, ToJson, Type};
446
447    extern "C" fn math_double(value: i64) -> i64 {
448        value * 2
449    }
450
451    #[test]
452    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
453        let vm = Vm::new();
454        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
455        vm.import_code(
456            "vm_dynamic_native",
457            br#"
458            pub fn run(value: i64) {
459                math::double(value)
460            }
461            "#
462            .to_vec(),
463        )?;
464
465        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
466        assert_eq!(compiled.ret_ty(), &Type::I64);
467        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
468        assert_eq!(run(21), 42);
469        Ok(())
470    }
471
472    #[test]
473    fn vm_new_registers_std_and_any() -> anyhow::Result<()> {
474        let vm = Vm::new();
475        vm.add_std()?;
476        vm.add_any()?;
477        assert_eq!(vm.infer("std::print", &[Type::Any])?, Type::Void);
478
479        vm.import_code(
480            "vm_new_default_any",
481            br#"
482            pub fn has_items(content) {
483                if content.is_map() {
484                    if content.contains("items") {
485                        return content.items.len() > 0;
486                    }
487                }
488                false
489            }
490            "#
491            .to_vec(),
492        )?;
493
494        assert_eq!(vm.infer("vm_new_default_any::has_items", &[Type::Any])?, Type::Bool);
495        let compiled = vm.get_fn("vm_new_default_any::has_items", &[Type::Any])?;
496        assert_eq!(compiled.ret_ty(), &Type::Bool);
497        Ok(())
498    }
499
500    #[test]
501    fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
502        let vm = Vm::with_all()?;
503        vm.import_code(
504            "vm_nested_struct_return_field",
505            br#"
506            pub struct Inner {
507                value: i64,
508            }
509
510            pub struct RoleMini {
511                inner: Inner,
512                hp: i64,
513            }
514
515            pub struct TeamMini {
516                role: RoleMini,
517            }
518
519            pub struct BigSummary {
520                winner: i64,
521                loser: i64,
522            }
523
524            pub fn make_big_with_team(team: TeamMini) {
525                let score = team.role.inner.value;
526                BigSummary{winner: score, loser: 0}
527            }
528
529            pub fn read_team_winner_direct() {
530                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
531                make_big_with_team(team).winner
532            }
533
534            pub fn read_team_winner_bound() {
535                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
536                let summary = make_big_with_team(team);
537                summary.winner
538            }
539            "#
540            .to_vec(),
541        )?;
542
543        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
544        assert_eq!(compiled.ret_ty(), &Type::I64);
545        let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
546        assert_eq!(direct(), 9);
547
548        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
549        assert_eq!(compiled.ret_ty(), &Type::I64);
550        let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
551        assert_eq!(bound(), 9);
552        Ok(())
553    }
554
555    #[test]
556    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
557        let vm = Vm::with_all()?;
558        vm.import_code(
559            "vm_any_push_reused_value",
560            br#"
561            pub fn run() {
562                let role_id = "acct_role_2";
563                let updated = [];
564                updated.push(role_id);
565                {
566                    ok: true,
567                    user_id: role_id,
568                    first: updated.get_idx(0)
569                }
570            }
571            "#
572            .to_vec(),
573        )?;
574
575        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
576        assert_eq!(compiled.ret_ty(), &Type::Any);
577        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
578        let result = unsafe { &*run() };
579        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
580        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
581        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
582        Ok(())
583    }
584
585    #[test]
586    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
587        let vm = Vm::with_all()?;
588        vm.import_code(
589            "vm_string_compare_any",
590            br#"
591            pub fn any_ne_empty(chat_path) {
592                chat_path != ""
593            }
594            "#
595            .to_vec(),
596        )?;
597
598        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
599        assert_eq!(compiled.ret_ty(), &Type::Bool);
600
601        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
602        let empty = Dynamic::from("");
603        let non_empty = Dynamic::from("chat");
604
605        assert!(!any_ne_empty(&empty));
606        assert!(any_ne_empty(&non_empty));
607        Ok(())
608    }
609
610    #[test]
611    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
612        let vm = Vm::with_all()?;
613        vm.import_code(
614            "vm_bool_compare",
615            br#"
616            pub fn eq_true(value: bool) {
617                value == true
618            }
619
620            pub fn ne_false(value: bool) {
621                value != false
622            }
623
624            pub fn literal_left(value: bool) {
625                true == value
626            }
627
628            pub fn eq_pair(left: bool, right: bool) {
629                left == right
630            }
631
632            pub fn logic_pair(left: bool, right: bool) {
633                (left && right) || (left == true && right != false)
634            }
635            "#
636            .to_vec(),
637        )?;
638
639        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
640        assert_eq!(compiled.ret_ty(), &Type::Bool);
641        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
642        assert!(eq_true(true));
643        assert!(!eq_true(false));
644
645        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
646        assert_eq!(compiled.ret_ty(), &Type::Bool);
647        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
648        assert!(ne_false(true));
649        assert!(!ne_false(false));
650
651        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
652        assert_eq!(compiled.ret_ty(), &Type::Bool);
653        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
654        assert!(literal_left(true));
655        assert!(!literal_left(false));
656
657        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
658        assert_eq!(compiled.ret_ty(), &Type::Bool);
659        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
660        assert!(eq_pair(true, true));
661        assert!(eq_pair(false, false));
662        assert!(!eq_pair(true, false));
663        assert!(!eq_pair(false, true));
664
665        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
666        assert_eq!(compiled.ret_ty(), &Type::Bool);
667        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
668        assert!(logic_pair(true, true));
669        assert!(!logic_pair(true, false));
670        assert!(!logic_pair(false, true));
671        assert!(!logic_pair(false, false));
672        Ok(())
673    }
674
675    #[test]
676    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
677        let vm = Vm::with_all()?;
678        vm.import_code(
679            "vm_parenthesized_method_call",
680            br#"
681            pub fn run(value) {
682                (value + 2).to_i64()
683            }
684            "#
685            .to_vec(),
686        )?;
687
688        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
689        assert_eq!(compiled.ret_ty(), &Type::I64);
690        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
691        let value = Dynamic::from(40i64);
692
693        assert_eq!(run(&value), 42);
694        Ok(())
695    }
696
697    #[test]
698    fn casts_any_float_to_i32_without_zeroing() -> anyhow::Result<()> {
699        let vm = Vm::with_all()?;
700        vm.import_code(
701            "vm_any_float_to_i32",
702            br#"
703            pub fn direct(value) {
704                value as i32
705            }
706
707            pub fn map_field(value) {
708                let field = value.v;
709                field as i32
710            }
711
712            pub fn damage(attacker, def_rate) {
713                let x = attacker.atk * (1.0 - def_rate);
714                x as i32
715            }
716            "#
717            .to_vec(),
718        )?;
719
720        let compiled = vm.get_fn("vm_any_float_to_i32::direct", &[Type::Any])?;
721        assert_eq!(compiled.ret_ty(), &Type::I32);
722        let direct: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
723        let value = Dynamic::from(9.5f64);
724        assert_eq!(direct(&value), 9);
725
726        let compiled = vm.get_fn("vm_any_float_to_i32::map_field", &[Type::Any])?;
727        assert_eq!(compiled.ret_ty(), &Type::I32);
728        let map_field: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
729        let value = dynamic::map!("v"=> 9.5f64);
730        assert_eq!(map_field(&value), 9);
731
732        let compiled = vm.get_fn("vm_any_float_to_i32::damage", &[Type::Any, Type::Any])?;
733        assert_eq!(compiled.ret_ty(), &Type::I32);
734        let damage: extern "C" fn(*const Dynamic, *const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
735        let attacker = dynamic::map!("atk"=> 64i64);
736        let def_rate = Dynamic::from(0.17f64);
737        assert_eq!(damage(&attacker, &def_rate), 53);
738        Ok(())
739    }
740
741    #[test]
742    fn binary_imm_promotes_integer_literals_for_float_left_values() -> anyhow::Result<()> {
743        let vm = Vm::with_all()?;
744        vm.import_code(
745            "vm_float_binary_imm",
746            br#"
747            pub fn add_f32(value: f32) {
748                value + 1i32
749            }
750
751            pub fn sub_f32(value: f32) {
752                value - 1i32
753            }
754
755            pub fn mul_f32(value: f32) {
756                value * 2i32
757            }
758
759            pub fn div_f32(value: f32) {
760                value / 2i32
761            }
762
763            pub fn gt_f32(value: f32) {
764                value > 2i32
765            }
766            "#
767            .to_vec(),
768        )?;
769
770        let compiled = vm.get_fn("vm_float_binary_imm::add_f32", &[Type::F32])?;
771        assert_eq!(compiled.ret_ty(), &Type::F32);
772        let add_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
773        assert_eq!(add_f32(2.5), 3.5);
774
775        let compiled = vm.get_fn("vm_float_binary_imm::sub_f32", &[Type::F32])?;
776        assert_eq!(compiled.ret_ty(), &Type::F32);
777        let sub_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
778        assert_eq!(sub_f32(2.5), 1.5);
779
780        let compiled = vm.get_fn("vm_float_binary_imm::mul_f32", &[Type::F32])?;
781        assert_eq!(compiled.ret_ty(), &Type::F32);
782        let mul_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
783        assert_eq!(mul_f32(2.5), 5.0);
784
785        let compiled = vm.get_fn("vm_float_binary_imm::div_f32", &[Type::F32])?;
786        assert_eq!(compiled.ret_ty(), &Type::F32);
787        let div_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
788        assert_eq!(div_f32(5.0), 2.5);
789
790        let compiled = vm.get_fn("vm_float_binary_imm::gt_f32", &[Type::F32])?;
791        assert_eq!(compiled.ret_ty(), &Type::Bool);
792        let gt_f32: extern "C" fn(f32) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
793        assert!(gt_f32(2.5));
794        assert!(!gt_f32(1.5));
795        Ok(())
796    }
797
798    #[test]
799    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
800        let vm = Vm::with_all()?;
801        vm.import_code(
802            "vm_any_keys",
803            br#"
804            pub fn map_keys(value) {
805                let keys = value.keys();
806                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
807            }
808
809            pub fn non_map_keys(value) {
810                value.keys().len() == 0
811            }
812            "#
813            .to_vec(),
814        )?;
815
816        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
817        assert_eq!(compiled.ret_ty(), &Type::Bool);
818        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
819        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
820        assert!(map_keys(&value));
821
822        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
823        assert_eq!(compiled.ret_ty(), &Type::Bool);
824        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
825        let value = Dynamic::from("alpha");
826        assert!(non_map_keys(&value));
827        Ok(())
828    }
829
830    #[test]
831    fn string_methods_work_on_static_string_and_any_string_values() -> anyhow::Result<()> {
832        let vm = Vm::with_all()?;
833        vm.import_code(
834            "vm_string_methods",
835            br#"
836            pub fn static_string_methods(text: string) {
837                let parts = text.split(",");
838                text.starts_with("alpha")
839                    && text.is_string()
840                    && !text.is_null()
841                    && parts.len() == 2
842                    && parts.get_idx(0) == "alpha"
843                    && parts.get_idx(1) == "beta"
844            }
845
846            pub fn any_string_methods(value) {
847                let parts = value.split(",");
848                value.starts_with("alpha")
849                    && value.is_string()
850                    && !value.is_null()
851                    && parts.len() == 2
852                    && parts.get_idx(0) == "alpha"
853                    && parts.get_idx(1) == "beta"
854            }
855
856            pub fn any_null_methods(value) {
857                value.is_null() && !value.is_string()
858            }
859            "#
860            .to_vec(),
861        )?;
862
863        let compiled = vm.get_fn("vm_string_methods::static_string_methods", &[Type::Str])?;
864        assert_eq!(compiled.ret_ty(), &Type::Bool);
865        let static_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
866        let text = Dynamic::from("alpha,beta");
867        assert!(static_string_methods(&text));
868
869        let compiled = vm.get_fn("vm_string_methods::any_string_methods", &[Type::Any])?;
870        assert_eq!(compiled.ret_ty(), &Type::Bool);
871        let any_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
872        assert!(any_string_methods(&text));
873
874        let compiled = vm.get_fn("vm_string_methods::any_null_methods", &[Type::Any])?;
875        assert_eq!(compiled.ret_ty(), &Type::Bool);
876        let any_null_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
877        let value = Dynamic::Null;
878        assert!(any_null_methods(&value));
879        Ok(())
880    }
881
882    #[test]
883    fn primitive_type_check_methods_call_any_runtime() -> anyhow::Result<()> {
884        let vm = Vm::with_all()?;
885        vm.import_code(
886            "vm_primitive_type_check_methods",
887            br#"
888            pub fn int_checks() {
889                !42i64.is_list()
890                    && !42i64.is_map()
891                    && !42i64.is_string()
892                    && !42i64.is_null()
893            }
894
895            pub fn bool_checks() {
896                !true.is_list() && !true.is_map() && !true.is_null()
897            }
898            "#
899            .to_vec(),
900        )?;
901
902        let compiled = vm.get_fn("vm_primitive_type_check_methods::int_checks", &[])?;
903        assert_eq!(compiled.ret_ty(), &Type::Bool);
904        let int_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
905        assert!(int_checks());
906
907        let compiled = vm.get_fn("vm_primitive_type_check_methods::bool_checks", &[])?;
908        assert_eq!(compiled.ret_ty(), &Type::Bool);
909        let bool_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
910        assert!(bool_checks());
911        Ok(())
912    }
913
914    #[test]
915    fn for_loop_iterates_any_list_and_map_values() -> anyhow::Result<()> {
916        let vm = Vm::with_all()?;
917        vm.import_code(
918            "vm_for_any_collections",
919            br#"
920            pub fn list_sum(items) {
921                let total = 0i64;
922                for item in items {
923                    total += item;
924                }
925                total
926            }
927
928            pub fn map_sum(data) {
929                let total = 0i64;
930                for (key, value) in data {
931                    total += value;
932                }
933                total
934            }
935            "#
936            .to_vec(),
937        )?;
938
939        let compiled = vm.get_fn("vm_for_any_collections::list_sum", &[Type::Any])?;
940        assert_eq!(compiled.ret_ty(), &Type::Any);
941        let list_sum: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
942        let items = Dynamic::list(vec![1i64.into(), 2i64.into(), 3i64.into()]);
943        let result = unsafe { &*list_sum(&items) };
944        assert_eq!(result.as_int(), Some(6));
945
946        let compiled = vm.get_fn("vm_for_any_collections::map_sum", &[Type::Any])?;
947        assert_eq!(compiled.ret_ty(), &Type::Any);
948        let map_sum: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
949        let data = dynamic::map!("a"=> 4i64, "b"=> 5i64);
950        let result = unsafe { &*map_sum(&data) };
951        assert_eq!(result.as_int(), Some(9));
952        Ok(())
953    }
954
955    #[test]
956    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
957        let vm = Vm::with_all()?;
958        vm.import_code(
959            "vm_string_compare_imm",
960            br#"
961            pub fn int_eq_str(value: i64) {
962                value == "42"
963            }
964
965            pub fn int_to_str(value: i64) {
966                value + ""
967            }
968            "#
969            .to_vec(),
970        )?;
971
972        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
973        assert_eq!(compiled.ret_ty(), &Type::Bool);
974
975        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
976
977        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
978        assert_eq!(compiled.ret_ty(), &Type::Any);
979        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
980        let text = int_to_str(42);
981        assert_eq!(unsafe { &*text }.as_str(), "42");
982
983        assert!(int_eq_str(42));
984        assert!(!int_eq_str(7));
985        Ok(())
986    }
987
988    #[test]
989    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
990        let vm = Vm::with_all()?;
991        vm.import_code(
992            "vm_string_concat_integer",
993            br#"
994            pub fn idx_key(idx: i64) {
995                "" + idx
996            }
997
998            pub fn level_text(level: i64) {
999                "" + level + " level"
1000            }
1001
1002            pub fn gold_text(currency) {
1003                "" + currency.gold
1004            }
1005            "#
1006            .to_vec(),
1007        )?;
1008
1009        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
1010        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1011        let result = unsafe { &*idx_key(7) };
1012        assert_eq!(result.as_str(), "7");
1013
1014        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
1015        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1016        let result = unsafe { &*level_text(12) };
1017        assert_eq!(result.as_str(), "12 level");
1018
1019        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
1020        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1021        let currency = dynamic::map!("gold"=> 345i64);
1022        let result = unsafe { &*gold_text(&currency) };
1023        assert_eq!(result.as_str(), "345");
1024        Ok(())
1025    }
1026
1027    #[test]
1028    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
1029        let vm = Vm::with_all()?;
1030        vm.import_code(
1031            "vm_string_concat_to_i64",
1032            br#"
1033            pub fn run(idx: i64) {
1034                ("" + idx) as i64
1035            }
1036            "#
1037            .to_vec(),
1038        )?;
1039
1040        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
1041        assert_eq!(compiled.ret_ty(), &Type::I64);
1042        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1043        assert_eq!(run(7), 0);
1044        Ok(())
1045    }
1046
1047    #[test]
1048    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
1049        let vm = Vm::with_all()?;
1050        vm.import_code(
1051            "vm_return_integer_widths",
1052            br#"
1053            pub fn selected(flag, slot) {
1054                if flag {
1055                    return slot;
1056                }
1057                0
1058            }
1059            "#
1060            .to_vec(),
1061        )?;
1062
1063        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
1064        assert_eq!(compiled.ret_ty(), &Type::I64);
1065        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1066
1067        assert_eq!(selected(true, 7), 7);
1068        assert_eq!(selected(false, 7), 0);
1069        Ok(())
1070    }
1071
1072    #[test]
1073    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
1074        let vm = Vm::with_all()?;
1075        vm.import_code(
1076            "vm_root_contains_condition",
1077            br#"
1078            pub fn exists(user_id) {
1079                if root::contains("redis/user/" + user_id) {
1080                    return 1;
1081                }
1082                0
1083            }
1084            "#
1085            .to_vec(),
1086        )?;
1087
1088        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
1089        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
1090        assert_eq!(compiled.ret_ty(), &Type::I32);
1091        Ok(())
1092    }
1093
1094    #[test]
1095    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
1096        let vm = Vm::with_all()?;
1097        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
1098        vm.import_code(
1099            "vm_root_add_map_print",
1100            br#"
1101            pub fn run() {
1102                print(root::add_map("local/world_handlers/til_map_novicevillage"));
1103            }
1104            "#
1105            .to_vec(),
1106        )?;
1107
1108        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
1109        assert!(compiled.ret_ty().is_void());
1110        Ok(())
1111    }
1112
1113    #[test]
1114    fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
1115        let vm = Vm::with_all()?;
1116        vm.import_code(
1117            "vm_std_log",
1118            br#"
1119            pub fn run(value) {
1120                log({ ok: true, value: value });
1121            }
1122            "#
1123            .to_vec(),
1124        )?;
1125
1126        let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
1127        assert!(compiled.ret_ty().is_void());
1128        let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
1129        let value = Dynamic::from(7i64);
1130        run(&value);
1131        Ok(())
1132    }
1133
1134    #[test]
1135    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
1136        let vm = Vm::with_all()?;
1137        vm.import_code(
1138            "vm_unary_not_any_loop_var",
1139            br#"
1140            pub fn count_missing(flags) {
1141                let missing = 0;
1142                for exists in flags {
1143                    if !exists {
1144                        missing = missing + 1;
1145                    }
1146                }
1147                missing
1148            }
1149            "#
1150            .to_vec(),
1151        )?;
1152
1153        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
1154        assert_eq!(compiled.ret_ty(), &Type::I32);
1155        Ok(())
1156    }
1157
1158    #[test]
1159    fn closure_literal_can_be_called_immediately() -> anyhow::Result<()> {
1160        let vm = Vm::with_all()?;
1161        vm.import_code(
1162            "vm_closure_immediate_call",
1163            br#"
1164            pub fn no_args() {
1165                let r = || { 1i32 }();
1166                r
1167            }
1168
1169            pub fn with_arg() {
1170                |value: i32| { value + 1i32 }(2i32)
1171            }
1172            "#
1173            .to_vec(),
1174        )?;
1175
1176        let compiled = vm.get_fn("vm_closure_immediate_call::no_args", &[])?;
1177        assert_eq!(compiled.ret_ty(), &Type::I32);
1178        let no_args: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1179        assert_eq!(no_args(), 1);
1180
1181        let compiled = vm.get_fn("vm_closure_immediate_call::with_arg", &[])?;
1182        assert_eq!(compiled.ret_ty(), &Type::I32);
1183        let with_arg: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1184        assert_eq!(with_arg(), 3);
1185        Ok(())
1186    }
1187
1188    #[test]
1189    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1190        let vm = Vm::with_all()?;
1191        vm.import_code(
1192            "vm_semicolon_tail_void",
1193            br#"
1194            pub fn send_role_select(idx, account_id, selected_slot) {
1195                root::send("local/ui/send_dialog", {
1196                    idx: idx,
1197                    account_id: account_id,
1198                    selected_slot: selected_slot
1199                });
1200            }
1201            "#
1202            .to_vec(),
1203        )?;
1204
1205        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1206        assert_eq!(compiled.ret_ty(), &Type::Void);
1207        Ok(())
1208    }
1209
1210    #[test]
1211    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1212        let vm = Vm::with_all()?;
1213        vm.import_code(
1214            "vm_bare_return_conflict",
1215            br#"
1216            pub fn run(flag) {
1217                if flag {
1218                    return;
1219                }
1220                1
1221            }
1222            "#
1223            .to_vec(),
1224        )?;
1225
1226        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1227            Ok(_) => panic!("expected mismatched return types to fail"),
1228            Err(err) => err,
1229        };
1230        assert!(format!("{err:#}").contains("返回类型不一致"));
1231        Ok(())
1232    }
1233
1234    #[test]
1235    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1236        let vm = Vm::with_all()?;
1237        vm.import_code(
1238            "vm_root_get_dynamic_concat",
1239            br#"
1240            pub fn get_action(req) {
1241                root::get("local/game/panel_actions/" + req.idx)
1242            }
1243            "#
1244            .to_vec(),
1245        )?;
1246
1247        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1248        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1249        assert_eq!(compiled.ret_ty(), &Type::Any);
1250        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1251        let req = dynamic::map!("idx"=> 7i64);
1252        let result = unsafe { &*get_action(&req) };
1253
1254        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1255        Ok(())
1256    }
1257
1258    #[test]
1259    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1260        let vm = Vm::with_all()?;
1261        vm.import_code(
1262            "vm_registered_panel_action",
1263            br#"
1264            pub fn panel_action(req) {
1265                root::get("local/game/panel_actions/" + req.idx)
1266            }
1267
1268            pub fn register() {
1269                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1270            }
1271            "#
1272            .to_vec(),
1273        )?;
1274
1275        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1276        assert_eq!(compiled.ret_ty(), &Type::Bool);
1277        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1278        assert!(register());
1279        Ok(())
1280    }
1281
1282    #[test]
1283    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
1284        let vm = Vm::with_all()?;
1285        vm.import_code(
1286            "vm_registered_string_concat",
1287            br#"
1288            pub fn send_panel(idx: i64) {
1289                let idx_key = "" + idx;
1290                idx_key
1291            }
1292            "#
1293            .to_vec(),
1294        )?;
1295
1296        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
1297        Ok(())
1298    }
1299
1300    #[test]
1301    fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
1302        fn echo_handler(msg: Dynamic) -> Dynamic {
1303            dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
1304        }
1305
1306        let vm = Vm::with_all()?;
1307        vm.import_code(
1308            "vm_root_send_idx_return",
1309            br#"
1310            pub fn call(req) {
1311                root::send_idx("local/send_idx_return_handlers", 0, req)
1312            }
1313            "#
1314            .to_vec(),
1315        )?;
1316
1317        root::add_list("local/send_idx_return_handlers")?;
1318        let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
1319        mount.push(name, root::Object::Native(echo_handler))?;
1320
1321        assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
1322        let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
1323        assert_eq!(compiled.ret_ty(), &Type::Any);
1324        let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1325        let req = dynamic::map!("id"=> 42i64);
1326        let result = unsafe { &*call(&req) };
1327
1328        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
1329        assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
1330        Ok(())
1331    }
1332
1333    #[test]
1334    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
1335        let vm = Vm::with_all()?;
1336        vm.import_code(
1337            "vm_public_hotspots",
1338            br#"
1339            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1340                {
1341                    path: action_map_path,
1342                    panel_id: panel_id,
1343                    action_id: action_id,
1344                    id: hotspot.id
1345                }
1346            }
1347
1348            pub fn public_hotspots(idx, panel_id, hotspots) {
1349                let idx_key = "" + idx;
1350                let action_map_path = "local/game/panel_actions/" + idx_key;
1351
1352                let existing_action_map = root::get(action_map_path);
1353                if !existing_action_map.is_map() {
1354                    root::add_map(action_map_path);
1355                }
1356
1357                if hotspots.is_map() {
1358                    let public_items = {};
1359                    for action_id in hotspots.keys() {
1360                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1361                    }
1362                    return public_items;
1363                }
1364
1365                let public_items = [];
1366                let i = 0;
1367                while i < hotspots.len() {
1368                    let hotspot = hotspots.get_idx(i);
1369                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1370                    public_items.push(item);
1371                    i = i + 1;
1372                }
1373
1374                public_items
1375            }
1376            "#
1377            .to_vec(),
1378        )?;
1379
1380        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
1381        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
1382        Ok(())
1383    }
1384
1385    #[test]
1386    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
1387        let vm = Vm::with_all()?;
1388        vm.import_code(
1389            "vm_send_panel_public_hotspots",
1390            br#"
1391            pub fn ok(value) {
1392                value
1393            }
1394
1395            pub fn panel_from_node(req) {
1396                {
1397                    panel_id: req.panel_id,
1398                    hotspots: req.hotspots
1399                }
1400            }
1401
1402            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1403                {
1404                    path: action_map_path,
1405                    panel_id: panel_id,
1406                    action_id: action_id,
1407                    id: hotspot.id
1408                }
1409            }
1410
1411            pub fn public_hotspots(idx, panel_id, hotspots) {
1412                let idx_key = "" + idx;
1413                let action_map_path = "local/game/panel_actions/" + idx_key;
1414
1415                let existing_action_map = root::get(action_map_path);
1416                if !existing_action_map.is_map() {
1417                    root::add_map(action_map_path);
1418                }
1419
1420                if hotspots.is_map() {
1421                    let public_items = {};
1422                    for action_id in hotspots.keys() {
1423                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1424                    }
1425                    return public_items;
1426                }
1427
1428                let public_items = [];
1429                let i = 0;
1430                while i < hotspots.len() {
1431                    let hotspot = hotspots.get_idx(i);
1432                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1433                    public_items.push(item);
1434                    i = i + 1;
1435                }
1436
1437                public_items
1438            }
1439
1440            pub fn send_panel(req) {
1441                let panel = req.panel;
1442                if !panel.is_map() {
1443                    panel = panel_from_node(req);
1444                }
1445                if !panel.is_map() {
1446                    return ok({
1447                        id: 4,
1448                        type: "panel_rejected",
1449                        reason: "invalid panel"
1450                    });
1451                }
1452                panel.id = 4;
1453                panel.idx = req.idx;
1454                if !panel.contains("type") {
1455                    panel.type = "panel";
1456                }
1457                if panel.contains("hotspots") {
1458                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1459                }
1460                root::send_idx("local/ws", req.idx, panel);
1461                ok({
1462                    id: 4,
1463                    type: "panel",
1464                    panel_id: panel.panel_id
1465                })
1466            }
1467            "#
1468            .to_vec(),
1469        )?;
1470
1471        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1472        assert_eq!(compiled.ret_ty(), &Type::Any);
1473        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1474        let req = dynamic::map!(
1475            "idx"=> 7i64,
1476            "panel"=> dynamic::map!(
1477                "panel_id"=> "main",
1478                "hotspots"=> dynamic::map!(
1479                    "open"=> dynamic::map!("id"=> "open")
1480                )
1481            )
1482        );
1483        let result = unsafe { &*send_panel(&req) };
1484
1485        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1486        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1487        Ok(())
1488    }
1489
1490    #[test]
1491    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1492        let vm = Vm::with_all()?;
1493        vm.import_code(
1494            "vm_string_concat_map_key",
1495            br##"
1496            pub fn write_action(action_map, panel_id, action_id, action) {
1497                action_map[panel_id + "#" + action_id] = action;
1498                action_map[panel_id + "#" + action_id]
1499            }
1500            "##
1501            .to_vec(),
1502        )?;
1503
1504        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1505        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1506        let action_map = dynamic::map!();
1507        let panel_id: Dynamic = "panel".into();
1508        let action_id: Dynamic = "open".into();
1509        let action = dynamic::map!("id"=> "open");
1510
1511        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1512
1513        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1514        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()));
1515        Ok(())
1516    }
1517
1518    #[test]
1519    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1520        let vm = Vm::with_all()?;
1521        vm.import_code(
1522            "vm_get_key_string_concat_key",
1523            br##"
1524            pub fn read_action(action_map, panel_id, action_id) {
1525                let action_key = panel_id + "#" + action_id;
1526                action_map.get_key(action_key)
1527            }
1528            "##
1529            .to_vec(),
1530        )?;
1531
1532        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1533        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1534        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1535        let panel_id: Dynamic = "panel".into();
1536        let action_id: Dynamic = "open".into();
1537
1538        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1539
1540        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1541        Ok(())
1542    }
1543
1544    #[test]
1545    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1546        let vm = Vm::with_all()?;
1547        vm.import_code(
1548            "vm_get_key_helper_string_key",
1549            br##"
1550            pub fn make_action_key(panel_id, action_id) {
1551                panel_id + "#" + action_id
1552            }
1553
1554            pub fn read_action(action_map, panel_id, action_id) {
1555                let action_key = make_action_key(panel_id, action_id);
1556                let action = action_map.get_key(action_key);
1557                action
1558            }
1559            "##
1560            .to_vec(),
1561        )?;
1562
1563        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1564        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1565        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1566        let panel_id: Dynamic = "panel".into();
1567        let action_id: Dynamic = "open".into();
1568
1569        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1570
1571        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1572        Ok(())
1573    }
1574
1575    #[test]
1576    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1577        let vm = Vm::with_all()?;
1578        vm.import_code(
1579            "vm_del_key_string_key",
1580            br##"
1581            pub fn remove_action(action_map, panel_id, action_id) {
1582                let action_key = panel_id + "#" + action_id;
1583                let removed = action_map.del_key(action_key);
1584                [removed, action_map.get_key(action_key)]
1585            }
1586            "##
1587            .to_vec(),
1588        )?;
1589
1590        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1591        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1592        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1593        let panel_id: Dynamic = "panel".into();
1594        let action_id: Dynamic = "open".into();
1595
1596        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1597
1598        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1599        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1600        assert!(action_map.get_dynamic("panel#open").is_none());
1601        Ok(())
1602    }
1603
1604    #[test]
1605    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1606        let vm = Vm::with_all()?;
1607        vm.import_code(
1608            "vm_dynamic_field_or",
1609            r#"
1610            pub fn direct_next() {
1611                let choice = {
1612                    label: "颜色",
1613                    next: "color"
1614                };
1615                choice.next
1616            }
1617
1618            pub fn bracket_next() {
1619                let choice = {
1620                    label: "颜色",
1621                    next: "color"
1622                };
1623                choice["next"]
1624            }
1625            "#
1626            .as_bytes()
1627            .to_vec(),
1628        )?;
1629
1630        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1631        assert_eq!(compiled.ret_ty(), &Type::Any);
1632        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1633        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1634
1635        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1636        assert_eq!(compiled.ret_ty(), &Type::Any);
1637        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1638        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1639        Ok(())
1640    }
1641
1642    #[test]
1643    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1644        let vm = Vm::with_all()?;
1645        vm.import_code(
1646            "vm_if_empty_object_branch",
1647            r#"
1648            pub fn first_note(steps) {
1649                let first = if steps.len() > 0 { steps[0] } else { {} };
1650                let first_note = if first.contains("note") { first.note } else { "fallback" };
1651                first_note
1652            }
1653
1654            pub fn first_ja(steps) {
1655                let first = if steps.len() > 0 { steps[0] } else { {} };
1656                if first.contains("ja") { first.ja } else { "すみません" }
1657            }
1658
1659            pub fn assign_first_note(steps) {
1660                let first = {};
1661                first = if steps.len() > 0 { steps[0] } else { {} };
1662                if first.contains("note") { first.note } else { "fallback" }
1663            }
1664            "#
1665            .as_bytes()
1666            .to_vec(),
1667        )?;
1668
1669        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1670        assert_eq!(compiled.ret_ty(), &Type::Any);
1671        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1672
1673        let empty_steps = Dynamic::list(Vec::new());
1674        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1675
1676        let mut step = std::collections::BTreeMap::new();
1677        step.insert("note".into(), "hello".into());
1678        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1679        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1680
1681        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1682        assert_eq!(compiled.ret_ty(), &Type::Any);
1683        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1684        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1685
1686        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1687        assert_eq!(compiled.ret_ty(), &Type::Any);
1688        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1689        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1690        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1691        Ok(())
1692    }
1693
1694    #[test]
1695    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1696        let vm = Vm::with_all()?;
1697        vm.import_code(
1698            "vm_tail_list_literal",
1699            r#"
1700            pub fn numbers() {
1701                [1, 2, 3]
1702            }
1703
1704            pub fn maps() {
1705                [
1706                    {note: "first"},
1707                    {note: "second"}
1708                ]
1709            }
1710
1711            pub fn object_with_maps() {
1712                {
1713                    steps: [
1714                        {note: "first"},
1715                        {note: "second"}
1716                    ]
1717                }
1718            }
1719
1720            pub fn return_maps() {
1721                return [
1722                    {note: "first"},
1723                    {note: "second"}
1724                ];
1725            }
1726
1727            pub fn return_maps_without_semicolon() {
1728                return [
1729                    {note: "first"},
1730                    {note: "second"}
1731                ]
1732            }
1733
1734            pub fn tail_bare_variable() {
1735                let value = [
1736                    {note: "first"},
1737                    {note: "second"}
1738                ];
1739                value
1740            }
1741
1742            pub fn return_bare_variable_without_semicolon() {
1743                let value = [
1744                    {note: "first"},
1745                    {note: "second"}
1746                ];
1747                return value
1748            }
1749
1750            pub fn tail_object_variable() {
1751                let result = {
1752                    steps: [
1753                        {note: "first"},
1754                        {note: "second"}
1755                    ]
1756                };
1757                result
1758            }
1759            "#
1760            .as_bytes()
1761            .to_vec(),
1762        )?;
1763
1764        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1765        assert_eq!(compiled.ret_ty(), &Type::Any);
1766        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1767        let result = unsafe { &*numbers() };
1768        assert_eq!(result.len(), 3);
1769        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1770
1771        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1772        assert_eq!(compiled.ret_ty(), &Type::Any);
1773        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1774        let result = unsafe { &*maps() };
1775        assert_eq!(result.len(), 2);
1776        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1777
1778        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1779        assert_eq!(compiled.ret_ty(), &Type::Any);
1780        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1781        let result = unsafe { &*object_with_maps() };
1782        let steps = result.get_dynamic("steps").expect("steps");
1783        assert_eq!(steps.len(), 2);
1784        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1785
1786        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1787        assert_eq!(compiled.ret_ty(), &Type::Any);
1788        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1789        let result = unsafe { &*return_maps() };
1790        assert_eq!(result.len(), 2);
1791        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1792
1793        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1794        assert_eq!(compiled.ret_ty(), &Type::Any);
1795        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1796        let result = unsafe { &*return_maps_without_semicolon() };
1797        assert_eq!(result.len(), 2);
1798        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1799
1800        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1801        assert_eq!(compiled.ret_ty(), &Type::Any);
1802        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1803        let result = unsafe { &*tail_bare_variable() };
1804        assert_eq!(result.len(), 2);
1805        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1806
1807        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1808        assert_eq!(compiled.ret_ty(), &Type::Any);
1809        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1810        let result = unsafe { &*return_bare_variable_without_semicolon() };
1811        assert_eq!(result.len(), 2);
1812        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1813
1814        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1815        assert_eq!(compiled.ret_ty(), &Type::Any);
1816        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1817        let result = unsafe { &*tail_object_variable() };
1818        let steps = result.get_dynamic("steps").expect("steps");
1819        assert_eq!(steps.len(), 2);
1820        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1821        Ok(())
1822    }
1823
1824    #[test]
1825    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1826        let vm = Vm::with_all()?;
1827        vm.import_code(
1828            "vm_returned_list_get_idx",
1829            r#"
1830            pub fn ids() {
1831                [
1832                    "base",
1833                    "2",
1834                    "3"
1835                ]
1836            }
1837
1838            pub fn combinations() {
1839                let result = [];
1840                let values = ids();
1841                let idx = 0;
1842                while idx < values.len() {
1843                    result.push(values.get_idx(idx));
1844                    idx = idx + 1;
1845                }
1846                result
1847            }
1848            "#
1849            .as_bytes()
1850            .to_vec(),
1851        )?;
1852
1853        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1854        assert_eq!(compiled.ret_ty(), &Type::Any);
1855        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1856        let result = unsafe { &*combinations() };
1857
1858        assert_eq!(result.len(), 3);
1859        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1860        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1861        Ok(())
1862    }
1863
1864    #[test]
1865    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1866        fn extra_page_literal(depth: usize) -> String {
1867            let mut value = "{leaf: \"done\"}".to_string();
1868            for idx in 0..depth {
1869                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1870            }
1871            value
1872        }
1873
1874        let extra = extra_page_literal(48);
1875        let code = format!(
1876            r#"
1877            pub fn script() {{
1878                return [
1879                    {{ja: "一つ目", note: "first", extra: {extra}}},
1880                    {{ja: "二つ目", note: "second", extra: {extra}}},
1881                    {{ja: "三つ目", note: "third", extra: {extra}}}
1882                ]
1883            }}
1884            "#
1885        );
1886
1887        let vm = Vm::with_all()?;
1888        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1889        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1890        assert_eq!(compiled.ret_ty(), &Type::Any);
1891        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1892        let result = unsafe { &*script() };
1893        assert_eq!(result.len(), 3);
1894        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1895        Ok(())
1896    }
1897
1898    #[test]
1899    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1900        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1901        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1902        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1903
1904        let vm1 = Vm::with_all()?;
1905        vm1.import_code(
1906            "vm_import_owner",
1907            format!(
1908                r#"
1909                pub fn run() {{
1910                    import("vm_imported_owner", "{module_path}");
1911                }}
1912                "#
1913            )
1914            .into_bytes(),
1915        )?;
1916        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1917
1918        let vm2 = Vm::with_all()?;
1919        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1920        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1921
1922        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1923        run();
1924
1925        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1926        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1927        Ok(())
1928    }
1929
1930    #[test]
1931    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1932        let vm = Vm::with_all()?;
1933        vm.import_code(
1934            "vm_object_last_call_field",
1935            r#"
1936            pub fn extra_page() {
1937                {
1938                    title: "extra",
1939                    pages: [
1940                        {note: "nested"}
1941                    ]
1942                }
1943            }
1944
1945            pub fn data() {
1946                return [
1947                    {
1948                        note: "first",
1949                        choices: ["a", "b"],
1950                        extras: extra_page()
1951                    },
1952                    {
1953                        note: "second",
1954                        choices: ["c"],
1955                        extras: extra_page()
1956                    }
1957                ]
1958            }
1959            "#
1960            .as_bytes()
1961            .to_vec(),
1962        )?;
1963
1964        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1965        assert_eq!(compiled.ret_ty(), &Type::Any);
1966        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1967        let result = unsafe { &*data() };
1968        assert_eq!(result.len(), 2);
1969        let first = result.get_idx(0).expect("first step");
1970        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1971        Ok(())
1972    }
1973
1974    #[test]
1975    fn string_return_survives_scope_exit() -> anyhow::Result<()> {
1976        let vm = Vm::with_all()?;
1977        vm.import_code(
1978            "vm_string_return_scope",
1979            r#"
1980            pub fn source_root() {
1981                "../assets/character/男主角换装"
1982            }
1983
1984            pub fn binary_root() {
1985                "character_binary/男主角换装"
1986            }
1987
1988            pub fn runtime_binary_url() {
1989                "/" + binary_root()
1990            }
1991
1992            pub fn action_groups() {
1993                let root = source_root();
1994                let binary_url = runtime_binary_url();
1995                let binary_root = binary_root();
1996                [
1997                    {
1998                        id: "field_bottom",
1999                        source_spine: root + "/战斗外/boy_b.spine",
2000                        skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
2001                        export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
2002                    }
2003                ]
2004            }
2005            "#
2006            .as_bytes()
2007            .to_vec(),
2008        )?;
2009
2010        let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
2011        assert_eq!(compiled.ret_ty(), &Type::Str);
2012        let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2013        let source_root = unsafe { &*source_root() };
2014        assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
2015
2016        let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
2017        assert_eq!(compiled.ret_ty(), &Type::Any);
2018        let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2019        let groups = unsafe { &*action_groups() };
2020        let first = groups.get_idx(0).expect("first action group");
2021        assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
2022        assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
2023        Ok(())
2024    }
2025
2026    #[test]
2027    fn dynamic_string_add_uses_any_binary_fast_path() -> anyhow::Result<()> {
2028        let vm = Vm::with_all()?;
2029        vm.import_code(
2030            "vm_dynamic_string_add",
2031            br#"
2032            pub fn concat(left, right) {
2033                left + right
2034            }
2035            "#
2036            .to_vec(),
2037        )?;
2038
2039        let compiled = vm.get_fn("vm_dynamic_string_add::concat", &[Type::Any, Type::Any])?;
2040        assert_eq!(compiled.ret_ty(), &Type::Any);
2041        let concat: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2042        let left = Dynamic::from("hello");
2043        let right = Dynamic::from(" world");
2044        let result = unsafe { &*concat(&left, &right) };
2045        assert_eq!(result.as_str(), "hello world");
2046        Ok(())
2047    }
2048
2049    #[test]
2050    fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
2051        let vm = Vm::with_all()?;
2052        let model_count = 180;
2053        let combination_count = 90;
2054        let models = (0..model_count)
2055            .map(|idx| {
2056                format!(
2057                    r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
2058                        {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
2059                        {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
2060                        {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
2061                    ]}}"#
2062                )
2063            })
2064            .collect::<Vec<_>>()
2065            .join(",\n");
2066        let combinations = (0..combination_count).map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#)).collect::<Vec<_>>().join(",\n");
2067        let code = format!(
2068            r#"
2069            pub fn source_root() {{
2070                "/美术资源/角色/少年/默认"
2071            }}
2072
2073            pub fn runtime_boy_url() {{
2074                "/cdn/runtime/角色/少年/少年.model.json"
2075            }}
2076
2077            pub fn parts() {{
2078                [
2079                    {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
2080                    {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
2081                    {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
2082                ]
2083            }}
2084
2085            pub fn action_groups() {{
2086                {{
2087                    idle: [
2088                        {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
2089                        {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
2090                    ],
2091                    move: [
2092                        {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
2093                        {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
2094                    ]
2095                }}
2096            }}
2097
2098            pub fn default_model() {{
2099                {{
2100                    id: "runtime_boy",
2101                    name: "运行时少年",
2102                    skins: [
2103                        {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
2104                        {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
2105                    ],
2106                    models: [
2107                        {models}
2108                    ]
2109                }}
2110            }}
2111
2112            pub fn first_nine_combinations() {{
2113                [
2114                    {combinations}
2115                ]
2116            }}
2117
2118            pub fn config() {{
2119                {{
2120                    source_root: source_root(),
2121                    runtime_boy_url: runtime_boy_url(),
2122                    parts: parts(),
2123                    action_groups: action_groups(),
2124                    default_model: default_model(),
2125                    first_nine_combinations: first_nine_combinations()
2126                }}
2127            }}
2128
2129            pub fn start() {{
2130                root::add("local/vm_large_inline_call_object/config", {{
2131                    source_root: source_root(),
2132                    runtime_boy_url: runtime_boy_url(),
2133                    parts: parts(),
2134                    action_groups: action_groups(),
2135                    default_model: default_model(),
2136                    first_nine_combinations: first_nine_combinations()
2137                }})
2138            }}
2139            "#
2140        );
2141        vm.import_code("vm_large_inline_call_object", code.into_bytes())?;
2142
2143        let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
2144        assert_eq!(compiled.ret_ty(), &Type::Any);
2145        let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2146        let result = unsafe { &*config() };
2147        assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
2148        assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
2149
2150        let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
2151        assert_eq!(compiled.ret_ty(), &Type::Bool);
2152        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2153        assert!(start());
2154        let saved = root::get("local/vm_large_inline_call_object/config")?;
2155        assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
2156        Ok(())
2157    }
2158
2159    #[test]
2160    fn http_serve_accepts_inline_config_map() -> anyhow::Result<()> {
2161        let vm = Vm::with_all()?;
2162        vm.import_code(
2163            "vm_http_serve_inline_config",
2164            br#"
2165            pub fn start() {
2166                let server = http::serve({host: "127.0.0.1:5192"});
2167                server
2168            }
2169            "#
2170            .to_vec(),
2171        )?;
2172
2173        let compiled = vm.get_fn("vm_http_serve_inline_config::start", &[])?;
2174        assert_eq!(compiled.ret_ty(), &Type::Any);
2175        Ok(())
2176    }
2177
2178    #[test]
2179    fn http_serve_accepts_variable_and_quoted_static_key() -> anyhow::Result<()> {
2180        let vm = Vm::with_all()?;
2181        vm.import_code(
2182            "vm_http_serve_quoted_static",
2183            br#"
2184            pub fn start(server_addr) {
2185                let http_server = http::serve({
2186                    host: server_addr,
2187                    ws: true,
2188                    upload: "upload",
2189                    "static": {
2190                        path: "/",
2191                        dir: "public/local"
2192                    }
2193                });
2194                http_server
2195            }
2196            "#
2197            .to_vec(),
2198        )?;
2199
2200        let compiled = vm.get_fn("vm_http_serve_quoted_static::start", &[Type::Any])?;
2201        assert_eq!(compiled.ret_ty(), &Type::Any);
2202        Ok(())
2203    }
2204
2205    #[test]
2206    fn oss_helpers_accept_explicit_config() -> anyhow::Result<()> {
2207        let vm = Vm::with_all()?;
2208        vm.import_code(
2209            "vm_oss_explicit_config",
2210            br#"
2211            pub fn upload(oss, bytes) {
2212                oss::upload(oss, "llm/input/audio.wav", bytes)
2213            }
2214
2215            pub fn http_upload(oss, bytes) {
2216                http::upload(oss, "uploads/input.bin", bytes)
2217            }
2218
2219            pub fn link(oss, uploaded) {
2220                oss::signed_url(oss, {oss_url: uploaded, expires: 3600})
2221            }
2222            "#
2223            .to_vec(),
2224        )?;
2225
2226        assert_eq!(vm.get_fn("vm_oss_explicit_config::upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
2227        assert_eq!(vm.get_fn("vm_oss_explicit_config::http_upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
2228        assert_eq!(vm.get_fn("vm_oss_explicit_config::link", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
2229        Ok(())
2230    }
2231
2232    #[test]
2233    fn load_script_accepts_http_serve_inline_config() -> anyhow::Result<()> {
2234        let vm = Vm::with_all()?;
2235        let (_fn_ptr, ty) = vm.load(
2236            br#"
2237            let server_addr = "127.0.0.1:5192";
2238            let http_server = http::serve({
2239                host: server_addr,
2240                ws: true,
2241                upload: "upload",
2242                "static": {
2243                    path: "/",
2244                    dir: "public/local"
2245                }
2246            });
2247            http_server
2248            "#
2249            .to_vec(),
2250            "arg".into(),
2251        )?;
2252
2253        assert_eq!(ty, Type::Any);
2254        Ok(())
2255    }
2256
2257    #[test]
2258    fn load_script_resolves_import_before_compile() -> anyhow::Result<()> {
2259        let module_path = std::env::temp_dir().join(format!("zust_vm_load_import_{}.zs", std::process::id()));
2260        std::fs::write(&module_path, "pub fn init() { return {ok: true}; }")?;
2261        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
2262
2263        let vm = Vm::with_all()?;
2264        let (_fn_ptr, ty) = vm.load(
2265            format!(
2266                r#"
2267                import("create_scene", "{module_path}");
2268                create_scene::init();
2269                "#
2270            )
2271            .into_bytes(),
2272            "req".into(),
2273        )?;
2274
2275        assert_eq!(ty, Type::Void);
2276        Ok(())
2277    }
2278
2279    #[test]
2280    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
2281        let vm = Vm::with_all()?;
2282        vm.import_code(
2283            "vm_gpu_layout",
2284            br#"
2285            pub struct Params {
2286                a: u32,
2287                b: u32,
2288                c: u32,
2289            }
2290            "#
2291            .to_vec(),
2292        )?;
2293
2294        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
2295        assert_eq!(layout.size, 16);
2296        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
2297
2298        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
2299        let bytes = layout.pack_map(&value)?;
2300        assert_eq!(bytes.len(), 16);
2301        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
2302        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
2303        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
2304
2305        let read = layout.unpack_map(&bytes)?;
2306        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
2307        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
2308        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
2309        Ok(())
2310    }
2311
2312    #[test]
2313    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
2314        let vm = Vm::with_all()?;
2315        vm.import_code(
2316            "vm_root_clone_bridge",
2317            br#"
2318            pub fn add_then_reuse(arg) {
2319                let user = {
2320                    address: "test-wallet",
2321                    points: 20
2322                };
2323                root::add("local/root-clone-bridge-user", user);
2324                user.points = user.points - 7;
2325                root::add("local/root-clone-bridge-user", user);
2326                {
2327                    user: user,
2328                    points: user.points
2329                }
2330            }
2331
2332            pub fn clone_then_mutate(arg) {
2333                let user = {
2334                    profile: {
2335                        points: 20
2336                    }
2337                };
2338                let copied = user.clone();
2339                copied.profile.points = 13;
2340                user
2341            }
2342            "#
2343            .to_vec(),
2344        )?;
2345
2346        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
2347        assert_eq!(compiled.ret_ty(), &Type::Any);
2348        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2349        let arg = Dynamic::Null;
2350        let result = add_then_reuse(&arg);
2351        let result = unsafe { &*result };
2352
2353        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
2354        let mut json = String::new();
2355        result.to_json(&mut json);
2356        assert!(json.contains("\"points\": 13"));
2357
2358        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
2359        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
2360        let result = clone_then_mutate(&arg);
2361        let result = unsafe { &*result };
2362        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
2363        Ok(())
2364    }
2365
2366    struct CounterForTypedReceiver {
2367        value: i64,
2368    }
2369
2370    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
2371        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
2372    }
2373
2374    struct NavMapForFunctionArg;
2375
2376    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
2377        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
2378    }
2379
2380    #[test]
2381    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
2382        let vm = Vm::with_all()?;
2383        vm.add_empty_type("Counter")?;
2384        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
2385        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
2386        vm.import_code(
2387            "vm_typed_receiver_method",
2388            br#"
2389            pub fn run(value) {
2390                value::<Counter>::get()
2391            }
2392            "#
2393            .to_vec(),
2394        )?;
2395
2396        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
2397        assert_eq!(compiled.ret_ty(), &Type::I64);
2398        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2399        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
2400
2401        assert_eq!(run(&value), 42);
2402        Ok(())
2403    }
2404
2405    #[test]
2406    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2407        let vm = Vm::with_all()?;
2408        vm.add_empty_type("NavMap")?;
2409        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2410        vm.import_code(
2411            "vm_native_custom_arg",
2412            br#"
2413            pub fn add_nav_spawns(world, navmap) {
2414                navmap
2415            }
2416
2417            pub fn run(world) {
2418                let navmap = NavMap::new();
2419                let with_spawns = add_nav_spawns(world, navmap);
2420                with_spawns
2421            }
2422            "#
2423            .to_vec(),
2424        )?;
2425
2426        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
2427        assert_eq!(compiled.ret_ty(), &Type::Any);
2428        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2429        let world = Dynamic::Null;
2430        let result = run(&world);
2431        let result = unsafe { &*result };
2432
2433        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2434        Ok(())
2435    }
2436
2437    #[test]
2438    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2439        let vm = Vm::with_all()?;
2440        vm.add_empty_type("NavMap")?;
2441        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
2442        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2443        vm.import_code(
2444            "vm_native_custom_typed_arg",
2445            br#"
2446            pub fn add_nav_spawns(world, navmap) {
2447                navmap
2448            }
2449
2450            pub fn run(world) {
2451                let navmap: NavMap = NavMap::new();
2452                let with_spawns = add_nav_spawns(world, navmap);
2453                with_spawns
2454            }
2455            "#
2456            .to_vec(),
2457        )?;
2458
2459        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
2460        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2461        let world = Dynamic::Null;
2462        let result = run(&world);
2463        let result = unsafe { &*result };
2464
2465        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2466        Ok(())
2467    }
2468
2469    // ---- 新增边界条件测试 ----
2470
2471    #[test]
2472    fn dynamic_type_checks_on_null_and_primitive_values() -> anyhow::Result<()> {
2473        let vm = Vm::with_all()?;
2474        vm.import_code(
2475            "vm_dynamic_type_checks",
2476            br#"
2477            pub fn is_list_on_int() {
2478                let x = 42i64;
2479                x.is_list()
2480            }
2481
2482            pub fn is_map_on_int() {
2483                let x = 42i64;
2484                x.is_map()
2485            }
2486
2487            pub fn is_null_on_int() {
2488                let x = 42i64;
2489                x.is_null()
2490            }
2491            "#
2492            .to_vec(),
2493        )?;
2494
2495        let compiled = vm.get_fn("vm_dynamic_type_checks::is_list_on_int", &[])?;
2496        assert_eq!(compiled.ret_ty(), &Type::Bool);
2497        let is_list_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2498        assert!(!is_list_on_int());
2499
2500        let compiled = vm.get_fn("vm_dynamic_type_checks::is_map_on_int", &[])?;
2501        assert_eq!(compiled.ret_ty(), &Type::Bool);
2502        let is_map_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2503        assert!(!is_map_on_int());
2504
2505        let compiled = vm.get_fn("vm_dynamic_type_checks::is_null_on_int", &[])?;
2506        assert_eq!(compiled.ret_ty(), &Type::Bool);
2507        let is_null_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2508        assert!(!is_null_on_int());
2509        Ok(())
2510    }
2511
2512    #[test]
2513    fn empty_for_loop_range_has_zero_iterations() -> anyhow::Result<()> {
2514        let vm = Vm::with_all()?;
2515        vm.import_code(
2516            "vm_empty_for_range",
2517            br#"
2518            pub fn empty_exclusive() {
2519                let count = 0i32;
2520                for i in 0..0 {
2521                    count += i;
2522                }
2523                count
2524            }
2525
2526            pub fn single_inclusive_iteration() {
2527                let count = 0i32;
2528                for i in 5..=5 {
2529                    count += i;
2530                }
2531                count
2532            }
2533            "#
2534            .to_vec(),
2535        )?;
2536
2537        let compiled = vm.get_fn("vm_empty_for_range::empty_exclusive", &[])?;
2538        assert_eq!(compiled.ret_ty(), &Type::I32);
2539        let empty_exclusive: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2540        assert_eq!(empty_exclusive(), 0);
2541
2542        let compiled = vm.get_fn("vm_empty_for_range::single_inclusive_iteration", &[])?;
2543        assert_eq!(compiled.ret_ty(), &Type::I32);
2544        let single_inclusive: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2545        assert_eq!(single_inclusive(), 5);
2546        Ok(())
2547    }
2548
2549    #[test]
2550    fn map_contains_key_on_non_existent_and_nested_keys() -> anyhow::Result<()> {
2551        let vm = Vm::with_all()?;
2552        vm.import_code(
2553            "vm_map_contains",
2554            br#"
2555            pub fn contains_existing(data) {
2556                data.contains("name")
2557            }
2558
2559            pub fn contains_missing(data) {
2560                data.contains("nothing")
2561            }
2562            "#
2563            .to_vec(),
2564        )?;
2565
2566        let compiled = vm.get_fn("vm_map_contains::contains_existing", &[Type::Any])?;
2567        assert_eq!(compiled.ret_ty(), &Type::Bool);
2568        let contains_existing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2569        let data = dynamic::map!("name"=> "test");
2570        assert!(contains_existing(&data));
2571
2572        let compiled = vm.get_fn("vm_map_contains::contains_missing", &[Type::Any])?;
2573        assert_eq!(compiled.ret_ty(), &Type::Bool);
2574        let contains_missing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2575        assert!(!contains_missing(&data));
2576        Ok(())
2577    }
2578
2579    #[test]
2580    fn list_pop_on_empty_list_returns_null() -> anyhow::Result<()> {
2581        let vm = Vm::with_all()?;
2582        vm.import_code(
2583            "vm_pop_empty",
2584            br#"
2585            pub fn pop_new_list() {
2586                let items = [];
2587                let value = items.pop();
2588                let still_empty = items.len() == 0;
2589                {value: value, empty: still_empty}
2590            }
2591
2592            pub fn pop_until_empty() {
2593                let items = [1i64, 2i64];
2594                items.pop();
2595                let last = items.pop();
2596                let drained = items.pop();
2597                {last: last, drained: drained}
2598            }
2599            "#
2600            .to_vec(),
2601        )?;
2602
2603        let compiled = vm.get_fn("vm_pop_empty::pop_new_list", &[])?;
2604        assert_eq!(compiled.ret_ty(), &Type::Any);
2605        let pop_new_list: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2606        let result = unsafe { &*pop_new_list() };
2607        assert!(result.get_dynamic("value").is_some_and(|v| v.is_null()));
2608        assert_eq!(result.get_dynamic("empty").and_then(|v| v.as_bool()), Some(true));
2609
2610        let compiled = vm.get_fn("vm_pop_empty::pop_until_empty", &[])?;
2611        assert_eq!(compiled.ret_ty(), &Type::Any);
2612        let pop_until_empty: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2613        let result = unsafe { &*pop_until_empty() };
2614        assert_eq!(result.get_dynamic("last").and_then(|v| v.as_int()), Some(1));
2615        assert!(result.get_dynamic("drained").is_some_and(|v| v.is_null()));
2616        Ok(())
2617    }
2618
2619    #[test]
2620    fn void_function_with_multiple_code_paths() -> anyhow::Result<()> {
2621        let vm = Vm::with_all()?;
2622        vm.import_code(
2623            "vm_void_multi_path",
2624            br#"
2625            pub fn log_if_positive(value: i64) {
2626                if value > 0 {
2627                    print(value);
2628                    return;
2629                }
2630                if value < 0 {
2631                    print(-value);
2632                    return;
2633                }
2634                print(0);
2635            }
2636            "#
2637            .to_vec(),
2638        )?;
2639
2640        let compiled = vm.get_fn("vm_void_multi_path::log_if_positive", &[Type::I64])?;
2641        assert!(compiled.ret_ty().is_void());
2642        Ok(())
2643    }
2644
2645    #[test]
2646    fn any_method_call_chain_on_returned_dynamic_value() -> anyhow::Result<()> {
2647        let vm = Vm::with_all()?;
2648        vm.import_code(
2649            "vm_any_method_chain",
2650            br#"
2651            pub fn get_tags(data) {
2652                let tags = data.tags;
2653                if tags.is_list() {
2654                    return tags.len();
2655                }
2656                0
2657            }
2658            "#
2659            .to_vec(),
2660        )?;
2661
2662        let compiled = vm.get_fn("vm_any_method_chain::get_tags", &[Type::Any])?;
2663        assert_eq!(compiled.ret_ty(), &Type::I32);
2664        let get_tags: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2665        let data = dynamic::map!("tags"=> Dynamic::list(vec!["a".into(), "b".into(), "c".into()]));
2666        assert_eq!(get_tags(&data), 3);
2667
2668        let empty_data = Dynamic::Null;
2669        assert_eq!(get_tags(&empty_data), 0);
2670        Ok(())
2671    }
2672
2673    #[test]
2674    fn infers_any_arg_function_return_before_body_compile() -> anyhow::Result<()> {
2675        let vm = Vm::with_all()?;
2676        vm.import_code(
2677            "vm_infer_any_arg_return",
2678            br#"
2679            pub fn caller(candidate) {
2680                let center = polygon_center(candidate.visualPolygon);
2681                center[0]
2682            }
2683
2684            pub fn polygon_center(point_list) {
2685                let total_x = 0;
2686                let total_y = 0;
2687                let count = 0;
2688                if point_list.is_list() {
2689                    for point in point_list {
2690                        if point.is_list() && point.len() >= 2 {
2691                            total_x += point[0];
2692                            total_y += point[1];
2693                            count += 1;
2694                        }
2695                    }
2696                }
2697                if count == 0 {
2698                    return [0, 0];
2699                }
2700                [total_x / count, total_y / count]
2701            }
2702            "#
2703            .to_vec(),
2704        )?;
2705
2706        let compiled = vm.get_fn("vm_infer_any_arg_return::caller", &[Type::Any])?;
2707        assert_eq!(compiled.ret_ty(), &Type::Any);
2708        let caller: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2709        let candidate = dynamic::map!(
2710            "visualPolygon"=> Dynamic::list(vec![
2711                Dynamic::list(vec![2i64.into(), 4i64.into()]),
2712                Dynamic::list(vec![6i64.into(), 8i64.into()]),
2713            ])
2714        );
2715        let result = unsafe { &*caller(&candidate) };
2716        assert_eq!(result.as_int(), Some(4));
2717        Ok(())
2718    }
2719
2720    #[test]
2721    fn recursive_factorial_keeps_static_return_type() -> anyhow::Result<()> {
2722        let vm = Vm::with_all()?;
2723        vm.import_code(
2724            "vm_recursive_factorial",
2725            br#"
2726            fn factorial(n: i64) {
2727                if n <= 1 {
2728                    return 1;
2729                }
2730                n * factorial(n - 1)
2731            }
2732
2733            pub fn run(n: i64) {
2734                factorial(n)
2735            }
2736            "#
2737            .to_vec(),
2738        )?;
2739
2740        let compiled = vm.get_fn("vm_recursive_factorial::run", &[Type::I64])?;
2741        assert_eq!(compiled.ret_ty(), &Type::I64);
2742        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2743        assert_eq!(run(5), 120);
2744        Ok(())
2745    }
2746
2747    #[test]
2748    fn dynamic_list_index_sum_uses_static_accumulator_type() -> anyhow::Result<()> {
2749        let vm = Vm::with_all()?;
2750        vm.import_code(
2751            "vm_dynamic_index_sum",
2752            br#"
2753            pub fn sum_list(n: i64) {
2754                let l = [];
2755                for i in 0..n {
2756                    l.push(i);
2757                }
2758                let sum = 0i64;
2759                for i in 0..n {
2760                    sum = sum + l.get_idx(i);
2761                }
2762                sum
2763            }
2764            "#
2765            .to_vec(),
2766        )?;
2767
2768        let compiled = vm.get_fn("vm_dynamic_index_sum::sum_list", &[Type::I64])?;
2769        assert_eq!(compiled.ret_ty(), &Type::I64);
2770        let sum_list: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2771        assert_eq!(sum_list(1000), 499500);
2772        Ok(())
2773    }
2774
2775    #[test]
2776    fn root_get_returns_null_for_missing_key_which_compares_correctly() -> anyhow::Result<()> {
2777        let vm = Vm::with_all()?;
2778        vm.import_code(
2779            "vm_root_get_missing",
2780            br#"
2781            pub fn check_missing() {
2782                let existing = root::get("local/vm_root_get_missing_test");
2783                if existing.is_map() {
2784                    return false;
2785                }
2786                true
2787            }
2788            "#
2789            .to_vec(),
2790        )?;
2791
2792        let compiled = vm.get_fn("vm_root_get_missing::check_missing", &[])?;
2793        assert_eq!(compiled.ret_ty(), &Type::Bool);
2794        let check_missing: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2795        assert!(check_missing());
2796        Ok(())
2797    }
2798
2799    #[test]
2800    fn map_get_key_on_null_map_returns_null() -> anyhow::Result<()> {
2801        let vm = Vm::with_all()?;
2802        vm.import_code(
2803            "vm_get_key_null_map",
2804            br#"
2805            pub fn get_key_null(data) {
2806                data.get_key("missing")
2807            }
2808            "#
2809            .to_vec(),
2810        )?;
2811
2812        let compiled = vm.get_fn("vm_get_key_null_map::get_key_null", &[Type::Any])?;
2813        assert_eq!(compiled.ret_ty(), &Type::Any);
2814        let get_key_null: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2815
2816        let data_map = dynamic::map!("exists"=> 1i64);
2817        let missing = unsafe { &*get_key_null(&data_map) };
2818        assert!(missing.is_null());
2819
2820        let null = Dynamic::Null;
2821        let result = unsafe { &*get_key_null(&null) };
2822        assert!(result.is_null());
2823        Ok(())
2824    }
2825
2826    #[test]
2827    fn keys_on_empty_map_returns_empty_list() -> anyhow::Result<()> {
2828        let vm = Vm::with_all()?;
2829        vm.import_code(
2830            "vm_keys_empty_map",
2831            br#"
2832            pub fn empty_map_keys() {
2833                let data = {};
2834                data.keys().len()
2835            }
2836            "#
2837            .to_vec(),
2838        )?;
2839
2840        let compiled = vm.get_fn("vm_keys_empty_map::empty_map_keys", &[])?;
2841        assert_eq!(compiled.ret_ty(), &Type::I32);
2842        let empty_map_keys: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2843        assert_eq!(empty_map_keys(), 0);
2844        Ok(())
2845    }
2846
2847    #[test]
2848    fn cast_between_all_integer_widths() -> anyhow::Result<()> {
2849        let vm = Vm::with_all()?;
2850        vm.import_code(
2851            "vm_cast_integer_widths",
2852            br#"
2853            pub fn i64_to_i32(value: i64) {
2854                value as i32
2855            }
2856
2857            pub fn i32_to_i64(value: i32) {
2858                value as i64
2859            }
2860
2861            pub fn u32_to_i64(value: u32) {
2862                value as i64
2863            }
2864            "#
2865            .to_vec(),
2866        )?;
2867
2868        let compiled = vm.get_fn("vm_cast_integer_widths::i64_to_i32", &[Type::I64])?;
2869        assert_eq!(compiled.ret_ty(), &Type::I32);
2870        let i64_to_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2871        assert_eq!(i64_to_i32(42), 42);
2872
2873        let compiled = vm.get_fn("vm_cast_integer_widths::i32_to_i64", &[Type::I32])?;
2874        assert_eq!(compiled.ret_ty(), &Type::I64);
2875        let i32_to_i64: extern "C" fn(i32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2876        assert_eq!(i32_to_i64(-1), -1);
2877
2878        let compiled = vm.get_fn("vm_cast_integer_widths::u32_to_i64", &[Type::U32])?;
2879        assert_eq!(compiled.ret_ty(), &Type::I64);
2880        let u32_to_i64: extern "C" fn(u32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2881        assert_eq!(u32_to_i64(42), 42);
2882        Ok(())
2883    }
2884
2885    #[test]
2886    fn boolean_literals_in_complex_expression_trees() -> anyhow::Result<()> {
2887        let vm = Vm::with_all()?;
2888        vm.import_code(
2889            "vm_complex_boolean",
2890            br#"
2891            pub fn exclusive_or(a: bool, b: bool) {
2892                (a && !b) || (!a && b)
2893            }
2894
2895            pub fn implies(a: bool, b: bool) {
2896                !a || b
2897            }
2898            "#
2899            .to_vec(),
2900        )?;
2901
2902        let compiled = vm.get_fn("vm_complex_boolean::exclusive_or", &[Type::Bool, Type::Bool])?;
2903        assert_eq!(compiled.ret_ty(), &Type::Bool);
2904        let exclusive_or: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2905        assert!(exclusive_or(true, false));
2906        assert!(exclusive_or(false, true));
2907        assert!(!exclusive_or(true, true));
2908        assert!(!exclusive_or(false, false));
2909
2910        let compiled = vm.get_fn("vm_complex_boolean::implies", &[Type::Bool, Type::Bool])?;
2911        assert_eq!(compiled.ret_ty(), &Type::Bool);
2912        let implies: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2913        assert!(implies(false, true));
2914        assert!(implies(false, false));
2915        assert!(implies(true, true));
2916        assert!(!implies(true, false));
2917        Ok(())
2918    }
2919
2920    #[test]
2921    fn concrete_struct_method_returning_self_type() -> anyhow::Result<()> {
2922        let vm = Vm::with_all()?;
2923        vm.import_code(
2924            "vm_struct_method_self",
2925            br#"
2926            pub struct Vec3 {
2927                x: f64,
2928                y: f64,
2929                z: f64,
2930            }
2931
2932            impl Vec3 {
2933                pub fn add(self: Vec3, other: Vec3) {
2934                    Vec3{x: self.x + other.x, y: self.y + other.y, z: self.z + other.z}
2935                }
2936            }
2937
2938            pub fn run() {
2939                let v1 = Vec3{x: 1.0f64, y: 2.0f64, z: 3.0f64};
2940                let v2 = Vec3{x: 4.0f64, y: 5.0f64, z: 6.0f64};
2941                let sum = v1.add(v2);
2942                sum.x + sum.y + sum.z
2943            }
2944            "#
2945            .to_vec(),
2946        )?;
2947
2948        let compiled = vm.get_fn("vm_struct_method_self::run", &[])?;
2949        assert_eq!(compiled.ret_ty(), &Type::F64);
2950        let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
2951        assert_eq!(run(), 21.0);
2952        Ok(())
2953    }
2954
2955    #[test]
2956    fn deep_nested_struct_access_with_multiple_field_levels() -> anyhow::Result<()> {
2957        let vm = Vm::with_all()?;
2958        vm.import_code(
2959            "vm_deep_nested_struct",
2960            br#"
2961            pub struct A {
2962                value: i64,
2963            }
2964
2965            pub struct B {
2966                a: A,
2967            }
2968
2969            pub struct C {
2970                b: B,
2971            }
2972
2973            pub fn direct_access() {
2974                let c = C{b: B{a: A{value: 99}}};
2975                c.b.a.value
2976            }
2977
2978            pub fn via_variable() {
2979                let c = C{b: B{a: A{value: 77}}};
2980                let b = c.b;
2981                let a = b.a;
2982                a.value
2983            }
2984            "#
2985            .to_vec(),
2986        )?;
2987
2988        let compiled = vm.get_fn("vm_deep_nested_struct::direct_access", &[])?;
2989        assert_eq!(compiled.ret_ty(), &Type::I64);
2990        let direct_access: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2991        assert_eq!(direct_access(), 99);
2992
2993        let compiled = vm.get_fn("vm_deep_nested_struct::via_variable", &[])?;
2994        assert_eq!(compiled.ret_ty(), &Type::I64);
2995        let via_variable: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2996        assert_eq!(via_variable(), 77);
2997        Ok(())
2998    }
2999
3000    #[test]
3001    fn array_index_with_dynamic_value_via_method() -> anyhow::Result<()> {
3002        let vm = Vm::with_all()?;
3003        vm.import_code(
3004            "vm_array_idx_dynamic",
3005            br#"
3006            pub fn get_by_idx(list, idx) {
3007                list.get_idx(idx)
3008            }
3009            "#
3010            .to_vec(),
3011        )?;
3012
3013        let compiled = vm.get_fn("vm_array_idx_dynamic::get_by_idx", &[Type::Any, Type::I64])?;
3014        assert_eq!(compiled.ret_ty(), &Type::Any);
3015        let get_by_idx: extern "C" fn(*const Dynamic, i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3016
3017        let list = Dynamic::list(vec!["a".into(), "b".into()]);
3018        let first = unsafe { &*get_by_idx(&list, 0) };
3019        assert_eq!(first.as_str(), "a");
3020
3021        let out = unsafe { &*get_by_idx(&list, 10) };
3022        assert!(out.is_null());
3023        Ok(())
3024    }
3025
3026    #[test]
3027    fn dynamic_field_access_with_optional_or_fallback() -> anyhow::Result<()> {
3028        let vm = Vm::with_all()?;
3029        vm.import_code(
3030            "vm_dynamic_or_fallback",
3031            br#"
3032            pub fn with_fallback(data) {
3033                if data.contains("name") { data.name } else { "unknown" }
3034            }
3035
3036            pub fn with_fallback_missing(data) {
3037                if data.contains("nickname") { data.nickname } else { "unnamed" }
3038            }
3039            "#
3040            .to_vec(),
3041        )?;
3042
3043        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback", &[Type::Any])?;
3044        assert_eq!(compiled.ret_ty(), &Type::Any);
3045        let with_fallback: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3046        let data = dynamic::map!("name"=> "Alice");
3047        let result = unsafe { &*with_fallback(&data) };
3048        assert_eq!(result.as_str(), "Alice");
3049
3050        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback_missing", &[Type::Any])?;
3051        let with_fallback_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3052        let result = unsafe { &*with_fallback_missing(&data) };
3053        assert_eq!(result.as_str(), "unnamed");
3054        Ok(())
3055    }
3056
3057    #[test]
3058    fn for_in_loop_iterates_over_list_and_map_directly() -> anyhow::Result<()> {
3059        let vm = Vm::with_all()?;
3060        vm.import_code(
3061            "vm_for_in_collection",
3062            br#"
3063            pub fn sum_list(items) {
3064                let total = 0i64;
3065                for item in items {
3066                    total = total + 1;
3067                }
3068                total
3069            }
3070
3071            pub fn count_map_keys(data) {
3072                let count = 0i64;
3073                for key in data.keys() {
3074                    count = count + 1;
3075                }
3076                count
3077            }
3078
3079            pub fn for_in_list_works(items) {
3080                let exists = false;
3081                for item in items {
3082                    exists = true;
3083                }
3084                exists
3085            }
3086
3087            pub fn for_in_map_values_works(data) {
3088                let exists = false;
3089                for value in data {
3090                    exists = true;
3091                }
3092                exists
3093            }
3094            "#
3095            .to_vec(),
3096        )?;
3097
3098        let compiled = vm.get_fn("vm_for_in_collection::sum_list", &[Type::Any])?;
3099        assert_eq!(compiled.ret_ty(), &Type::I64);
3100        let sum_list: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3101        let items = Dynamic::list(vec![Dynamic::from(1i64), Dynamic::from(2i64), Dynamic::from(3i64)]);
3102        assert_eq!(sum_list(&items), 3);
3103
3104        let data = dynamic::map!("x"=> 1i64, "y"=> 2i64);
3105        let compiled = vm.get_fn("vm_for_in_collection::count_map_keys", &[Type::Any])?;
3106        assert_eq!(compiled.ret_ty(), &Type::I64);
3107        let count_map_keys: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3108        assert_eq!(count_map_keys(&data), 2);
3109
3110        let compiled = vm.get_fn("vm_for_in_collection::for_in_list_works", &[Type::Any])?;
3111        assert_eq!(compiled.ret_ty(), &Type::Bool);
3112        let for_in_list_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3113        let empty = Dynamic::list(Vec::new());
3114        assert!(!for_in_list_works(&empty));
3115        assert!(for_in_list_works(&items));
3116
3117        let compiled = vm.get_fn("vm_for_in_collection::for_in_map_values_works", &[Type::Any])?;
3118        assert_eq!(compiled.ret_ty(), &Type::Bool);
3119        let for_in_map_values_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3120        let empty_map = dynamic::map!();
3121        assert!(!for_in_map_values_works(&empty_map));
3122        assert!(for_in_map_values_works(&data));
3123
3124        Ok(())
3125    }
3126
3127    #[test]
3128    fn concurrent_100_threads_no_memory_leak() -> anyhow::Result<()> {
3129        let vm = Vm::with_all()?;
3130        vm.import_code(
3131            "vm_stress",
3132            br#"
3133            pub fn heavy_alloc(idx: i64) {
3134                let items = [];
3135                let i = 0;
3136                while i < 50 {
3137                    items.push({
3138                        id: i + idx,
3139                        name: "item-" + i,
3140                        tags: ["tag-a", "tag-b", "tag-c"],
3141                        meta: {
3142                            created: 1234567890i64,
3143                            score: (i * 3.14f64) as i64,
3144                            extra: "prefix/" + i + "/" + idx
3145                        }
3146                    });
3147                    i = i + 1;
3148                }
3149                items
3150            }
3151
3152            pub fn string_concat_stress() {
3153                let i = 0;
3154                let result = "";
3155                while i < 200 {
3156                    result = result + "data-" + i + ",";
3157                    i = i + 1;
3158                }
3159                result
3160            }
3161            "#
3162            .to_vec(),
3163        )?;
3164
3165        let (heavy_ptr, _) = vm.get_fn_ptr("vm_stress::heavy_alloc", &[Type::I64])?;
3166        let (concat_ptr, _) = vm.get_fn_ptr("vm_stress::string_concat_stress", &[])?;
3167
3168        let threads: usize = std::thread::available_parallelism()
3169            .map(|n| n.get())
3170            .unwrap_or(4)
3171            .max(100);
3172        let iters_per_thread = 200;
3173        let total_calls = threads * iters_per_thread * 2;
3174
3175        let before = current_rss_kb();
3176        eprintln!("threads={threads} iters_per_thread={iters_per_thread} total_calls={total_calls} rss_before={before}KB");
3177
3178        // Round 1: first concurrent execution (arena warm-up)
3179        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
3180        let r1 = current_rss_kb();
3181        eprintln!("rss_after_round1={r1}KB");
3182
3183        // Round 2: should stabilize (no unbounded growth)
3184        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
3185        let r2 = current_rss_kb();
3186        eprintln!("rss_after_round2={r2}KB");
3187
3188        // Round 3: final check
3189        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
3190        let r3 = current_rss_kb();
3191        eprintln!("rss_after_round3={r3}KB");
3192
3193        // Growth should decrease (memory stabilizes after arena allocation in round 1)
3194        let d12 = r2.saturating_sub(r1);
3195        let d23 = r3.saturating_sub(r2);
3196        eprintln!("delta_r1→r2={d12}KB delta_r2→r3={d23}KB");
3197
3198        // After arena warm-up, subsequent rounds should not grow significantly.
3199        // Allow up to 20 MB growth per round (OS page cache, thread stack reuse, etc.)
3200        let max_growth_kb = 20 * 1024;
3201        assert!(
3202            d12 < max_growth_kb && d23 < max_growth_kb,
3203            "memory keeps growing between rounds: round1={r1} round2={r2} round3={r3} delta12={d12}KB delta23={d23}KB (max allowed={max_growth_kb}KB)"
3204        );
3205
3206        Ok(())
3207    }
3208
3209    fn run_stress_round(threads: usize, iters: usize, heavy_ptr: usize, concat_ptr: usize) {
3210        std::thread::scope(|scope| {
3211            let mut handles = Vec::with_capacity(threads);
3212            for t in 0..threads {
3213                let heavy_ptr = heavy_ptr;
3214                let concat_ptr = concat_ptr;
3215                handles.push(scope.spawn(move || {
3216                    let heavy_fn: extern "C" fn(i64) -> *const Dynamic =
3217                        unsafe { std::mem::transmute(heavy_ptr as *const u8) };
3218                    let concat_fn: extern "C" fn() -> *const Dynamic =
3219                        unsafe { std::mem::transmute(concat_ptr as *const u8) };
3220                    for i in 0..iters {
3221                        // heavy_alloc: drop returned value to free heap allocation
3222                        let r_ptr = heavy_fn((t * iters + i) as i64);
3223                        assert!(!r_ptr.is_null());
3224                        unsafe {
3225                            let r = &*r_ptr;
3226                            assert!(r.len() > 0, "heavy_alloc returned empty list");
3227                            drop(Box::from_raw(r_ptr as *mut Dynamic));
3228                        }
3229
3230                        // concat: same, drop returned value
3231                        let s_ptr = concat_fn();
3232                        assert!(!s_ptr.is_null());
3233                        unsafe {
3234                            let s = &*s_ptr;
3235                            assert!(s.len() > 0, "string_concat_stress returned empty");
3236                            drop(Box::from_raw(s_ptr as *mut Dynamic));
3237                        }
3238                    }
3239                }));
3240            }
3241            for h in handles {
3242                h.join().unwrap();
3243            }
3244        });
3245    }
3246
3247    fn current_rss_kb() -> u64 {
3248    // macOS: use ps
3249    let pid = std::process::id();
3250    if let Ok(output) = std::process::Command::new("ps")
3251        .args(["-p", &pid.to_string(), "-o", "rss="])
3252        .output()
3253    {
3254        if let Ok(s) = String::from_utf8(output.stdout) {
3255            if let Some(kb) = s.trim().parse::<u64>().ok() {
3256                return kb;
3257            }
3258        }
3259    }
3260    // Linux fallback: /proc/self/statm
3261    if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
3262        let parts: Vec<&str> = statm.split_whitespace().collect();
3263        if let Some(rss_pages) = parts.get(1).and_then(|s| s.parse::<u64>().ok()) {
3264            return rss_pages * 4; // pages (4KB) → KB
3265        }
3266    }
3267    0
3268}
3269
3270}