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