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 root_module;
24pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
25
26use std::sync::{Mutex, OnceLock, Weak};
27static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
28pub fn ptr_type() -> types::Type {
29    PTR_TYPE.get().cloned().unwrap()
30}
31
32pub fn get_type(ty: &Type) -> Result<types::Type> {
33    if ty.is_f64() {
34        Ok(types::F64)
35    } else if ty.is_f32() {
36        Ok(types::F32)
37    } else if ty.is_int() | ty.is_uint() {
38        match ty.width() {
39            1 => Ok(types::I8),
40            2 => Ok(types::I16),
41            4 => Ok(types::I32),
42            8 => Ok(types::I64),
43            _ => Err(anyhow!("非法类型 {:?}", ty)),
44        }
45    } else if let Type::Bool = ty {
46        Ok(types::I8)
47    } else {
48        Ok(ptr_type())
49    }
50}
51
52use compiler::Symbol;
53use cranelift::prelude::*;
54use cranelift_module::Module;
55
56pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
57    jit.add_all()?;
58    Ok(jit)
59}
60
61use std::sync::Arc;
62unsafe impl Send for JITRunTime {}
63unsafe impl Sync for JITRunTime {}
64
65pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
66    if context.is_null() {
67        return Err(anyhow!("VM context is null"));
68    }
69    let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
70    let vm = Vm { jit };
71    f(&vm)
72}
73
74fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
75    let def_id = jit.get_id(def)?;
76    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
77        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
78            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
79        }
80    }
81    Ok(())
82}
83
84fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
85    jit.add_module(module);
86    for (name, arg_tys, ret_ty, fn_ptr) in fns {
87        let full_name = format!("{}::{}", module, name);
88        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
89    }
90    jit.pop_module();
91    Ok(())
92}
93
94impl JITRunTime {
95    fn add_memory_runtime(&mut self) -> Result<()> {
96        self.native_symbols.write().unwrap().insert("__vm_scope_enter".to_string(), memory::scope_enter as *const () as usize);
97        self.native_symbols.write().unwrap().insert("__vm_scope_exit_void".to_string(), memory::scope_exit_void as *const () as usize);
98        self.native_symbols.write().unwrap().insert("__vm_scope_exit_dynamic".to_string(), memory::scope_exit_dynamic as *const () as usize);
99        self.native_symbols.write().unwrap().insert("__vm_scope_exit_bytes".to_string(), memory::scope_exit_bytes as *const () as usize);
100        self.native_symbols.write().unwrap().insert("__vm_struct_alloc".to_string(), native::struct_alloc as *const () as usize);
101        self.native_symbols.write().unwrap().insert("__vm_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
102
103        let void_sig = self.get_sig(&[], Type::Void)?;
104        self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
105        self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
106
107        let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
108        self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
109
110        let bytes_sig = self.get_sig(&[Type::Any, Type::I64], Type::Any)?;
111        self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
112
113        let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
114        self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
115
116        let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
117        self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
118        Ok(())
119    }
120
121    pub fn add_module(&mut self, name: &str) {
122        self.compiler.symbols.add_module(name.into());
123    }
124
125    pub fn pop_module(&mut self) {
126        self.compiler.symbols.pop_module();
127    }
128
129    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
130        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
131    }
132
133    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
134        match self.get_id(name) {
135            Ok(id) => Ok(id),
136            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
137        }
138    }
139
140    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
141        self.add_module(module);
142        let full_name = format!("{}::{}", module, name);
143        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
144        self.pop_module();
145        result
146    }
147
148    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> {
149        self.add_module(module);
150        let full_name = format!("{}::{}", module, name);
151        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
152        self.pop_module();
153        result
154    }
155
156    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
157        self.add_empty_type(def)?;
158        let full_name = format!("{}::{}", def, method);
159        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
160        add_method_field(self, def, method, id)?;
161        Ok(id)
162    }
163
164    pub fn add_std(&mut self) -> Result<()> {
165        self.add_module("std");
166        for (name, arg_tys, ret_ty, fn_ptr) in STD {
167            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
168        }
169        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
170        Ok(())
171    }
172
173    pub fn add_any(&mut self) -> Result<()> {
174        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
175            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
176            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
177        }
178        Ok(())
179    }
180
181    pub fn add_vec(&mut self) -> Result<()> {
182        self.add_empty_type("Vec")?;
183        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
184        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
185            if let Some(ctx) = ctx {
186                let width = ctx.builder.ins().iconst(types::I64, 4);
187                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
188                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
189                let dest = ctx.builder.ins().imul(args[2], width);
190                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
191                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
192                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
193                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
194                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
195            }
196            Err(anyhow!("无返回值"))
197        })?;
198
199        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
200            if let Some(ctx) = ctx {
201                let width = ctx.builder.ins().iconst(types::I64, 4);
202                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
203                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
204                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
205            } else {
206                Ok((None, Type::I32))
207            }
208        })?;
209        Ok(())
210    }
211
212    pub fn add_llm(&mut self) -> Result<()> {
213        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
214    }
215
216    pub fn add_root(&mut self) -> Result<()> {
217        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
218        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)?;
219        Ok(())
220    }
221
222    pub fn add_http(&mut self) -> Result<()> {
223        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
224    }
225
226    pub fn add_db(&mut self) -> Result<()> {
227        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
228    }
229
230    pub fn add_gpu(&mut self) -> Result<()> {
231        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
232    }
233
234    pub fn add_all(&mut self) -> Result<()> {
235        self.add_std()?;
236        self.add_any()?;
237        self.add_vec()?;
238        self.add_llm()?;
239        self.add_root()?;
240        self.add_http()?;
241        self.add_db()?;
242        self.add_gpu()?;
243        Ok(())
244    }
245}
246
247#[derive(Clone)]
248pub struct Vm {
249    jit: Arc<Mutex<JITRunTime>>,
250}
251
252#[derive(Clone)]
253pub struct CompiledFn {
254    ptr: usize,
255    ret: Type,
256    owner: Vm,
257}
258
259impl CompiledFn {
260    pub fn ptr(&self) -> *const u8 {
261        self.ptr as *const u8
262    }
263
264    pub fn ret_ty(&self) -> &Type {
265        &self.ret
266    }
267
268    pub fn owner(&self) -> &Vm {
269        &self.owner
270    }
271}
272
273impl Vm {
274    pub fn new() -> Self {
275        dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
276        let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
277        {
278            let mut guard = jit.lock().unwrap();
279            guard.set_owner(Arc::downgrade(&jit));
280            guard.add_memory_runtime().expect("register VM memory runtime");
281        }
282        Self { jit }
283    }
284
285    pub fn with_all() -> Result<Self> {
286        let vm = Self::new();
287        vm.add_all()?;
288        Ok(vm)
289    }
290
291    pub fn add_module(&self, name: &str) {
292        self.jit.lock().unwrap().add_module(name)
293    }
294
295    pub fn pop_module(&self) {
296        self.jit.lock().unwrap().pop_module()
297    }
298
299    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
300        self.jit.lock().unwrap().add_type(name, ty, is_pub)
301    }
302
303    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
304        self.jit.lock().unwrap().add_empty_type(name)
305    }
306
307    pub fn add_std(&self) -> Result<()> {
308        self.jit.lock().unwrap().add_std()
309    }
310
311    pub fn add_any(&self) -> Result<()> {
312        self.jit.lock().unwrap().add_any()
313    }
314
315    pub fn add_vec(&self) -> Result<()> {
316        self.jit.lock().unwrap().add_vec()
317    }
318
319    pub fn add_llm(&self) -> Result<()> {
320        self.jit.lock().unwrap().add_llm()
321    }
322
323    pub fn add_root(&self) -> Result<()> {
324        self.jit.lock().unwrap().add_root()
325    }
326
327    pub fn add_http(&self) -> Result<()> {
328        self.jit.lock().unwrap().add_http()
329    }
330
331    pub fn add_db(&self) -> Result<()> {
332        self.jit.lock().unwrap().add_db()
333    }
334
335    pub fn add_gpu(&self) -> Result<()> {
336        self.jit.lock().unwrap().add_gpu()
337    }
338
339    pub fn add_all(&self) -> Result<()> {
340        self.jit.lock().unwrap().add_all()
341    }
342
343    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
344        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
345    }
346
347    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
348        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
349    }
350
351    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
352        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
353    }
354
355    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> {
356        self.jit.lock().unwrap().add_inline(name, args, ret, f)
357    }
358
359    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
360        self.jit.lock().unwrap().import_code(name, code)
361    }
362
363    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
364        self.jit.lock().unwrap().compiler.import_file(name, path)?;
365        Ok(())
366    }
367
368    pub fn import(&self, name: &str, path: &str) -> Result<()> {
369        if root::contains(path) {
370            let code = root::get(path).unwrap();
371            if code.is_str() {
372                self.import_code(name, code.as_str().as_bytes().to_vec())
373            } else {
374                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
375            }
376        } else {
377            self.import_file(name, path)
378        }
379    }
380
381    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
382        self.jit.lock().unwrap().get_type(name, arg_tys)
383    }
384
385    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
386        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
387    }
388
389    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
390        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
391        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
392    }
393
394    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
395        self.jit.lock().unwrap().load(code, arg_name)
396    }
397
398    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
399        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
400    }
401
402    pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
403        let jit = self.jit.lock().unwrap();
404        GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
405    }
406
407    pub fn disassemble(&self, name: &str) -> Result<String> {
408        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
409    }
410
411    #[cfg(feature = "ir-disassembly")]
412    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
413        self.jit.lock().unwrap().disassemble_ir(name)
414    }
415}
416
417impl Default for Vm {
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::Vm;
426    use dynamic::{Dynamic, ToJson, Type};
427
428    extern "C" fn math_double(value: i64) -> i64 {
429        value * 2
430    }
431
432    #[test]
433    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
434        let vm = Vm::new();
435        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
436        vm.import_code(
437            "vm_dynamic_native",
438            br#"
439            pub fn run(value: i64) {
440                math::double(value)
441            }
442            "#
443            .to_vec(),
444        )?;
445
446        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
447        assert_eq!(compiled.ret_ty(), &Type::I64);
448        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
449        assert_eq!(run(21), 42);
450        Ok(())
451    }
452
453    #[test]
454    fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
455        let vm = Vm::with_all()?;
456        vm.import_code(
457            "vm_nested_struct_return_field",
458            br#"
459            pub struct Inner {
460                value: i64,
461            }
462
463            pub struct RoleMini {
464                inner: Inner,
465                hp: i64,
466            }
467
468            pub struct TeamMini {
469                role: RoleMini,
470            }
471
472            pub struct BigSummary {
473                winner: i64,
474                loser: i64,
475            }
476
477            pub fn make_big_with_team(team: TeamMini) {
478                let score = team.role.inner.value;
479                BigSummary{winner: score, loser: 0}
480            }
481
482            pub fn read_team_winner_direct() {
483                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
484                make_big_with_team(team).winner
485            }
486
487            pub fn read_team_winner_bound() {
488                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
489                let summary = make_big_with_team(team);
490                summary.winner
491            }
492            "#
493            .to_vec(),
494        )?;
495
496        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
497        assert_eq!(compiled.ret_ty(), &Type::I64);
498        let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
499        assert_eq!(direct(), 9);
500
501        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
502        assert_eq!(compiled.ret_ty(), &Type::I64);
503        let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
504        assert_eq!(bound(), 9);
505        Ok(())
506    }
507
508    #[test]
509    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
510        let vm = Vm::with_all()?;
511        vm.import_code(
512            "vm_any_push_reused_value",
513            br#"
514            pub fn run() {
515                let role_id = "acct_role_2";
516                let updated = [];
517                updated.push(role_id);
518                {
519                    ok: true,
520                    user_id: role_id,
521                    first: updated.get_idx(0)
522                }
523            }
524            "#
525            .to_vec(),
526        )?;
527
528        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
529        assert_eq!(compiled.ret_ty(), &Type::Any);
530        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
531        let result = unsafe { &*run() };
532        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
533        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
534        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
535        Ok(())
536    }
537
538    #[test]
539    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
540        let vm = Vm::with_all()?;
541        vm.import_code(
542            "vm_string_compare_any",
543            br#"
544            pub fn any_ne_empty(chat_path) {
545                chat_path != ""
546            }
547            "#
548            .to_vec(),
549        )?;
550
551        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
552        assert_eq!(compiled.ret_ty(), &Type::Bool);
553
554        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
555        let empty = Dynamic::from("");
556        let non_empty = Dynamic::from("chat");
557
558        assert!(!any_ne_empty(&empty));
559        assert!(any_ne_empty(&non_empty));
560        Ok(())
561    }
562
563    #[test]
564    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
565        let vm = Vm::with_all()?;
566        vm.import_code(
567            "vm_bool_compare",
568            br#"
569            pub fn eq_true(value: bool) {
570                value == true
571            }
572
573            pub fn ne_false(value: bool) {
574                value != false
575            }
576
577            pub fn literal_left(value: bool) {
578                true == value
579            }
580
581            pub fn eq_pair(left: bool, right: bool) {
582                left == right
583            }
584
585            pub fn logic_pair(left: bool, right: bool) {
586                (left && right) || (left == true && right != false)
587            }
588            "#
589            .to_vec(),
590        )?;
591
592        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
593        assert_eq!(compiled.ret_ty(), &Type::Bool);
594        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
595        assert!(eq_true(true));
596        assert!(!eq_true(false));
597
598        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
599        assert_eq!(compiled.ret_ty(), &Type::Bool);
600        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
601        assert!(ne_false(true));
602        assert!(!ne_false(false));
603
604        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
605        assert_eq!(compiled.ret_ty(), &Type::Bool);
606        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
607        assert!(literal_left(true));
608        assert!(!literal_left(false));
609
610        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
611        assert_eq!(compiled.ret_ty(), &Type::Bool);
612        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
613        assert!(eq_pair(true, true));
614        assert!(eq_pair(false, false));
615        assert!(!eq_pair(true, false));
616        assert!(!eq_pair(false, true));
617
618        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
619        assert_eq!(compiled.ret_ty(), &Type::Bool);
620        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
621        assert!(logic_pair(true, true));
622        assert!(!logic_pair(true, false));
623        assert!(!logic_pair(false, true));
624        assert!(!logic_pair(false, false));
625        Ok(())
626    }
627
628    #[test]
629    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
630        let vm = Vm::with_all()?;
631        vm.import_code(
632            "vm_parenthesized_method_call",
633            br#"
634            pub fn run(value) {
635                (value + 2).to_i64()
636            }
637            "#
638            .to_vec(),
639        )?;
640
641        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
642        assert_eq!(compiled.ret_ty(), &Type::I64);
643        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
644        let value = Dynamic::from(40i64);
645
646        assert_eq!(run(&value), 42);
647        Ok(())
648    }
649
650    #[test]
651    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
652        let vm = Vm::with_all()?;
653        vm.import_code(
654            "vm_any_keys",
655            br#"
656            pub fn map_keys(value) {
657                let keys = value.keys();
658                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
659            }
660
661            pub fn non_map_keys(value) {
662                value.keys().len() == 0
663            }
664            "#
665            .to_vec(),
666        )?;
667
668        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
669        assert_eq!(compiled.ret_ty(), &Type::Bool);
670        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
671        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
672        assert!(map_keys(&value));
673
674        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
675        assert_eq!(compiled.ret_ty(), &Type::Bool);
676        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
677        let value = Dynamic::from("alpha");
678        assert!(non_map_keys(&value));
679        Ok(())
680    }
681
682    #[test]
683    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
684        let vm = Vm::with_all()?;
685        vm.import_code(
686            "vm_string_compare_imm",
687            br#"
688            pub fn int_eq_str(value: i64) {
689                value == "42"
690            }
691
692            pub fn int_to_str(value: i64) {
693                value + ""
694            }
695            "#
696            .to_vec(),
697        )?;
698
699        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
700        assert_eq!(compiled.ret_ty(), &Type::Bool);
701
702        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
703
704        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
705        assert_eq!(compiled.ret_ty(), &Type::Any);
706        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
707        let text = int_to_str(42);
708        assert_eq!(unsafe { &*text }.as_str(), "42");
709
710        assert!(int_eq_str(42));
711        assert!(!int_eq_str(7));
712        Ok(())
713    }
714
715    #[test]
716    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
717        let vm = Vm::with_all()?;
718        vm.import_code(
719            "vm_string_concat_integer",
720            br#"
721            pub fn idx_key(idx: i64) {
722                "" + idx
723            }
724
725            pub fn level_text(level: i64) {
726                "" + level + " level"
727            }
728
729            pub fn gold_text(currency) {
730                "" + currency.gold
731            }
732            "#
733            .to_vec(),
734        )?;
735
736        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
737        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
738        let result = unsafe { &*idx_key(7) };
739        assert_eq!(result.as_str(), "7");
740
741        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
742        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
743        let result = unsafe { &*level_text(12) };
744        assert_eq!(result.as_str(), "12 level");
745
746        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
747        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
748        let currency = dynamic::map!("gold"=> 345i64);
749        let result = unsafe { &*gold_text(&currency) };
750        assert_eq!(result.as_str(), "345");
751        Ok(())
752    }
753
754    #[test]
755    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
756        let vm = Vm::with_all()?;
757        vm.import_code(
758            "vm_string_concat_to_i64",
759            br#"
760            pub fn run(idx: i64) {
761                ("" + idx) as i64
762            }
763            "#
764            .to_vec(),
765        )?;
766
767        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
768        assert_eq!(compiled.ret_ty(), &Type::I64);
769        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
770        assert_eq!(run(7), 0);
771        Ok(())
772    }
773
774    #[test]
775    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
776        let vm = Vm::with_all()?;
777        vm.import_code(
778            "vm_return_integer_widths",
779            br#"
780            pub fn selected(flag, slot) {
781                if flag {
782                    return slot;
783                }
784                0
785            }
786            "#
787            .to_vec(),
788        )?;
789
790        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
791        assert_eq!(compiled.ret_ty(), &Type::I64);
792        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
793
794        assert_eq!(selected(true, 7), 7);
795        assert_eq!(selected(false, 7), 0);
796        Ok(())
797    }
798
799    #[test]
800    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
801        let vm = Vm::with_all()?;
802        vm.import_code(
803            "vm_root_contains_condition",
804            br#"
805            pub fn exists(user_id) {
806                if root::contains("redis/user/" + user_id) {
807                    return 1;
808                }
809                0
810            }
811            "#
812            .to_vec(),
813        )?;
814
815        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
816        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
817        assert_eq!(compiled.ret_ty(), &Type::I32);
818        Ok(())
819    }
820
821    #[test]
822    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
823        let vm = Vm::with_all()?;
824        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
825        vm.import_code(
826            "vm_root_add_map_print",
827            br#"
828            pub fn run() {
829                print(root::add_map("local/world_handlers/til_map_novicevillage"));
830            }
831            "#
832            .to_vec(),
833        )?;
834
835        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
836        assert!(compiled.ret_ty().is_void());
837        Ok(())
838    }
839
840    #[test]
841    fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
842        let vm = Vm::with_all()?;
843        vm.import_code(
844            "vm_std_log",
845            br#"
846            pub fn run(value) {
847                log({ ok: true, value: value });
848            }
849            "#
850            .to_vec(),
851        )?;
852
853        let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
854        assert!(compiled.ret_ty().is_void());
855        let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
856        let value = Dynamic::from(7i64);
857        run(&value);
858        Ok(())
859    }
860
861    #[test]
862    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
863        let vm = Vm::with_all()?;
864        vm.import_code(
865            "vm_unary_not_any_loop_var",
866            br#"
867            pub fn count_missing(flags) {
868                let missing = 0;
869                for exists in flags {
870                    if !exists {
871                        missing = missing + 1;
872                    }
873                }
874                missing
875            }
876            "#
877            .to_vec(),
878        )?;
879
880        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
881        assert_eq!(compiled.ret_ty(), &Type::I32);
882        Ok(())
883    }
884
885    #[test]
886    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
887        let vm = Vm::with_all()?;
888        vm.import_code(
889            "vm_semicolon_tail_void",
890            br#"
891            pub fn send_role_select(idx, account_id, selected_slot) {
892                root::send("local/ui/send_dialog", {
893                    idx: idx,
894                    account_id: account_id,
895                    selected_slot: selected_slot
896                });
897            }
898            "#
899            .to_vec(),
900        )?;
901
902        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
903        assert_eq!(compiled.ret_ty(), &Type::Void);
904        Ok(())
905    }
906
907    #[test]
908    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
909        let vm = Vm::with_all()?;
910        vm.import_code(
911            "vm_bare_return_conflict",
912            br#"
913            pub fn run(flag) {
914                if flag {
915                    return;
916                }
917                1
918            }
919            "#
920            .to_vec(),
921        )?;
922
923        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
924            Ok(_) => panic!("expected mismatched return types to fail"),
925            Err(err) => err,
926        };
927        assert!(format!("{err:#}").contains("返回类型不一致"));
928        Ok(())
929    }
930
931    #[test]
932    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
933        let vm = Vm::with_all()?;
934        vm.import_code(
935            "vm_root_get_dynamic_concat",
936            br#"
937            pub fn get_action(req) {
938                root::get("local/game/panel_actions/" + req.idx)
939            }
940            "#
941            .to_vec(),
942        )?;
943
944        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
945        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
946        assert_eq!(compiled.ret_ty(), &Type::Any);
947        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
948        let req = dynamic::map!("idx"=> 7i64);
949        let result = unsafe { &*get_action(&req) };
950
951        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
952        Ok(())
953    }
954
955    #[test]
956    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
957        let vm = Vm::with_all()?;
958        vm.import_code(
959            "vm_registered_panel_action",
960            br#"
961            pub fn panel_action(req) {
962                root::get("local/game/panel_actions/" + req.idx)
963            }
964
965            pub fn register() {
966                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
967            }
968            "#
969            .to_vec(),
970        )?;
971
972        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
973        assert_eq!(compiled.ret_ty(), &Type::Bool);
974        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
975        assert!(register());
976        Ok(())
977    }
978
979    #[test]
980    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
981        let vm = Vm::with_all()?;
982        vm.import_code(
983            "vm_registered_string_concat",
984            br#"
985            pub fn send_panel(idx: i64) {
986                let idx_key = "" + idx;
987                idx_key
988            }
989            "#
990            .to_vec(),
991        )?;
992
993        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
994        Ok(())
995    }
996
997    #[test]
998    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
999        let vm = Vm::with_all()?;
1000        vm.import_code(
1001            "vm_public_hotspots",
1002            br#"
1003            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1004                {
1005                    path: action_map_path,
1006                    panel_id: panel_id,
1007                    action_id: action_id,
1008                    id: hotspot.id
1009                }
1010            }
1011
1012            pub fn public_hotspots(idx, panel_id, hotspots) {
1013                let idx_key = "" + idx;
1014                let action_map_path = "local/game/panel_actions/" + idx_key;
1015
1016                let existing_action_map = root::get(action_map_path);
1017                if !existing_action_map.is_map() {
1018                    root::add_map(action_map_path);
1019                }
1020
1021                if hotspots.is_map() {
1022                    let public_items = {};
1023                    for action_id in hotspots.keys() {
1024                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1025                    }
1026                    return public_items;
1027                }
1028
1029                let public_items = [];
1030                let i = 0;
1031                while i < hotspots.len() {
1032                    let hotspot = hotspots.get_idx(i);
1033                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1034                    public_items.push(item);
1035                    i = i + 1;
1036                }
1037
1038                public_items
1039            }
1040            "#
1041            .to_vec(),
1042        )?;
1043
1044        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
1045        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
1046        Ok(())
1047    }
1048
1049    #[test]
1050    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
1051        let vm = Vm::with_all()?;
1052        vm.import_code(
1053            "vm_send_panel_public_hotspots",
1054            br#"
1055            pub fn ok(value) {
1056                value
1057            }
1058
1059            pub fn panel_from_node(req) {
1060                {
1061                    panel_id: req.panel_id,
1062                    hotspots: req.hotspots
1063                }
1064            }
1065
1066            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1067                {
1068                    path: action_map_path,
1069                    panel_id: panel_id,
1070                    action_id: action_id,
1071                    id: hotspot.id
1072                }
1073            }
1074
1075            pub fn public_hotspots(idx, panel_id, hotspots) {
1076                let idx_key = "" + idx;
1077                let action_map_path = "local/game/panel_actions/" + idx_key;
1078
1079                let existing_action_map = root::get(action_map_path);
1080                if !existing_action_map.is_map() {
1081                    root::add_map(action_map_path);
1082                }
1083
1084                if hotspots.is_map() {
1085                    let public_items = {};
1086                    for action_id in hotspots.keys() {
1087                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1088                    }
1089                    return public_items;
1090                }
1091
1092                let public_items = [];
1093                let i = 0;
1094                while i < hotspots.len() {
1095                    let hotspot = hotspots.get_idx(i);
1096                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1097                    public_items.push(item);
1098                    i = i + 1;
1099                }
1100
1101                public_items
1102            }
1103
1104            pub fn send_panel(req) {
1105                let panel = req.panel;
1106                if !panel.is_map() {
1107                    panel = panel_from_node(req);
1108                }
1109                if !panel.is_map() {
1110                    return ok({
1111                        id: 4,
1112                        type: "panel_rejected",
1113                        reason: "invalid panel"
1114                    });
1115                }
1116                panel.id = 4;
1117                panel.idx = req.idx;
1118                if !panel.contains("type") {
1119                    panel.type = "panel";
1120                }
1121                if panel.contains("hotspots") {
1122                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1123                }
1124                root::send_idx("local/ws", req.idx, panel);
1125                ok({
1126                    id: 4,
1127                    type: "panel",
1128                    panel_id: panel.panel_id
1129                })
1130            }
1131            "#
1132            .to_vec(),
1133        )?;
1134
1135        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1136        assert_eq!(compiled.ret_ty(), &Type::Any);
1137        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1138        let req = dynamic::map!(
1139            "idx"=> 7i64,
1140            "panel"=> dynamic::map!(
1141                "panel_id"=> "main",
1142                "hotspots"=> dynamic::map!(
1143                    "open"=> dynamic::map!("id"=> "open")
1144                )
1145            )
1146        );
1147        let result = unsafe { &*send_panel(&req) };
1148
1149        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1150        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1151        Ok(())
1152    }
1153
1154    #[test]
1155    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1156        let vm = Vm::with_all()?;
1157        vm.import_code(
1158            "vm_string_concat_map_key",
1159            br##"
1160            pub fn write_action(action_map, panel_id, action_id, action) {
1161                action_map[panel_id + "#" + action_id] = action;
1162                action_map[panel_id + "#" + action_id]
1163            }
1164            "##
1165            .to_vec(),
1166        )?;
1167
1168        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1169        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1170        let action_map = dynamic::map!();
1171        let panel_id: Dynamic = "panel".into();
1172        let action_id: Dynamic = "open".into();
1173        let action = dynamic::map!("id"=> "open");
1174
1175        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1176
1177        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1178        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()));
1179        Ok(())
1180    }
1181
1182    #[test]
1183    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1184        let vm = Vm::with_all()?;
1185        vm.import_code(
1186            "vm_get_key_string_concat_key",
1187            br##"
1188            pub fn read_action(action_map, panel_id, action_id) {
1189                let action_key = panel_id + "#" + action_id;
1190                action_map.get_key(action_key)
1191            }
1192            "##
1193            .to_vec(),
1194        )?;
1195
1196        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1197        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1198        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1199        let panel_id: Dynamic = "panel".into();
1200        let action_id: Dynamic = "open".into();
1201
1202        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1203
1204        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1205        Ok(())
1206    }
1207
1208    #[test]
1209    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1210        let vm = Vm::with_all()?;
1211        vm.import_code(
1212            "vm_get_key_helper_string_key",
1213            br##"
1214            pub fn make_action_key(panel_id, action_id) {
1215                panel_id + "#" + action_id
1216            }
1217
1218            pub fn read_action(action_map, panel_id, action_id) {
1219                let action_key = make_action_key(panel_id, action_id);
1220                let action = action_map.get_key(action_key);
1221                action
1222            }
1223            "##
1224            .to_vec(),
1225        )?;
1226
1227        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1228        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1229        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1230        let panel_id: Dynamic = "panel".into();
1231        let action_id: Dynamic = "open".into();
1232
1233        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1234
1235        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1236        Ok(())
1237    }
1238
1239    #[test]
1240    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1241        let vm = Vm::with_all()?;
1242        vm.import_code(
1243            "vm_del_key_string_key",
1244            br##"
1245            pub fn remove_action(action_map, panel_id, action_id) {
1246                let action_key = panel_id + "#" + action_id;
1247                let removed = action_map.del_key(action_key);
1248                [removed, action_map.get_key(action_key)]
1249            }
1250            "##
1251            .to_vec(),
1252        )?;
1253
1254        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1255        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1256        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1257        let panel_id: Dynamic = "panel".into();
1258        let action_id: Dynamic = "open".into();
1259
1260        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1261
1262        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1263        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1264        assert!(action_map.get_dynamic("panel#open").is_none());
1265        Ok(())
1266    }
1267
1268    #[test]
1269    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1270        let vm = Vm::with_all()?;
1271        vm.import_code(
1272            "vm_dynamic_field_or",
1273            r#"
1274            pub fn next_or_start() {
1275                let choice = {
1276                    label: "颜色",
1277                    next: "color"
1278                };
1279                choice.next || "start"
1280            }
1281
1282            pub fn direct_next() {
1283                let choice = {
1284                    label: "颜色",
1285                    next: "color"
1286                };
1287                choice.next
1288            }
1289
1290            pub fn bracket_next() {
1291                let choice = {
1292                    label: "颜色",
1293                    next: "color"
1294                };
1295                choice["next"]
1296            }
1297
1298            pub fn assigned_preview() {
1299                let choice = {
1300                    next: "tax_free"
1301                };
1302                choice.preview = choice.next || "start";
1303                choice
1304            }
1305            "#
1306            .as_bytes()
1307            .to_vec(),
1308        )?;
1309
1310        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1311        assert_eq!(compiled.ret_ty(), &Type::Any);
1312        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1313        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1314
1315        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1316        assert_eq!(compiled.ret_ty(), &Type::Any);
1317        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1318        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1319
1320        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
1321        assert_eq!(compiled.ret_ty(), &Type::Any);
1322        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1323        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
1324
1325        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
1326        assert_eq!(compiled.ret_ty(), &Type::Any);
1327        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1328        let choice = unsafe { &*assigned_preview() };
1329        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
1330        Ok(())
1331    }
1332
1333    #[test]
1334    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1335        let vm = Vm::with_all()?;
1336        vm.import_code(
1337            "vm_if_empty_object_branch",
1338            r#"
1339            pub fn first_note(steps) {
1340                let first = if steps.len() > 0 { steps[0] } else { {} };
1341                let first_note = first.note || "fallback";
1342                first_note
1343            }
1344
1345            pub fn first_ja(steps) {
1346                let first = if steps.len() > 0 { steps[0] } else { {} };
1347                first.ja || "すみません"
1348            }
1349
1350            pub fn assign_first_note(steps) {
1351                let first = {};
1352                first = if steps.len() > 0 { steps[0] } else { {} };
1353                first.note || "fallback"
1354            }
1355            "#
1356            .as_bytes()
1357            .to_vec(),
1358        )?;
1359
1360        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1361        assert_eq!(compiled.ret_ty(), &Type::Any);
1362        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1363
1364        let empty_steps = Dynamic::list(Vec::new());
1365        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1366
1367        let mut step = std::collections::BTreeMap::new();
1368        step.insert("note".into(), "hello".into());
1369        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1370        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1371
1372        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1373        assert_eq!(compiled.ret_ty(), &Type::Any);
1374        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1375        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1376
1377        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1378        assert_eq!(compiled.ret_ty(), &Type::Any);
1379        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1380        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1381        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1382        Ok(())
1383    }
1384
1385    #[test]
1386    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1387        let vm = Vm::with_all()?;
1388        vm.import_code(
1389            "vm_tail_list_literal",
1390            r#"
1391            pub fn numbers() {
1392                [1, 2, 3]
1393            }
1394
1395            pub fn maps() {
1396                [
1397                    {note: "first"},
1398                    {note: "second"}
1399                ]
1400            }
1401
1402            pub fn object_with_maps() {
1403                {
1404                    steps: [
1405                        {note: "first"},
1406                        {note: "second"}
1407                    ]
1408                }
1409            }
1410
1411            pub fn return_maps() {
1412                return [
1413                    {note: "first"},
1414                    {note: "second"}
1415                ];
1416            }
1417
1418            pub fn return_maps_without_semicolon() {
1419                return [
1420                    {note: "first"},
1421                    {note: "second"}
1422                ]
1423            }
1424
1425            pub fn tail_bare_variable() {
1426                let value = [
1427                    {note: "first"},
1428                    {note: "second"}
1429                ];
1430                value
1431            }
1432
1433            pub fn return_bare_variable_without_semicolon() {
1434                let value = [
1435                    {note: "first"},
1436                    {note: "second"}
1437                ];
1438                return value
1439            }
1440
1441            pub fn tail_object_variable() {
1442                let result = {
1443                    steps: [
1444                        {note: "first"},
1445                        {note: "second"}
1446                    ]
1447                };
1448                result
1449            }
1450            "#
1451            .as_bytes()
1452            .to_vec(),
1453        )?;
1454
1455        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1456        assert_eq!(compiled.ret_ty(), &Type::Any);
1457        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1458        let result = unsafe { &*numbers() };
1459        assert_eq!(result.len(), 3);
1460        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1461
1462        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1463        assert_eq!(compiled.ret_ty(), &Type::Any);
1464        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1465        let result = unsafe { &*maps() };
1466        assert_eq!(result.len(), 2);
1467        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1468
1469        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1470        assert_eq!(compiled.ret_ty(), &Type::Any);
1471        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1472        let result = unsafe { &*object_with_maps() };
1473        let steps = result.get_dynamic("steps").expect("steps");
1474        assert_eq!(steps.len(), 2);
1475        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1476
1477        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1478        assert_eq!(compiled.ret_ty(), &Type::Any);
1479        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1480        let result = unsafe { &*return_maps() };
1481        assert_eq!(result.len(), 2);
1482        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1483
1484        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1485        assert_eq!(compiled.ret_ty(), &Type::Any);
1486        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1487        let result = unsafe { &*return_maps_without_semicolon() };
1488        assert_eq!(result.len(), 2);
1489        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1490
1491        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1492        assert_eq!(compiled.ret_ty(), &Type::Any);
1493        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1494        let result = unsafe { &*tail_bare_variable() };
1495        assert_eq!(result.len(), 2);
1496        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1497
1498        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1499        assert_eq!(compiled.ret_ty(), &Type::Any);
1500        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1501        let result = unsafe { &*return_bare_variable_without_semicolon() };
1502        assert_eq!(result.len(), 2);
1503        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1504
1505        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1506        assert_eq!(compiled.ret_ty(), &Type::Any);
1507        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1508        let result = unsafe { &*tail_object_variable() };
1509        let steps = result.get_dynamic("steps").expect("steps");
1510        assert_eq!(steps.len(), 2);
1511        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1512        Ok(())
1513    }
1514
1515    #[test]
1516    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1517        let vm = Vm::with_all()?;
1518        vm.import_code(
1519            "vm_returned_list_get_idx",
1520            r#"
1521            pub fn ids() {
1522                [
1523                    "base",
1524                    "2",
1525                    "3"
1526                ]
1527            }
1528
1529            pub fn combinations() {
1530                let result = [];
1531                let values = ids();
1532                let idx = 0;
1533                while idx < values.len() {
1534                    result.push(values.get_idx(idx));
1535                    idx = idx + 1;
1536                }
1537                result
1538            }
1539            "#
1540            .as_bytes()
1541            .to_vec(),
1542        )?;
1543
1544        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1545        assert_eq!(compiled.ret_ty(), &Type::Any);
1546        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1547        let result = unsafe { &*combinations() };
1548
1549        assert_eq!(result.len(), 3);
1550        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1551        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1552        Ok(())
1553    }
1554
1555    #[test]
1556    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1557        fn extra_page_literal(depth: usize) -> String {
1558            let mut value = "{leaf: \"done\"}".to_string();
1559            for idx in 0..depth {
1560                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1561            }
1562            value
1563        }
1564
1565        let extra = extra_page_literal(48);
1566        let code = format!(
1567            r#"
1568            pub fn script() {{
1569                return [
1570                    {{ja: "一つ目", note: "first", extra: {extra}}},
1571                    {{ja: "二つ目", note: "second", extra: {extra}}},
1572                    {{ja: "三つ目", note: "third", extra: {extra}}}
1573                ]
1574            }}
1575            "#
1576        );
1577
1578        let vm = Vm::with_all()?;
1579        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1580        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1581        assert_eq!(compiled.ret_ty(), &Type::Any);
1582        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1583        let result = unsafe { &*script() };
1584        assert_eq!(result.len(), 3);
1585        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1586        Ok(())
1587    }
1588
1589    #[test]
1590    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1591        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1592        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1593        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1594
1595        let vm1 = Vm::with_all()?;
1596        vm1.import_code(
1597            "vm_import_owner",
1598            format!(
1599                r#"
1600                pub fn run() {{
1601                    import("vm_imported_owner", "{module_path}");
1602                }}
1603                "#
1604            )
1605            .into_bytes(),
1606        )?;
1607        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1608
1609        let vm2 = Vm::with_all()?;
1610        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1611        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1612
1613        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1614        run();
1615
1616        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1617        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1618        Ok(())
1619    }
1620
1621    #[test]
1622    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1623        let vm = Vm::with_all()?;
1624        vm.import_code(
1625            "vm_object_last_call_field",
1626            r#"
1627            pub fn extra_page() {
1628                {
1629                    title: "extra",
1630                    pages: [
1631                        {note: "nested"}
1632                    ]
1633                }
1634            }
1635
1636            pub fn data() {
1637                return [
1638                    {
1639                        note: "first",
1640                        choices: ["a", "b"],
1641                        extras: extra_page()
1642                    },
1643                    {
1644                        note: "second",
1645                        choices: ["c"],
1646                        extras: extra_page()
1647                    }
1648                ]
1649            }
1650            "#
1651            .as_bytes()
1652            .to_vec(),
1653        )?;
1654
1655        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1656        assert_eq!(compiled.ret_ty(), &Type::Any);
1657        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1658        let result = unsafe { &*data() };
1659        assert_eq!(result.len(), 2);
1660        let first = result.get_idx(0).expect("first step");
1661        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1662        Ok(())
1663    }
1664
1665    #[test]
1666    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1667        let vm = Vm::with_all()?;
1668        vm.import_code(
1669            "vm_gpu_layout",
1670            br#"
1671            pub struct Params {
1672                a: u32,
1673                b: u32,
1674                c: u32,
1675            }
1676            "#
1677            .to_vec(),
1678        )?;
1679
1680        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1681        assert_eq!(layout.size, 16);
1682        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1683
1684        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1685        let bytes = layout.pack_map(&value)?;
1686        assert_eq!(bytes.len(), 16);
1687        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1688        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1689        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1690
1691        let read = layout.unpack_map(&bytes)?;
1692        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1693        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1694        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1695        Ok(())
1696    }
1697
1698    #[test]
1699    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1700        let vm = Vm::with_all()?;
1701        vm.import_code(
1702            "vm_root_clone_bridge",
1703            br#"
1704            pub fn add_then_reuse(arg) {
1705                let user = {
1706                    address: "test-wallet",
1707                    points: 20
1708                };
1709                root::add("local/root-clone-bridge-user", user);
1710                user.points = user.points - 7;
1711                root::add("local/root-clone-bridge-user", user);
1712                {
1713                    user: user,
1714                    points: user.points
1715                }
1716            }
1717
1718            pub fn clone_then_mutate(arg) {
1719                let user = {
1720                    profile: {
1721                        points: 20
1722                    }
1723                };
1724                let copied = user.clone();
1725                copied.profile.points = 13;
1726                user
1727            }
1728            "#
1729            .to_vec(),
1730        )?;
1731
1732        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1733        assert_eq!(compiled.ret_ty(), &Type::Any);
1734        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1735        let arg = Dynamic::Null;
1736        let result = add_then_reuse(&arg);
1737        let result = unsafe { &*result };
1738
1739        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1740        let mut json = String::new();
1741        result.to_json(&mut json);
1742        assert!(json.contains("\"points\": 13"));
1743
1744        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
1745        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
1746        let result = clone_then_mutate(&arg);
1747        let result = unsafe { &*result };
1748        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
1749        Ok(())
1750    }
1751
1752    struct CounterForTypedReceiver {
1753        value: i64,
1754    }
1755
1756    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
1757        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
1758    }
1759
1760    struct NavMapForFunctionArg;
1761
1762    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
1763        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
1764    }
1765
1766    #[test]
1767    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
1768        let vm = Vm::with_all()?;
1769        vm.add_empty_type("Counter")?;
1770        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
1771        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
1772        vm.import_code(
1773            "vm_typed_receiver_method",
1774            br#"
1775            pub fn run(value) {
1776                value::<Counter>::get()
1777            }
1778            "#
1779            .to_vec(),
1780        )?;
1781
1782        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
1783        assert_eq!(compiled.ret_ty(), &Type::I64);
1784        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1785        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
1786
1787        assert_eq!(run(&value), 42);
1788        Ok(())
1789    }
1790
1791    #[test]
1792    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1793        let vm = Vm::with_all()?;
1794        vm.add_empty_type("NavMap")?;
1795        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1796        vm.import_code(
1797            "vm_native_custom_arg",
1798            br#"
1799            pub fn add_nav_spawns(world, navmap) {
1800                navmap
1801            }
1802
1803            pub fn run(world) {
1804                let navmap = NavMap::new();
1805                let with_spawns = add_nav_spawns(world, navmap);
1806                with_spawns
1807            }
1808            "#
1809            .to_vec(),
1810        )?;
1811
1812        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
1813        assert_eq!(compiled.ret_ty(), &Type::Any);
1814        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1815        let world = Dynamic::Null;
1816        let result = run(&world);
1817        let result = unsafe { &*result };
1818
1819        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1820        Ok(())
1821    }
1822
1823    #[test]
1824    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1825        let vm = Vm::with_all()?;
1826        vm.add_empty_type("NavMap")?;
1827        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
1828        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1829        vm.import_code(
1830            "vm_native_custom_typed_arg",
1831            br#"
1832            pub fn add_nav_spawns(world, navmap) {
1833                navmap
1834            }
1835
1836            pub fn run(world) {
1837                let navmap: NavMap = NavMap::new();
1838                let with_spawns = add_nav_spawns(world, navmap);
1839                with_spawns
1840            }
1841            "#
1842            .to_vec(),
1843        )?;
1844
1845        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
1846        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1847        let world = Dynamic::Null;
1848        let result = run(&world);
1849        let result = unsafe { &*result };
1850
1851        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1852        Ok(())
1853    }
1854}