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