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 unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
842        let vm = Vm::with_all()?;
843        vm.import_code(
844            "vm_unary_not_any_loop_var",
845            br#"
846            pub fn count_missing(flags) {
847                let missing = 0;
848                for exists in flags {
849                    if !exists {
850                        missing = missing + 1;
851                    }
852                }
853                missing
854            }
855            "#
856            .to_vec(),
857        )?;
858
859        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
860        assert_eq!(compiled.ret_ty(), &Type::I32);
861        Ok(())
862    }
863
864    #[test]
865    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
866        let vm = Vm::with_all()?;
867        vm.import_code(
868            "vm_semicolon_tail_void",
869            br#"
870            pub fn send_role_select(idx, account_id, selected_slot) {
871                root::send("local/ui/send_dialog", {
872                    idx: idx,
873                    account_id: account_id,
874                    selected_slot: selected_slot
875                });
876            }
877            "#
878            .to_vec(),
879        )?;
880
881        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
882        assert_eq!(compiled.ret_ty(), &Type::Void);
883        Ok(())
884    }
885
886    #[test]
887    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
888        let vm = Vm::with_all()?;
889        vm.import_code(
890            "vm_bare_return_conflict",
891            br#"
892            pub fn run(flag) {
893                if flag {
894                    return;
895                }
896                1
897            }
898            "#
899            .to_vec(),
900        )?;
901
902        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
903            Ok(_) => panic!("expected mismatched return types to fail"),
904            Err(err) => err,
905        };
906        assert!(format!("{err:#}").contains("返回类型不一致"));
907        Ok(())
908    }
909
910    #[test]
911    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
912        let vm = Vm::with_all()?;
913        vm.import_code(
914            "vm_root_get_dynamic_concat",
915            br#"
916            pub fn get_action(req) {
917                root::get("local/game/panel_actions/" + req.idx)
918            }
919            "#
920            .to_vec(),
921        )?;
922
923        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
924        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
925        assert_eq!(compiled.ret_ty(), &Type::Any);
926        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
927        let req = dynamic::map!("idx"=> 7i64);
928        let result = unsafe { &*get_action(&req) };
929
930        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
931        Ok(())
932    }
933
934    #[test]
935    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
936        let vm = Vm::with_all()?;
937        vm.import_code(
938            "vm_registered_panel_action",
939            br#"
940            pub fn panel_action(req) {
941                root::get("local/game/panel_actions/" + req.idx)
942            }
943
944            pub fn register() {
945                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
946            }
947            "#
948            .to_vec(),
949        )?;
950
951        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
952        assert_eq!(compiled.ret_ty(), &Type::Bool);
953        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
954        assert!(register());
955        Ok(())
956    }
957
958    #[test]
959    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
960        let vm = Vm::with_all()?;
961        vm.import_code(
962            "vm_registered_string_concat",
963            br#"
964            pub fn send_panel(idx: i64) {
965                let idx_key = "" + idx;
966                idx_key
967            }
968            "#
969            .to_vec(),
970        )?;
971
972        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
973        Ok(())
974    }
975
976    #[test]
977    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
978        let vm = Vm::with_all()?;
979        vm.import_code(
980            "vm_public_hotspots",
981            br#"
982            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
983                {
984                    path: action_map_path,
985                    panel_id: panel_id,
986                    action_id: action_id,
987                    id: hotspot.id
988                }
989            }
990
991            pub fn public_hotspots(idx, panel_id, hotspots) {
992                let idx_key = "" + idx;
993                let action_map_path = "local/game/panel_actions/" + idx_key;
994
995                let existing_action_map = root::get(action_map_path);
996                if !existing_action_map.is_map() {
997                    root::add_map(action_map_path);
998                }
999
1000                if hotspots.is_map() {
1001                    let public_items = {};
1002                    for action_id in hotspots.keys() {
1003                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1004                    }
1005                    return public_items;
1006                }
1007
1008                let public_items = [];
1009                let i = 0;
1010                while i < hotspots.len() {
1011                    let hotspot = hotspots.get_idx(i);
1012                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1013                    public_items.push(item);
1014                    i = i + 1;
1015                }
1016
1017                public_items
1018            }
1019            "#
1020            .to_vec(),
1021        )?;
1022
1023        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
1024        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
1025        Ok(())
1026    }
1027
1028    #[test]
1029    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
1030        let vm = Vm::with_all()?;
1031        vm.import_code(
1032            "vm_send_panel_public_hotspots",
1033            br#"
1034            pub fn ok(value) {
1035                value
1036            }
1037
1038            pub fn panel_from_node(req) {
1039                {
1040                    panel_id: req.panel_id,
1041                    hotspots: req.hotspots
1042                }
1043            }
1044
1045            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1046                {
1047                    path: action_map_path,
1048                    panel_id: panel_id,
1049                    action_id: action_id,
1050                    id: hotspot.id
1051                }
1052            }
1053
1054            pub fn public_hotspots(idx, panel_id, hotspots) {
1055                let idx_key = "" + idx;
1056                let action_map_path = "local/game/panel_actions/" + idx_key;
1057
1058                let existing_action_map = root::get(action_map_path);
1059                if !existing_action_map.is_map() {
1060                    root::add_map(action_map_path);
1061                }
1062
1063                if hotspots.is_map() {
1064                    let public_items = {};
1065                    for action_id in hotspots.keys() {
1066                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1067                    }
1068                    return public_items;
1069                }
1070
1071                let public_items = [];
1072                let i = 0;
1073                while i < hotspots.len() {
1074                    let hotspot = hotspots.get_idx(i);
1075                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1076                    public_items.push(item);
1077                    i = i + 1;
1078                }
1079
1080                public_items
1081            }
1082
1083            pub fn send_panel(req) {
1084                let panel = req.panel;
1085                if !panel.is_map() {
1086                    panel = panel_from_node(req);
1087                }
1088                if !panel.is_map() {
1089                    return ok({
1090                        id: 4,
1091                        type: "panel_rejected",
1092                        reason: "invalid panel"
1093                    });
1094                }
1095                panel.id = 4;
1096                panel.idx = req.idx;
1097                if !panel.contains("type") {
1098                    panel.type = "panel";
1099                }
1100                if panel.contains("hotspots") {
1101                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1102                }
1103                root::send_idx("local/ws", req.idx, panel);
1104                ok({
1105                    id: 4,
1106                    type: "panel",
1107                    panel_id: panel.panel_id
1108                })
1109            }
1110            "#
1111            .to_vec(),
1112        )?;
1113
1114        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1115        assert_eq!(compiled.ret_ty(), &Type::Any);
1116        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1117        let req = dynamic::map!(
1118            "idx"=> 7i64,
1119            "panel"=> dynamic::map!(
1120                "panel_id"=> "main",
1121                "hotspots"=> dynamic::map!(
1122                    "open"=> dynamic::map!("id"=> "open")
1123                )
1124            )
1125        );
1126        let result = unsafe { &*send_panel(&req) };
1127
1128        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1129        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1130        Ok(())
1131    }
1132
1133    #[test]
1134    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1135        let vm = Vm::with_all()?;
1136        vm.import_code(
1137            "vm_string_concat_map_key",
1138            br##"
1139            pub fn write_action(action_map, panel_id, action_id, action) {
1140                action_map[panel_id + "#" + action_id] = action;
1141                action_map[panel_id + "#" + action_id]
1142            }
1143            "##
1144            .to_vec(),
1145        )?;
1146
1147        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1148        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1149        let action_map = dynamic::map!();
1150        let panel_id: Dynamic = "panel".into();
1151        let action_id: Dynamic = "open".into();
1152        let action = dynamic::map!("id"=> "open");
1153
1154        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1155
1156        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1157        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()));
1158        Ok(())
1159    }
1160
1161    #[test]
1162    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1163        let vm = Vm::with_all()?;
1164        vm.import_code(
1165            "vm_get_key_string_concat_key",
1166            br##"
1167            pub fn read_action(action_map, panel_id, action_id) {
1168                let action_key = panel_id + "#" + action_id;
1169                action_map.get_key(action_key)
1170            }
1171            "##
1172            .to_vec(),
1173        )?;
1174
1175        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1176        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1177        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1178        let panel_id: Dynamic = "panel".into();
1179        let action_id: Dynamic = "open".into();
1180
1181        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1182
1183        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1184        Ok(())
1185    }
1186
1187    #[test]
1188    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1189        let vm = Vm::with_all()?;
1190        vm.import_code(
1191            "vm_get_key_helper_string_key",
1192            br##"
1193            pub fn make_action_key(panel_id, action_id) {
1194                panel_id + "#" + action_id
1195            }
1196
1197            pub fn read_action(action_map, panel_id, action_id) {
1198                let action_key = make_action_key(panel_id, action_id);
1199                let action = action_map.get_key(action_key);
1200                action
1201            }
1202            "##
1203            .to_vec(),
1204        )?;
1205
1206        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1207        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1208        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1209        let panel_id: Dynamic = "panel".into();
1210        let action_id: Dynamic = "open".into();
1211
1212        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1213
1214        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1215        Ok(())
1216    }
1217
1218    #[test]
1219    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1220        let vm = Vm::with_all()?;
1221        vm.import_code(
1222            "vm_del_key_string_key",
1223            br##"
1224            pub fn remove_action(action_map, panel_id, action_id) {
1225                let action_key = panel_id + "#" + action_id;
1226                let removed = action_map.del_key(action_key);
1227                [removed, action_map.get_key(action_key)]
1228            }
1229            "##
1230            .to_vec(),
1231        )?;
1232
1233        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1234        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1235        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1236        let panel_id: Dynamic = "panel".into();
1237        let action_id: Dynamic = "open".into();
1238
1239        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1240
1241        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1242        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1243        assert!(action_map.get_dynamic("panel#open").is_none());
1244        Ok(())
1245    }
1246
1247    #[test]
1248    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1249        let vm = Vm::with_all()?;
1250        vm.import_code(
1251            "vm_dynamic_field_or",
1252            r#"
1253            pub fn next_or_start() {
1254                let choice = {
1255                    label: "颜色",
1256                    next: "color"
1257                };
1258                choice.next || "start"
1259            }
1260
1261            pub fn direct_next() {
1262                let choice = {
1263                    label: "颜色",
1264                    next: "color"
1265                };
1266                choice.next
1267            }
1268
1269            pub fn bracket_next() {
1270                let choice = {
1271                    label: "颜色",
1272                    next: "color"
1273                };
1274                choice["next"]
1275            }
1276
1277            pub fn assigned_preview() {
1278                let choice = {
1279                    next: "tax_free"
1280                };
1281                choice.preview = choice.next || "start";
1282                choice
1283            }
1284            "#
1285            .as_bytes()
1286            .to_vec(),
1287        )?;
1288
1289        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1290        assert_eq!(compiled.ret_ty(), &Type::Any);
1291        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1292        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1293
1294        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1295        assert_eq!(compiled.ret_ty(), &Type::Any);
1296        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1297        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1298
1299        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
1300        assert_eq!(compiled.ret_ty(), &Type::Any);
1301        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1302        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
1303
1304        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
1305        assert_eq!(compiled.ret_ty(), &Type::Any);
1306        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1307        let choice = unsafe { &*assigned_preview() };
1308        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
1309        Ok(())
1310    }
1311
1312    #[test]
1313    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1314        let vm = Vm::with_all()?;
1315        vm.import_code(
1316            "vm_if_empty_object_branch",
1317            r#"
1318            pub fn first_note(steps) {
1319                let first = if steps.len() > 0 { steps[0] } else { {} };
1320                let first_note = first.note || "fallback";
1321                first_note
1322            }
1323
1324            pub fn first_ja(steps) {
1325                let first = if steps.len() > 0 { steps[0] } else { {} };
1326                first.ja || "すみません"
1327            }
1328
1329            pub fn assign_first_note(steps) {
1330                let first = {};
1331                first = if steps.len() > 0 { steps[0] } else { {} };
1332                first.note || "fallback"
1333            }
1334            "#
1335            .as_bytes()
1336            .to_vec(),
1337        )?;
1338
1339        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1340        assert_eq!(compiled.ret_ty(), &Type::Any);
1341        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1342
1343        let empty_steps = Dynamic::list(Vec::new());
1344        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1345
1346        let mut step = std::collections::BTreeMap::new();
1347        step.insert("note".into(), "hello".into());
1348        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1349        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1350
1351        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1352        assert_eq!(compiled.ret_ty(), &Type::Any);
1353        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1354        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1355
1356        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1357        assert_eq!(compiled.ret_ty(), &Type::Any);
1358        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1359        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1360        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1361        Ok(())
1362    }
1363
1364    #[test]
1365    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1366        let vm = Vm::with_all()?;
1367        vm.import_code(
1368            "vm_tail_list_literal",
1369            r#"
1370            pub fn numbers() {
1371                [1, 2, 3]
1372            }
1373
1374            pub fn maps() {
1375                [
1376                    {note: "first"},
1377                    {note: "second"}
1378                ]
1379            }
1380
1381            pub fn object_with_maps() {
1382                {
1383                    steps: [
1384                        {note: "first"},
1385                        {note: "second"}
1386                    ]
1387                }
1388            }
1389
1390            pub fn return_maps() {
1391                return [
1392                    {note: "first"},
1393                    {note: "second"}
1394                ];
1395            }
1396
1397            pub fn return_maps_without_semicolon() {
1398                return [
1399                    {note: "first"},
1400                    {note: "second"}
1401                ]
1402            }
1403
1404            pub fn tail_bare_variable() {
1405                let value = [
1406                    {note: "first"},
1407                    {note: "second"}
1408                ];
1409                value
1410            }
1411
1412            pub fn return_bare_variable_without_semicolon() {
1413                let value = [
1414                    {note: "first"},
1415                    {note: "second"}
1416                ];
1417                return value
1418            }
1419
1420            pub fn tail_object_variable() {
1421                let result = {
1422                    steps: [
1423                        {note: "first"},
1424                        {note: "second"}
1425                    ]
1426                };
1427                result
1428            }
1429            "#
1430            .as_bytes()
1431            .to_vec(),
1432        )?;
1433
1434        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1435        assert_eq!(compiled.ret_ty(), &Type::Any);
1436        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1437        let result = unsafe { &*numbers() };
1438        assert_eq!(result.len(), 3);
1439        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1440
1441        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1442        assert_eq!(compiled.ret_ty(), &Type::Any);
1443        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1444        let result = unsafe { &*maps() };
1445        assert_eq!(result.len(), 2);
1446        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1447
1448        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1449        assert_eq!(compiled.ret_ty(), &Type::Any);
1450        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1451        let result = unsafe { &*object_with_maps() };
1452        let steps = result.get_dynamic("steps").expect("steps");
1453        assert_eq!(steps.len(), 2);
1454        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1455
1456        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1457        assert_eq!(compiled.ret_ty(), &Type::Any);
1458        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1459        let result = unsafe { &*return_maps() };
1460        assert_eq!(result.len(), 2);
1461        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1462
1463        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1464        assert_eq!(compiled.ret_ty(), &Type::Any);
1465        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1466        let result = unsafe { &*return_maps_without_semicolon() };
1467        assert_eq!(result.len(), 2);
1468        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1469
1470        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1471        assert_eq!(compiled.ret_ty(), &Type::Any);
1472        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1473        let result = unsafe { &*tail_bare_variable() };
1474        assert_eq!(result.len(), 2);
1475        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1476
1477        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1478        assert_eq!(compiled.ret_ty(), &Type::Any);
1479        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1480        let result = unsafe { &*return_bare_variable_without_semicolon() };
1481        assert_eq!(result.len(), 2);
1482        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1483
1484        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1485        assert_eq!(compiled.ret_ty(), &Type::Any);
1486        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1487        let result = unsafe { &*tail_object_variable() };
1488        let steps = result.get_dynamic("steps").expect("steps");
1489        assert_eq!(steps.len(), 2);
1490        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1491        Ok(())
1492    }
1493
1494    #[test]
1495    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1496        let vm = Vm::with_all()?;
1497        vm.import_code(
1498            "vm_returned_list_get_idx",
1499            r#"
1500            pub fn ids() {
1501                [
1502                    "base",
1503                    "2",
1504                    "3"
1505                ]
1506            }
1507
1508            pub fn combinations() {
1509                let result = [];
1510                let values = ids();
1511                let idx = 0;
1512                while idx < values.len() {
1513                    result.push(values.get_idx(idx));
1514                    idx = idx + 1;
1515                }
1516                result
1517            }
1518            "#
1519            .as_bytes()
1520            .to_vec(),
1521        )?;
1522
1523        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1524        assert_eq!(compiled.ret_ty(), &Type::Any);
1525        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1526        let result = unsafe { &*combinations() };
1527
1528        assert_eq!(result.len(), 3);
1529        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1530        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1531        Ok(())
1532    }
1533
1534    #[test]
1535    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1536        fn extra_page_literal(depth: usize) -> String {
1537            let mut value = "{leaf: \"done\"}".to_string();
1538            for idx in 0..depth {
1539                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1540            }
1541            value
1542        }
1543
1544        let extra = extra_page_literal(48);
1545        let code = format!(
1546            r#"
1547            pub fn script() {{
1548                return [
1549                    {{ja: "一つ目", note: "first", extra: {extra}}},
1550                    {{ja: "二つ目", note: "second", extra: {extra}}},
1551                    {{ja: "三つ目", note: "third", extra: {extra}}}
1552                ]
1553            }}
1554            "#
1555        );
1556
1557        let vm = Vm::with_all()?;
1558        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1559        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1560        assert_eq!(compiled.ret_ty(), &Type::Any);
1561        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1562        let result = unsafe { &*script() };
1563        assert_eq!(result.len(), 3);
1564        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1565        Ok(())
1566    }
1567
1568    #[test]
1569    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1570        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1571        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1572        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1573
1574        let vm1 = Vm::with_all()?;
1575        vm1.import_code(
1576            "vm_import_owner",
1577            format!(
1578                r#"
1579                pub fn run() {{
1580                    import("vm_imported_owner", "{module_path}");
1581                }}
1582                "#
1583            )
1584            .into_bytes(),
1585        )?;
1586        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1587
1588        let vm2 = Vm::with_all()?;
1589        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1590        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1591
1592        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1593        run();
1594
1595        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1596        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1597        Ok(())
1598    }
1599
1600    #[test]
1601    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1602        let vm = Vm::with_all()?;
1603        vm.import_code(
1604            "vm_object_last_call_field",
1605            r#"
1606            pub fn extra_page() {
1607                {
1608                    title: "extra",
1609                    pages: [
1610                        {note: "nested"}
1611                    ]
1612                }
1613            }
1614
1615            pub fn data() {
1616                return [
1617                    {
1618                        note: "first",
1619                        choices: ["a", "b"],
1620                        extras: extra_page()
1621                    },
1622                    {
1623                        note: "second",
1624                        choices: ["c"],
1625                        extras: extra_page()
1626                    }
1627                ]
1628            }
1629            "#
1630            .as_bytes()
1631            .to_vec(),
1632        )?;
1633
1634        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1635        assert_eq!(compiled.ret_ty(), &Type::Any);
1636        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1637        let result = unsafe { &*data() };
1638        assert_eq!(result.len(), 2);
1639        let first = result.get_idx(0).expect("first step");
1640        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1641        Ok(())
1642    }
1643
1644    #[test]
1645    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1646        let vm = Vm::with_all()?;
1647        vm.import_code(
1648            "vm_gpu_layout",
1649            br#"
1650            pub struct Params {
1651                a: u32,
1652                b: u32,
1653                c: u32,
1654            }
1655            "#
1656            .to_vec(),
1657        )?;
1658
1659        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1660        assert_eq!(layout.size, 16);
1661        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1662
1663        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1664        let bytes = layout.pack_map(&value)?;
1665        assert_eq!(bytes.len(), 16);
1666        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1667        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1668        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1669
1670        let read = layout.unpack_map(&bytes)?;
1671        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1672        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1673        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1674        Ok(())
1675    }
1676
1677    #[test]
1678    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1679        let vm = Vm::with_all()?;
1680        vm.import_code(
1681            "vm_root_clone_bridge",
1682            br#"
1683            pub fn add_then_reuse(arg) {
1684                let user = {
1685                    address: "test-wallet",
1686                    points: 20
1687                };
1688                root::add("local/root-clone-bridge-user", user);
1689                user.points = user.points - 7;
1690                root::add("local/root-clone-bridge-user", user);
1691                {
1692                    user: user,
1693                    points: user.points
1694                }
1695            }
1696
1697            pub fn clone_then_mutate(arg) {
1698                let user = {
1699                    profile: {
1700                        points: 20
1701                    }
1702                };
1703                let copied = user.clone();
1704                copied.profile.points = 13;
1705                user
1706            }
1707            "#
1708            .to_vec(),
1709        )?;
1710
1711        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1712        assert_eq!(compiled.ret_ty(), &Type::Any);
1713        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1714        let arg = Dynamic::Null;
1715        let result = add_then_reuse(&arg);
1716        let result = unsafe { &*result };
1717
1718        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1719        let mut json = String::new();
1720        result.to_json(&mut json);
1721        assert!(json.contains("\"points\": 13"));
1722
1723        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
1724        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
1725        let result = clone_then_mutate(&arg);
1726        let result = unsafe { &*result };
1727        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
1728        Ok(())
1729    }
1730
1731    struct CounterForTypedReceiver {
1732        value: i64,
1733    }
1734
1735    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
1736        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
1737    }
1738
1739    struct NavMapForFunctionArg;
1740
1741    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
1742        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
1743    }
1744
1745    #[test]
1746    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
1747        let vm = Vm::with_all()?;
1748        vm.add_empty_type("Counter")?;
1749        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
1750        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
1751        vm.import_code(
1752            "vm_typed_receiver_method",
1753            br#"
1754            pub fn run(value) {
1755                value::<Counter>::get()
1756            }
1757            "#
1758            .to_vec(),
1759        )?;
1760
1761        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
1762        assert_eq!(compiled.ret_ty(), &Type::I64);
1763        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1764        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
1765
1766        assert_eq!(run(&value), 42);
1767        Ok(())
1768    }
1769
1770    #[test]
1771    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1772        let vm = Vm::with_all()?;
1773        vm.add_empty_type("NavMap")?;
1774        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1775        vm.import_code(
1776            "vm_native_custom_arg",
1777            br#"
1778            pub fn add_nav_spawns(world, navmap) {
1779                navmap
1780            }
1781
1782            pub fn run(world) {
1783                let navmap = NavMap::new();
1784                let with_spawns = add_nav_spawns(world, navmap);
1785                with_spawns
1786            }
1787            "#
1788            .to_vec(),
1789        )?;
1790
1791        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
1792        assert_eq!(compiled.ret_ty(), &Type::Any);
1793        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1794        let world = Dynamic::Null;
1795        let result = run(&world);
1796        let result = unsafe { &*result };
1797
1798        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1799        Ok(())
1800    }
1801
1802    #[test]
1803    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1804        let vm = Vm::with_all()?;
1805        vm.add_empty_type("NavMap")?;
1806        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
1807        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1808        vm.import_code(
1809            "vm_native_custom_typed_arg",
1810            br#"
1811            pub fn add_nav_spawns(world, navmap) {
1812                navmap
1813            }
1814
1815            pub fn run(world) {
1816                let navmap: NavMap = NavMap::new();
1817                let with_spawns = add_nav_spawns(world, navmap);
1818                with_spawns
1819            }
1820            "#
1821            .to_vec(),
1822        )?;
1823
1824        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
1825        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1826        let world = Dynamic::Null;
1827        let result = run(&world);
1828        let result = unsafe { &*result };
1829
1830        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1831        Ok(())
1832    }
1833}