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