1mod binary;
3mod memory;
4mod native;
5pub use native::{ANY, STD, ZustCallback};
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::{Dynamic, Type};
16pub use rt::JITRunTime;
17#[cfg(feature = "db")]
18mod db_module;
19mod gpu_layout;
20#[cfg(feature = "gpu")]
21mod gpu_module;
22#[cfg(feature = "http")]
23mod http_module;
24#[cfg(feature = "llm")]
25mod llm_module;
26#[cfg(feature = "llm")]
27mod oss_module;
28mod root_module;
29pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
30pub use parking_lot::RwLock;
31
32use std::sync::{OnceLock, Weak};
33static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
34pub fn ptr_type() -> types::Type {
35 PTR_TYPE.get().cloned().unwrap()
36}
37
38pub fn get_type(ty: &Type) -> Result<types::Type> {
39 if ty.is_f64() {
40 Ok(types::F64)
41 } else if ty.is_f32() {
42 Ok(types::F32)
43 } else if ty.is_int() | ty.is_uint() {
44 match ty.width() {
45 1 => Ok(types::I8),
46 2 => Ok(types::I16),
47 4 => Ok(types::I32),
48 8 => Ok(types::I64),
49 _ => Err(anyhow!("非法类型 {:?}", ty)),
50 }
51 } else if let Type::Bool = ty {
52 Ok(types::I8)
53 } else {
54 Ok(ptr_type())
55 }
56}
57
58use compiler::Symbol;
59use cranelift::prelude::*;
60use cranelift_module::Module;
61
62pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
63 jit.add_all()?;
64 Ok(jit)
65}
66
67use std::sync::Arc;
68unsafe impl Send for JITRunTime {}
69unsafe impl Sync for JITRunTime {}
70
71pub(crate) fn with_vm_context<T>(context: *const Weak<RwLock<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
72 if context.is_null() {
73 return Err(anyhow!("VM context is null"));
74 }
75 let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
76 let vm = Vm { jit };
77 f(&vm)
78}
79
80fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
81 let def_id = jit.get_id(def)?;
82 if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
83 if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
84 fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
85 }
86 }
87 Ok(())
88}
89
90fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
91 jit.add_module(module);
92 for (name, arg_tys, ret_ty, fn_ptr) in fns {
93 let full_name = format!("{}::{}", module, name);
94 jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
95 }
96 jit.pop_module();
97 Ok(())
98}
99
100impl JITRunTime {
101 fn add_memory_runtime(&mut self) -> Result<()> {
102 self.native_symbols.write().insert("__vm_scope_enter".to_string(), memory::scope_enter as *const () as usize);
103 self.native_symbols.write().insert("__vm_scope_exit_void".to_string(), memory::scope_exit_void as *const () as usize);
104 self.native_symbols.write().insert("__vm_scope_exit_dynamic".to_string(), memory::scope_exit_dynamic as *const () as usize);
105 self.native_symbols.write().insert("__vm_scope_exit_bytes".to_string(), memory::scope_exit_bytes as *const () as usize);
106 self.native_symbols.write().insert("__vm_struct_alloc".to_string(), native::struct_alloc as *const () as usize);
107 self.native_symbols.write().insert("__vm_repeat_fill".to_string(), native::repeat_fill as *const () as usize);
108 self.native_symbols.write().insert("__vm_strcat".to_string(), native::strcat as *const () as usize);
109 self.native_symbols.write().insert("__vm_strcat_i64".to_string(), native::strcat_i64 as *const () as usize);
110 self.native_symbols.write().insert("__vm_strcat_assign".to_string(), native::strcat_assign as *const () as usize);
111 self.native_symbols.write().insert("__vm_callback_new".to_string(), native::callback_new as *const () as usize);
112 self.native_symbols.write().insert("__vm_spawn_ptr".to_string(), native::spawn_ptr as *const () as usize);
113 self.native_symbols.write().insert("__vm_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
114 self.native_symbols.write().insert("__vm_array_from_ptr".to_string(), native::array_from_ptr as *const () as usize);
115 self.native_symbols.write().insert("__vm_array_to_ptr".to_string(), native::array_to_ptr as *const () as usize);
116 self.native_symbols.write().insert("__vm_arith_fault".to_string(), memory::arith_fault as *const () as usize);
117
118 let void_sig = self.get_sig(&[], Type::Void)?;
119 self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
120 self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
121
122 let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
123 self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
124
125 let bytes_sig = self.get_sig(&[Type::Any, Type::I64, Type::I64], Type::Any)?;
126 self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
127
128 let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
129 self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
130
131 let repeat_fill_sig = self.get_sig(&[Type::Any, Type::I64, Type::I64, Type::I64], Type::Void)?;
132 self.repeat_fill_fn = Some(self.module.declare_function("__vm_repeat_fill", cranelift_module::Linkage::Import, &repeat_fill_sig)?);
133
134 let strcat_sig = self.get_sig(&[Type::Str, Type::Str], Type::Str)?;
135 self.strcat_fn = Some(self.module.declare_function("__vm_strcat", cranelift_module::Linkage::Import, &strcat_sig)?);
136
137 let strcat_i64_sig = self.get_sig(&[Type::Str, Type::I64], Type::Str)?;
138 self.strcat_i64_fn = Some(self.module.declare_function("__vm_strcat_i64", cranelift_module::Linkage::Import, &strcat_i64_sig)?);
139
140 let strcat_assign_sig = self.get_sig(&[Type::Any, Type::Any], Type::Any)?;
141 self.strcat_assign_fn = Some(self.module.declare_function("__vm_strcat_assign", cranelift_module::Linkage::Import, &strcat_assign_sig)?);
142
143 let callback_new_sig = self.get_sig(&[Type::I64, Type::I64, Type::I64, Type::Any], Type::Any)?;
144 self.callback_new_fn = Some(self.module.declare_function("__vm_callback_new", cranelift_module::Linkage::Import, &callback_new_sig)?);
145
146 let spawn_ptr_sig = self.get_sig(&[Type::I64, Type::I64, Type::Any], Type::Bool)?;
147 self.spawn_ptr_fn = Some(self.module.declare_function("__vm_spawn_ptr", cranelift_module::Linkage::Import, &spawn_ptr_sig)?);
148
149 let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
150 self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
151 self.array_from_ptr_fn = Some(self.module.declare_function("__vm_array_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
152 let array_to_ptr_sig = self.get_sig(&[Type::Any, Type::Any, Type::I64], Type::Void)?;
153 self.array_to_ptr_fn = Some(self.module.declare_function("__vm_array_to_ptr", cranelift_module::Linkage::Import, &array_to_ptr_sig)?);
154
155 self.arith_fault_fn = Some(self.module.declare_function("__vm_arith_fault", cranelift_module::Linkage::Import, &void_sig)?);
156 Ok(())
157 }
158
159 pub fn add_module(&mut self, name: &str) {
160 self.compiler.symbols.add_module(name.into());
161 }
162
163 pub fn pop_module(&mut self) {
164 self.compiler.symbols.pop_module();
165 }
166
167 pub fn add_native_const(&mut self, name: &str, value: impl Into<Dynamic>, ty: Type) -> u32 {
168 self.compiler.add_symbol(name, Symbol::Const { value: value.into(), ty, is_pub: true })
169 }
170
171 pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
172 self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
173 }
174
175 pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
176 match self.get_id(name) {
177 Ok(id) => Ok(id),
178 Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
179 }
180 }
181
182 pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
183 self.add_module(module);
184 let full_name = format!("{}::{}", module, name);
185 let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
186 self.pop_module();
187 result
188 }
189
190 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> {
191 self.add_module(module);
192 let full_name = format!("{}::{}", module, name);
193 let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
194 self.pop_module();
195 result
196 }
197
198 pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
199 self.add_empty_type(def)?;
200 let full_name = format!("{}::{}", def, method);
201 let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
202 add_method_field(self, def, method, id)?;
203 Ok(id)
204 }
205
206 pub fn add_std(&mut self) -> Result<()> {
207 if self.compiler.symbols.get_id("std::print").is_ok() {
208 return Ok(());
209 }
210 self.add_module("std");
211 for (name, arg_tys, ret_ty, fn_ptr) in STD {
212 self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
213 }
214 self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
215 self.add_context_native_ptr("spawn", "spawn", &[Type::Any, Type::Any], Type::Bool, native::spawn_with_vm as *const u8)?;
216 Ok(())
217 }
218
219 pub fn add_any(&mut self) -> Result<()> {
220 if self.compiler.symbols.get_id("Any").is_ok() && self.compiler.symbols.get_id("Any::is_map").is_ok() {
221 return Ok(());
222 }
223 for (name, arg_tys, ret_ty, fn_ptr) in ANY {
224 let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
225 self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
226 }
227 Ok(())
228 }
229
230 pub fn add_vec(&mut self) -> Result<()> {
231 if self.compiler.symbols.get_id("Vec::get_idx").is_ok() {
232 return Ok(());
233 }
234 self.add_empty_type("Vec")?;
235 let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
236 self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
237 if let Some(ctx) = ctx {
238 let width = ctx.builder.ins().iconst(types::I64, 4);
239 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);
242 let dest_addr = ctx.builder.ins().iadd(args[0], dest); let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
244 let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
245 ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
246 ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
247 }
248 Err(anyhow!("无返回值"))
249 })?;
250
251 self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
252 if let Some(ctx) = ctx {
253 let width = ctx.builder.ins().iconst(types::I64, 4);
254 let offset_val = ctx.builder.ins().imul(args[1], width); let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
256 Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
257 } else {
258 Ok((None, Type::I32))
259 }
260 })?;
261 Ok(())
262 }
263
264 #[cfg(feature = "llm")]
265 pub fn add_llm(&mut self) -> Result<()> {
266 if self.compiler.symbols.get_id("llm::complete").is_ok() {
267 return Ok(());
268 }
269 add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)?;
270 add_native_module_fns(self, "oss", &oss_module::OSS_NATIVE)
271 }
272
273 pub fn add_root(&mut self) -> Result<()> {
274 if self.compiler.symbols.get_id("root::get").is_ok() {
275 return Ok(());
276 }
277 add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
278 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)?;
279 Ok(())
280 }
281
282 #[cfg(feature = "http")]
283 pub fn add_http(&mut self) -> Result<()> {
284 if self.compiler.symbols.get_id("http::request").is_ok() {
285 return Ok(());
286 }
287 add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)?;
288 http_module::add_root_handlers()
289 }
290
291 #[cfg(feature = "db")]
292 pub fn add_db(&mut self) -> Result<()> {
293 if self.compiler.symbols.get_id("db::select").is_ok() {
294 return Ok(());
295 }
296 add_native_module_fns(self, "db", &db_module::DB_NATIVE)
297 }
298
299 #[cfg(feature = "gpu")]
300 pub fn add_gpu(&mut self) -> Result<()> {
301 if self.compiler.symbols.get_id("gpu::spirv_check").is_ok() {
302 return Ok(());
303 }
304 add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
305 }
306
307 pub fn add_all(&mut self) -> Result<()> {
308 self.add_std()?;
309 self.add_any()?;
310 self.add_vec()?;
311 self.add_root()?;
312 #[cfg(feature = "llm")]
313 self.add_llm()?;
314 #[cfg(feature = "http")]
315 self.add_http()?;
316 #[cfg(feature = "db")]
317 self.add_db()?;
318 #[cfg(feature = "gpu")]
319 self.add_gpu()?;
320 Ok(())
321 }
322}
323
324#[derive(Clone)]
325pub struct Vm {
326 pub jit: Arc<parking_lot::RwLock<JITRunTime>>,
327}
328
329impl Vm {
330 pub fn new() -> Self {
331 dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
332 let jit = Arc::new(RwLock::new(JITRunTime::new(|_| {})));
333 {
334 let mut guard = jit.write();
335 guard.set_owner(Arc::downgrade(&jit));
336 guard.add_memory_runtime().expect("register VM memory runtime");
337 guard.add_std().expect("register VM std runtime");
338 guard.add_any().expect("register VM Any runtime");
339 guard.add_vec().expect("register VM Vec runtime");
340 guard.add_root().expect("register VM root runtime");
341 }
342 Self { jit }
343 }
344
345 pub fn with_all() -> Result<Self> {
346 let vm = Self::new();
347 vm.jit.write().add_all()?;
348 Ok(vm)
349 }
350
351 pub fn import(&self, name: &str, path: &str) -> Result<()> {
352 if let Ok(code) = root::get(path) {
355 if code.is_str() {
356 self.jit.write().import_code(name, code.as_str().as_bytes().to_vec())?;
357 } else {
358 self.jit.write().import_code(name, code.get_dynamic("code").ok_or_else(|| anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())?;
359 }
360 Ok(())
361 } else {
362 self.jit.write().compiler.import_file(name, path)?;
363 Ok(())
364 }
365 }
366}
367
368impl Default for Vm {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::{GpuStructLayout, Vm, ZustCallback};
377 use dynamic::{CustomProperty, Dynamic, ToJson, Type};
378 use std::collections::BTreeMap;
379
380 struct TestFn {
382 ptr: *const u8,
383 ret: Type,
384 }
385
386 impl TestFn {
387 fn ptr(&self) -> *const u8 {
388 self.ptr
389 }
390 fn ret_ty(&self) -> &Type {
391 &self.ret
392 }
393 }
394
395 trait VmTestExt {
397 fn import_code(&self, name: &str, code: Vec<u8>) -> anyhow::Result<()>;
398 fn get_fn(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<TestFn>;
399 fn get_fn_with_params(&self, name: &str, arg_tys: &[Type], generic_args: &[Type]) -> anyhow::Result<TestFn>;
400 fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<(*const u8, Type)>;
401 fn infer(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<Type>;
402 fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32>;
403 fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32>;
404 fn add_empty_type(&self, name: &str) -> anyhow::Result<u32>;
405 fn add_std(&self) -> anyhow::Result<()>;
406 fn add_any(&self) -> anyhow::Result<()>;
407 fn get_symbol(&self, name: &str, params: Vec<Type>) -> anyhow::Result<Type>;
408 fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> anyhow::Result<GpuStructLayout>;
409 fn load(&self, code: Vec<u8>, arg_name: smol_str::SmolStr) -> anyhow::Result<(i64, Type)>;
410 }
411
412 impl VmTestExt for Vm {
413 fn import_code(&self, name: &str, code: Vec<u8>) -> anyhow::Result<()> {
414 self.jit.write().import_code(name, code)
415 }
416 fn get_fn(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<TestFn> {
417 let (ptr, ret) = self.jit.write().get_fn_ptr(name, arg_tys)?;
418 Ok(TestFn { ptr, ret })
419 }
420 fn get_fn_with_params(&self, name: &str, arg_tys: &[Type], generic_args: &[Type]) -> anyhow::Result<TestFn> {
421 let (ptr, ret) = self.jit.write().get_fn_ptr_with_params(name, arg_tys, generic_args)?;
422 Ok(TestFn { ptr, ret })
423 }
424 fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<(*const u8, Type)> {
425 self.jit.write().get_fn_ptr(name, arg_tys)
426 }
427 fn infer(&self, name: &str, arg_tys: &[Type]) -> anyhow::Result<Type> {
428 self.jit.write().get_type(name, arg_tys)
429 }
430 fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32> {
431 self.jit.write().add_native_module_ptr(module, name, arg_tys, ret_ty, ptr)
432 }
433 fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, ptr: *const u8) -> anyhow::Result<u32> {
434 self.jit.write().add_native_method_ptr(def, method, arg_tys, ret_ty, ptr)
435 }
436 fn add_empty_type(&self, name: &str) -> anyhow::Result<u32> {
437 self.jit.write().add_empty_type(name)
438 }
439 fn add_std(&self) -> anyhow::Result<()> {
440 self.jit.write().add_std()
441 }
442 fn add_any(&self) -> anyhow::Result<()> {
443 self.jit.write().add_any()
444 }
445 fn get_symbol(&self, name: &str, params: Vec<Type>) -> anyhow::Result<Type> {
446 Ok(Type::Symbol { id: self.jit.write().get_id(name)?, params })
447 }
448 fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> anyhow::Result<GpuStructLayout> {
449 let jit = self.jit.write();
450 GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
451 }
452 fn load(&self, code: Vec<u8>, arg_name: smol_str::SmolStr) -> anyhow::Result<(i64, Type)> {
453 self.jit.write().load(code, arg_name)
454 }
455 }
456
457 extern "C" fn math_double(value: i64) -> i64 {
458 value * 2
459 }
460
461 #[test]
462 fn build_context_set_var_fills_sparse_none_slots() -> anyhow::Result<()> {
463 use crate::context::{BuildContext, LocalVar};
464 use cranelift::codegen::ir::{Function, Signature, UserFuncName};
465 use cranelift::codegen::isa::CallConv;
466 use cranelift::prelude::{FunctionBuilder, FunctionBuilderContext};
467
468 let mut function = Function::with_name_signature(UserFuncName::user(0, 0), Signature::new(CallConv::Fast));
469 let mut function_ctx = FunctionBuilderContext::new();
470 let builder = FunctionBuilder::new(&mut function, &mut function_ctx);
471 let mut ctx = BuildContext::new(builder, &[], Type::Void)?;
472
473 ctx.set_var(33, LocalVar::None)?;
474
475 assert!(matches!(ctx.get_var(32)?, LocalVar::None));
476 assert!(matches!(ctx.get_var(33)?, LocalVar::None));
477 assert!(ctx.get_var(34).is_err());
478 Ok(())
479 }
480
481 #[test]
482 fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
483 let vm = Vm::new();
484 vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
485 vm.import_code(
486 "vm_dynamic_native",
487 br#"
488 pub fn run(value: i64) {
489 math::double(value)
490 }
491 "#
492 .to_vec(),
493 )?;
494
495 let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
496 assert_eq!(compiled.ret_ty(), &Type::I64);
497 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
498 assert_eq!(run(21), 42);
499 Ok(())
500 }
501
502 #[test]
503 fn vm_new_registers_std_and_any() -> anyhow::Result<()> {
504 let vm = Vm::new();
505 vm.add_std()?;
506 vm.add_any()?;
507 assert_eq!(vm.infer("std::print", &[Type::Any])?, Type::Void);
508 assert_eq!(vm.infer("std::sqrt", &[Type::F64])?, Type::F64);
509
510 vm.import_code(
511 "vm_new_default_any",
512 br#"
513 pub fn has_items(content) {
514 if content.is_map() {
515 if content.contains("items") {
516 return content.items.len() > 0;
517 }
518 }
519 false
520 }
521 "#
522 .to_vec(),
523 )?;
524
525 assert_eq!(vm.infer("vm_new_default_any::has_items", &[Type::Any])?, Type::Bool);
526 let compiled = vm.get_fn("vm_new_default_any::has_items", &[Type::Any])?;
527 assert_eq!(compiled.ret_ty(), &Type::Bool);
528 Ok(())
529 }
530
531 #[test]
532 fn std_sqrt_is_available_as_top_level_function() -> anyhow::Result<()> {
533 let vm = Vm::with_all()?;
534 vm.import_code(
535 "vm_std_sqrt",
536 br#"
537 pub fn run() {
538 sqrt(9.0f64)
539 }
540 "#
541 .to_vec(),
542 )?;
543
544 let compiled = vm.get_fn("vm_std_sqrt::run", &[])?;
545 assert_eq!(compiled.ret_ty(), &Type::F64);
546 let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
547 assert_eq!(run(), 3.0);
548 Ok(())
549 }
550
551 #[test]
552 fn tuple_assignment_uses_simultaneous_scalar_temps() -> anyhow::Result<()> {
553 let vm = Vm::with_all()?;
554 vm.import_code(
555 "vm_tuple_assignment",
556 br#"
557 pub fn swap() {
558 let a = 1i64;
559 let b = 2i64;
560 (a, b) = (b, a);
561 a * 10i64 + b
562 }
563
564 pub fn fib(n: i64) {
565 let a = 0i64;
566 let b = 1i64;
567 for _ in 0..n {
568 (a, b) = (b, (a + b) % 1000000007i64);
569 }
570 a
571 }
572 "#
573 .to_vec(),
574 )?;
575
576 let swap = vm.get_fn("vm_tuple_assignment::swap", &[])?;
577 let swap: extern "C" fn() -> i64 = unsafe { std::mem::transmute(swap.ptr()) };
578 assert_eq!(swap(), 21);
579
580 let fib = vm.get_fn("vm_tuple_assignment::fib", &[Type::I64])?;
581 let fib: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(fib.ptr()) };
582 assert_eq!(fib(10), 55);
583 Ok(())
584 }
585
586 #[test]
587 fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
588 let vm = Vm::with_all()?;
589 vm.import_code(
590 "vm_nested_struct_return_field",
591 br#"
592 pub struct Inner {
593 value: i64,
594 }
595
596 pub struct RoleMini {
597 inner: Inner,
598 hp: i64,
599 }
600
601 pub struct TeamMini {
602 role: RoleMini,
603 }
604
605 pub struct BigSummary {
606 winner: i64,
607 loser: i64,
608 }
609
610 pub fn make_big_with_team(team: TeamMini) {
611 let score = team.role.inner.value;
612 BigSummary{winner: score, loser: 0}
613 }
614
615 pub fn read_team_winner_direct() {
616 let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
617 make_big_with_team(team).winner
618 }
619
620 pub fn read_team_winner_bound() {
621 let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
622 let summary = make_big_with_team(team);
623 summary.winner
624 }
625 "#
626 .to_vec(),
627 )?;
628
629 let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
630 assert_eq!(compiled.ret_ty(), &Type::I64);
631 let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
632 assert_eq!(direct(), 9);
633
634 let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
635 assert_eq!(compiled.ret_ty(), &Type::I64);
636 let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
637 assert_eq!(bound(), 9);
638 Ok(())
639 }
640
641 #[test]
642 fn returned_nested_struct_dynamic_fields_are_read_inline() -> anyhow::Result<()> {
643 let vm = Vm::with_all()?;
644 vm.import_code(
645 "vm_returned_nested_struct_dynamic",
646 br#"
647 pub struct Inner {
648 value: i64,
649 }
650
651 pub struct Outer {
652 inner: Inner,
653 tag: i64,
654 }
655
656 pub fn make() {
657 Outer{inner: Inner{value: 17}, tag: 3}
658 }
659 "#
660 .to_vec(),
661 )?;
662
663 let compiled = vm.get_fn("vm_returned_nested_struct_dynamic::make", &[])?;
664 let make: extern "C" fn() -> *const u8 = unsafe { std::mem::transmute(compiled.ptr()) };
665 let ty = compiled.ret_ty().clone();
666 let value = Dynamic::struct_view(make() as usize, ty);
667 let inner = value.get_dynamic("inner").expect("inner field");
668 assert_eq!(inner.get_dynamic("value").and_then(|value| value.as_int()), Some(17));
669 assert_eq!(value.get_dynamic("tag").and_then(|value| value.as_int()), Some(3));
670 Ok(())
671 }
672
673 #[test]
674 fn returned_struct_with_dynamic_field_survives_scope_exit() -> anyhow::Result<()> {
675 let vm = Vm::with_all()?;
676 vm.import_code(
677 "vm_returned_struct_dynamic_field",
678 br#"
679 pub struct Bag {
680 name: string,
681 value: string,
682 }
683
684 pub fn make() {
685 Bag{name: "alpha", value: "omega"}
686 }
687 "#
688 .to_vec(),
689 )?;
690
691 let compiled = vm.get_fn("vm_returned_struct_dynamic_field::make", &[])?;
692 let make: extern "C" fn() -> *const u8 = unsafe { std::mem::transmute(compiled.ptr()) };
693 let value = Dynamic::struct_view(make() as usize, compiled.ret_ty().clone());
694 assert_eq!(value.get_dynamic("name").map(|value| value.as_str().to_string()), Some("alpha".to_string()));
695 assert_eq!(value.get_dynamic("value").map(|value| value.as_str().to_string()), Some("omega".to_string()));
696 Ok(())
697 }
698
699 #[test]
700 fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
701 let vm = Vm::with_all()?;
702 vm.import_code(
703 "vm_any_push_reused_value",
704 br#"
705 pub fn run() {
706 let role_id = "acct_role_2";
707 let updated = [];
708 updated.push(role_id);
709 {
710 ok: true,
711 user_id: role_id,
712 first: updated.get_idx(0)
713 }
714 }
715 "#
716 .to_vec(),
717 )?;
718
719 let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
720 assert_eq!(compiled.ret_ty(), &Type::Any);
721 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
722 let result = unsafe { &*run() };
723 assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
724 assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
725 assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
726 Ok(())
727 }
728
729 #[test]
730 fn negate_narrow_integers() -> anyhow::Result<()> {
731 let vm = Vm::with_all()?;
732 vm.import_code(
733 "vm_neg_narrow",
734 br#"
735 pub fn neg_i8(a: i8) { -a }
736 pub fn neg_i16(a: i16) { -a }
737 "#
738 .to_vec(),
739 )?;
740
741 let neg_i8 = vm.get_fn("vm_neg_narrow::neg_i8", &[Type::I8])?;
742 assert_eq!(neg_i8.ret_ty(), &Type::I8);
743 let neg_i8: extern "C" fn(i8) -> i8 = unsafe { std::mem::transmute(neg_i8.ptr()) };
744 assert_eq!(neg_i8(5), -5);
745 assert_eq!(neg_i8(-7), 7);
746
747 let neg_i16 = vm.get_fn("vm_neg_narrow::neg_i16", &[Type::I16])?;
748 assert_eq!(neg_i16.ret_ty(), &Type::I16);
749 let neg_i16: extern "C" fn(i16) -> i16 = unsafe { std::mem::transmute(neg_i16.ptr()) };
750 assert_eq!(neg_i16(5), -5);
751 assert_eq!(neg_i16(-300), 300);
752 Ok(())
753 }
754
755 #[test]
756 fn integer_divide_by_zero_does_not_crash() -> anyhow::Result<()> {
757 let vm = Vm::with_all()?;
758 vm.import_code(
759 "vm_div_by_zero",
760 br#"
761 pub fn divz(a: i64, b: i64) { a / b }
762 pub fn modz(a: i64, b: i64) { a % b }
763 pub fn overflow(a: i64, b: i64) { a / b }
764 "#
765 .to_vec(),
766 )?;
767
768 let divz = vm.get_fn("vm_div_by_zero::divz", &[Type::I64, Type::I64])?;
769 let modz = vm.get_fn("vm_div_by_zero::modz", &[Type::I64, Type::I64])?;
770 let overflow = vm.get_fn("vm_div_by_zero::overflow", &[Type::I64, Type::I64])?;
771 let divz: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(divz.ptr()) };
772 let modz: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(modz.ptr()) };
773 let overflow: extern "C" fn(i64, i64) -> i64 = unsafe { std::mem::transmute(overflow.ptr()) };
774
775 let _ = dynamic::take_fault();
777 assert_eq!(divz(7, 2), 3);
778 assert_eq!(modz(7, 2), 1);
779 assert!(dynamic::take_fault().is_none());
780
781 assert_eq!(divz(7, 0), 0);
783 assert!(dynamic::take_fault().is_some());
784 assert_eq!(modz(7, 0), 0);
785 assert!(dynamic::take_fault().is_some());
786
787 assert_eq!(overflow(i64::MIN, -1), 0);
789 assert!(dynamic::take_fault().is_some());
790 Ok(())
791 }
792
793 #[test]
794 fn constant_divide_by_zero_does_not_crash() -> anyhow::Result<()> {
795 let vm = Vm::with_all()?;
796 vm.import_code(
797 "vm_const_div_zero",
798 br#"
799 pub fn divz(a: i64) { a / 0 }
800 pub fn modz(a: i64) { a % 0 }
801 pub fn divc(a: i64) { a / 7 }
802 "#
803 .to_vec(),
804 )?;
805 let divz: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::divz", &[Type::I64])?.ptr()) };
806 let modz: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::modz", &[Type::I64])?.ptr()) };
807 let divc: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(vm.get_fn("vm_const_div_zero::divc", &[Type::I64])?.ptr()) };
808
809 let _ = dynamic::take_fault();
810 assert_eq!(divz(42), 0);
812 assert!(dynamic::take_fault().is_some());
813 assert_eq!(modz(42), 0);
814 assert!(dynamic::take_fault().is_some());
815 assert_eq!(divc(42), 6);
817 assert!(dynamic::take_fault().is_none());
818 Ok(())
819 }
820
821 #[test]
822 fn dynamic_divide_by_zero_returns_null() -> anyhow::Result<()> {
823 let vm = Vm::with_all()?;
824 vm.import_code(
825 "vm_any_div_by_zero",
826 br#"
827 pub fn divz(a, b) { a / b }
828 "#
829 .to_vec(),
830 )?;
831
832 let divz = vm.get_fn("vm_any_div_by_zero::divz", &[Type::Any, Type::Any])?;
833 let divz: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(divz.ptr()) };
834 let a = Dynamic::from(7i64);
835 let zero = Dynamic::from(0i64);
836 let _ = dynamic::take_fault();
837 let result = unsafe { &*divz(&a, &zero) };
838 assert!(result.is_null());
839 assert!(dynamic::take_fault().is_some());
840 Ok(())
841 }
842
843 #[test]
844 fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
845 let vm = Vm::with_all()?;
846 vm.import_code(
847 "vm_string_compare_any",
848 br#"
849 pub fn any_ne_empty(chat_path) {
850 chat_path != ""
851 }
852 "#
853 .to_vec(),
854 )?;
855
856 let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
857 assert_eq!(compiled.ret_ty(), &Type::Bool);
858
859 let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
860 let empty = Dynamic::from("");
861 let non_empty = Dynamic::from("chat");
862
863 assert!(!any_ne_empty(&empty));
864 assert!(any_ne_empty(&non_empty));
865 Ok(())
866 }
867
868 #[test]
869 fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
870 let vm = Vm::with_all()?;
871 vm.import_code(
872 "vm_bool_compare",
873 br#"
874 pub fn eq_true(value: bool) {
875 value == true
876 }
877
878 pub fn ne_false(value: bool) {
879 value != false
880 }
881
882 pub fn literal_left(value: bool) {
883 true == value
884 }
885
886 pub fn eq_pair(left: bool, right: bool) {
887 left == right
888 }
889
890 pub fn logic_pair(left: bool, right: bool) {
891 (left && right) || (left == true && right != false)
892 }
893 "#
894 .to_vec(),
895 )?;
896
897 let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
898 assert_eq!(compiled.ret_ty(), &Type::Bool);
899 let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
900 assert!(eq_true(true));
901 assert!(!eq_true(false));
902
903 let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
904 assert_eq!(compiled.ret_ty(), &Type::Bool);
905 let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
906 assert!(ne_false(true));
907 assert!(!ne_false(false));
908
909 let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
910 assert_eq!(compiled.ret_ty(), &Type::Bool);
911 let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
912 assert!(literal_left(true));
913 assert!(!literal_left(false));
914
915 let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
916 assert_eq!(compiled.ret_ty(), &Type::Bool);
917 let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
918 assert!(eq_pair(true, true));
919 assert!(eq_pair(false, false));
920 assert!(!eq_pair(true, false));
921 assert!(!eq_pair(false, true));
922
923 let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
924 assert_eq!(compiled.ret_ty(), &Type::Bool);
925 let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
926 assert!(logic_pair(true, true));
927 assert!(!logic_pair(true, false));
928 assert!(!logic_pair(false, true));
929 assert!(!logic_pair(false, false));
930 Ok(())
931 }
932
933 #[test]
934 fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
935 let vm = Vm::with_all()?;
936 vm.import_code(
937 "vm_parenthesized_method_call",
938 br#"
939 pub fn run(value) {
940 (value + 2).to_i64()
941 }
942 "#
943 .to_vec(),
944 )?;
945
946 let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
947 assert_eq!(compiled.ret_ty(), &Type::I64);
948 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
949 let value = Dynamic::from(40i64);
950
951 assert_eq!(run(&value), 42);
952 Ok(())
953 }
954
955 #[test]
956 fn casts_any_float_to_i32_without_zeroing() -> anyhow::Result<()> {
957 let vm = Vm::with_all()?;
958 vm.import_code(
959 "vm_any_float_to_i32",
960 br#"
961 pub fn direct(value) {
962 value as i32
963 }
964
965 pub fn map_field(value) {
966 let field = value.v;
967 field as i32
968 }
969
970 pub fn damage(attacker, def_rate) {
971 let x = attacker.atk * (1.0 - def_rate);
972 x as i32
973 }
974 "#
975 .to_vec(),
976 )?;
977
978 let compiled = vm.get_fn("vm_any_float_to_i32::direct", &[Type::Any])?;
979 assert_eq!(compiled.ret_ty(), &Type::I32);
980 let direct: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
981 let value = Dynamic::from(9.5f64);
982 assert_eq!(direct(&value), 9);
983
984 let compiled = vm.get_fn("vm_any_float_to_i32::map_field", &[Type::Any])?;
985 assert_eq!(compiled.ret_ty(), &Type::I32);
986 let map_field: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
987 let value = dynamic::map!("v"=> 9.5f64);
988 assert_eq!(map_field(&value), 9);
989
990 let compiled = vm.get_fn("vm_any_float_to_i32::damage", &[Type::Any, Type::Any])?;
991 assert_eq!(compiled.ret_ty(), &Type::I32);
992 let damage: extern "C" fn(*const Dynamic, *const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
993 let attacker = dynamic::map!("atk"=> 64i64);
994 let def_rate = Dynamic::from(0.17f64);
995 assert_eq!(damage(&attacker, &def_rate), 53);
996 Ok(())
997 }
998
999 #[test]
1000 fn binary_imm_promotes_integer_literals_for_float_left_values() -> anyhow::Result<()> {
1001 let vm = Vm::with_all()?;
1002 vm.import_code(
1003 "vm_float_binary_imm",
1004 br#"
1005 pub fn add_f32(value: f32) {
1006 value + 1i32
1007 }
1008
1009 pub fn sub_f32(value: f32) {
1010 value - 1i32
1011 }
1012
1013 pub fn mul_f32(value: f32) {
1014 value * 2i32
1015 }
1016
1017 pub fn div_f32(value: f32) {
1018 value / 2i32
1019 }
1020
1021 pub fn gt_f32(value: f32) {
1022 value > 2i32
1023 }
1024 "#
1025 .to_vec(),
1026 )?;
1027
1028 let compiled = vm.get_fn("vm_float_binary_imm::add_f32", &[Type::F32])?;
1029 assert_eq!(compiled.ret_ty(), &Type::F32);
1030 let add_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1031 assert_eq!(add_f32(2.5), 3.5);
1032
1033 let compiled = vm.get_fn("vm_float_binary_imm::sub_f32", &[Type::F32])?;
1034 assert_eq!(compiled.ret_ty(), &Type::F32);
1035 let sub_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1036 assert_eq!(sub_f32(2.5), 1.5);
1037
1038 let compiled = vm.get_fn("vm_float_binary_imm::mul_f32", &[Type::F32])?;
1039 assert_eq!(compiled.ret_ty(), &Type::F32);
1040 let mul_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1041 assert_eq!(mul_f32(2.5), 5.0);
1042
1043 let compiled = vm.get_fn("vm_float_binary_imm::div_f32", &[Type::F32])?;
1044 assert_eq!(compiled.ret_ty(), &Type::F32);
1045 let div_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
1046 assert_eq!(div_f32(5.0), 2.5);
1047
1048 let compiled = vm.get_fn("vm_float_binary_imm::gt_f32", &[Type::F32])?;
1049 assert_eq!(compiled.ret_ty(), &Type::Bool);
1050 let gt_f32: extern "C" fn(f32) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1051 assert!(gt_f32(2.5));
1052 assert!(!gt_f32(1.5));
1053 Ok(())
1054 }
1055
1056 #[test]
1057 fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
1058 let vm = Vm::with_all()?;
1059 vm.import_code(
1060 "vm_any_keys",
1061 br#"
1062 pub fn map_keys(value) {
1063 let keys = value.keys();
1064 keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
1065 }
1066
1067 pub fn non_map_keys(value) {
1068 value.keys().len() == 0
1069 }
1070 "#
1071 .to_vec(),
1072 )?;
1073
1074 let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
1075 assert_eq!(compiled.ret_ty(), &Type::Bool);
1076 let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1077 let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
1078 assert!(map_keys(&value));
1079
1080 let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
1081 assert_eq!(compiled.ret_ty(), &Type::Bool);
1082 let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1083 let value = Dynamic::from("alpha");
1084 assert!(non_map_keys(&value));
1085 Ok(())
1086 }
1087
1088 #[test]
1089 fn any_logic_comparisons_use_bool_abi() -> anyhow::Result<()> {
1090 let vm = Vm::with_all()?;
1091 vm.import_code(
1092 "vm_any_logic_abi",
1093 br#"
1094 pub fn ne_empty(value) {
1095 value != ""
1096 }
1097
1098 pub fn eq_empty(value) {
1099 value == ""
1100 }
1101
1102 pub fn less_than_ten(value) {
1103 value < 10
1104 }
1105
1106 pub fn contains_key(value) {
1107 value.contains("alpha") == true
1108 }
1109 "#
1110 .to_vec(),
1111 )?;
1112
1113 let compiled = vm.get_fn("vm_any_logic_abi::ne_empty", &[Type::Any])?;
1114 assert_eq!(compiled.ret_ty(), &Type::Bool);
1115 let ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1116 assert!(ne_empty(&Dynamic::from("x")));
1117 assert!(!ne_empty(&Dynamic::from("")));
1118
1119 let compiled = vm.get_fn("vm_any_logic_abi::eq_empty", &[Type::Any])?;
1120 assert_eq!(compiled.ret_ty(), &Type::Bool);
1121 let eq_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1122 assert!(eq_empty(&Dynamic::from("")));
1123 assert!(!eq_empty(&Dynamic::from("x")));
1124
1125 let compiled = vm.get_fn("vm_any_logic_abi::less_than_ten", &[Type::Any])?;
1126 assert_eq!(compiled.ret_ty(), &Type::Bool);
1127 let less_than_ten: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1128 assert!(less_than_ten(&Dynamic::from(4i64)));
1129 assert!(!less_than_ten(&Dynamic::from(14i64)));
1130
1131 let compiled = vm.get_fn("vm_any_logic_abi::contains_key", &[Type::Any])?;
1132 assert_eq!(compiled.ret_ty(), &Type::Bool);
1133 let contains_key: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1134 assert!(contains_key(&dynamic::map!("alpha"=> 1i64)));
1135 assert!(!contains_key(&dynamic::map!("beta"=> 1i64)));
1136 Ok(())
1137 }
1138
1139 #[test]
1140 fn string_methods_work_on_static_string_and_any_string_values() -> anyhow::Result<()> {
1141 let vm = Vm::with_all()?;
1142 vm.import_code(
1143 "vm_string_methods",
1144 br#"
1145 pub fn static_string_methods(text: string) {
1146 let parts = text.split(",");
1147 text.starts_with("alpha")
1148 && text.is_string()
1149 && !text.is_null()
1150 && parts.len() == 2
1151 && parts.get_idx(0) == "alpha"
1152 && parts.get_idx(1) == "beta"
1153 }
1154
1155 pub fn any_string_methods(value) {
1156 let parts = value.split(",");
1157 value.starts_with("alpha")
1158 && value.is_string()
1159 && !value.is_null()
1160 && parts.len() == 2
1161 && parts.get_idx(0) == "alpha"
1162 && parts.get_idx(1) == "beta"
1163 }
1164
1165 pub fn any_null_methods(value) {
1166 value.is_null() && !value.is_string()
1167 }
1168 "#
1169 .to_vec(),
1170 )?;
1171
1172 let compiled = vm.get_fn("vm_string_methods::static_string_methods", &[Type::Str])?;
1173 assert_eq!(compiled.ret_ty(), &Type::Bool);
1174 let static_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1175 let text = Dynamic::from("alpha,beta");
1176 assert!(static_string_methods(&text));
1177
1178 let compiled = vm.get_fn("vm_string_methods::any_string_methods", &[Type::Any])?;
1179 assert_eq!(compiled.ret_ty(), &Type::Bool);
1180 let any_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1181 assert!(any_string_methods(&text));
1182
1183 let compiled = vm.get_fn("vm_string_methods::any_null_methods", &[Type::Any])?;
1184 assert_eq!(compiled.ret_ty(), &Type::Bool);
1185 let any_null_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1186 let value = Dynamic::Null;
1187 assert!(any_null_methods(&value));
1188 Ok(())
1189 }
1190
1191 #[test]
1192 fn static_string_add_uses_direct_strcat() -> anyhow::Result<()> {
1193 let vm = Vm::with_all()?;
1194 vm.import_code(
1195 "vm_static_strcat",
1196 br#"
1197 pub fn join(left: string, right: string) {
1198 left + right
1199 }
1200
1201 pub fn suffix(left: string) {
1202 left + "-tail"
1203 }
1204
1205 pub fn append_local() {
1206 let text: string = "alpha";
1207 text += "-beta";
1208 text += "-tail";
1209 text
1210 }
1211
1212 pub fn append_local_assign() {
1213 let text: string = "alpha";
1214 text = text + "-beta";
1215 text = text + "-tail";
1216 text
1217 }
1218
1219 pub fn append_arg(text: string) {
1220 text += "-tail";
1221 text
1222 }
1223
1224 pub fn append_arg_assign(text: string) {
1225 text = text + "-tail";
1226 text
1227 }
1228
1229 pub fn append_any(value) {
1230 value += "-tail";
1231 value
1232 }
1233
1234 pub fn add_sub_assign_form() {
1235 let x = 10i64;
1236 x = x + 1i64;
1237 x = x - 2i64;
1238 x
1239 }
1240 "#
1241 .to_vec(),
1242 )?;
1243
1244 let compiled = vm.get_fn("vm_static_strcat::join", &[Type::Str, Type::Str])?;
1245 assert_eq!(compiled.ret_ty(), &Type::Str);
1246 let join: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1247 let left = Dynamic::from("alpha");
1248 let right = Dynamic::from("-beta");
1249 let result = unsafe { &*join(&left, &right) };
1250 assert!(matches!(result, Dynamic::StringBuf(_)));
1251 assert_eq!(result.as_str(), "alpha-beta");
1252
1253 let compiled = vm.get_fn("vm_static_strcat::suffix", &[Type::Str])?;
1254 assert_eq!(compiled.ret_ty(), &Type::Str);
1255 let suffix: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1256 let result = unsafe { &*suffix(&left) };
1257 assert!(matches!(result, Dynamic::StringBuf(_)));
1258 assert_eq!(result.as_str(), "alpha-tail");
1259
1260 let compiled = vm.get_fn("vm_static_strcat::append_local", &[])?;
1261 assert_eq!(compiled.ret_ty(), &Type::Str);
1262 let append_local: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1263 let result = unsafe { &*append_local() };
1264 assert!(matches!(result, Dynamic::StringBuf(_)));
1265 assert_eq!(result.as_str(), "alpha-beta-tail");
1266
1267 let compiled = vm.get_fn("vm_static_strcat::append_local_assign", &[])?;
1268 assert_eq!(compiled.ret_ty(), &Type::Str);
1269 let append_local_assign: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1270 let result = unsafe { &*append_local_assign() };
1271 assert!(matches!(result, Dynamic::StringBuf(_)));
1272 assert_eq!(result.as_str(), "alpha-beta-tail");
1273
1274 let compiled = vm.get_fn("vm_static_strcat::append_arg", &[Type::Str])?;
1275 assert_eq!(compiled.ret_ty(), &Type::Str);
1276 let append_arg: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1277 let input = Dynamic::from("alpha");
1278 let result = unsafe { &*append_arg(&input) };
1279 assert_eq!(result.as_str(), "alpha-tail");
1280 assert_eq!(input.as_str(), "alpha");
1281
1282 let compiled = vm.get_fn("vm_static_strcat::append_arg_assign", &[Type::Str])?;
1283 assert_eq!(compiled.ret_ty(), &Type::Str);
1284 let append_arg_assign: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1285 let input = Dynamic::from("alpha");
1286 let result = unsafe { &*append_arg_assign(&input) };
1287 assert_eq!(result.as_str(), "alpha-tail");
1288 assert_eq!(input.as_str(), "alpha");
1289
1290 let compiled = vm.get_fn("vm_static_strcat::append_any", &[Type::Any])?;
1291 assert_eq!(compiled.ret_ty(), &Type::Str);
1292 let append_any: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1293 let input = Dynamic::from("alpha");
1294 let result = unsafe { &*append_any(&input) };
1295 assert_eq!(result.as_str(), "alpha-tail");
1296 assert_eq!(input.as_str(), "alpha");
1297
1298 let compiled = vm.get_fn("vm_static_strcat::add_sub_assign_form", &[])?;
1299 assert_eq!(compiled.ret_ty(), &Type::I64);
1300 let add_sub_assign_form: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1301 assert_eq!(add_sub_assign_form(), 9);
1302 Ok(())
1303 }
1304
1305 #[test]
1306 fn primitive_type_check_methods_call_any_runtime() -> anyhow::Result<()> {
1307 let vm = Vm::with_all()?;
1308 vm.import_code(
1309 "vm_primitive_type_check_methods",
1310 br#"
1311 pub fn int_checks() {
1312 !42i64.is_list()
1313 && !42i64.is_map()
1314 && !42i64.is_string()
1315 && !42i64.is_null()
1316 }
1317
1318 pub fn bool_checks() {
1319 !true.is_list() && !true.is_map() && !true.is_null()
1320 }
1321 "#
1322 .to_vec(),
1323 )?;
1324
1325 let compiled = vm.get_fn("vm_primitive_type_check_methods::int_checks", &[])?;
1326 assert_eq!(compiled.ret_ty(), &Type::Bool);
1327 let int_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1328 assert!(int_checks());
1329
1330 let compiled = vm.get_fn("vm_primitive_type_check_methods::bool_checks", &[])?;
1331 assert_eq!(compiled.ret_ty(), &Type::Bool);
1332 let bool_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1333 assert!(bool_checks());
1334 Ok(())
1335 }
1336
1337 #[test]
1338 fn for_loop_iterates_any_list_and_map_values() -> anyhow::Result<()> {
1339 let vm = Vm::with_all()?;
1340 vm.import_code(
1341 "vm_for_any_collections",
1342 br#"
1343 pub fn list_sum(items) {
1344 let total = 0i64;
1345 for item in items {
1346 total += item;
1347 }
1348 total
1349 }
1350
1351 pub fn map_sum(data) {
1352 let total = 0i64;
1353 for (key, value) in data {
1354 total += value;
1355 }
1356 total
1357 }
1358 "#
1359 .to_vec(),
1360 )?;
1361
1362 let compiled = vm.get_fn("vm_for_any_collections::list_sum", &[Type::Any])?;
1363 assert_eq!(compiled.ret_ty(), &Type::I64);
1364 let list_sum: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1365 let items = Dynamic::list(vec![1i64.into(), 2i64.into(), 3i64.into()]);
1366 assert_eq!(list_sum(&items), 6);
1367
1368 let compiled = vm.get_fn("vm_for_any_collections::map_sum", &[Type::Any])?;
1369 assert_eq!(compiled.ret_ty(), &Type::I64);
1370 let map_sum: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1371 let data = dynamic::map!("a"=> 4i64, "b"=> 5i64);
1372 assert_eq!(map_sum(&data), 9);
1373 Ok(())
1374 }
1375
1376 #[test]
1377 fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
1378 let vm = Vm::with_all()?;
1379 vm.import_code(
1380 "vm_string_compare_imm",
1381 br#"
1382 pub fn int_eq_str(value: i64) {
1383 value == "42"
1384 }
1385
1386 pub fn int_to_str(value: i64) {
1387 value + ""
1388 }
1389 "#
1390 .to_vec(),
1391 )?;
1392
1393 let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
1394 assert_eq!(compiled.ret_ty(), &Type::Bool);
1395
1396 let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1397
1398 let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
1399 assert_eq!(compiled.ret_ty(), &Type::Str);
1400 let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1401 let text = int_to_str(42);
1402 assert_eq!(unsafe { &*text }.as_str(), "42");
1403
1404 assert!(int_eq_str(42));
1405 assert!(!int_eq_str(7));
1406 Ok(())
1407 }
1408
1409 #[test]
1410 fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
1411 let vm = Vm::with_all()?;
1412 vm.import_code(
1413 "vm_string_concat_integer",
1414 br#"
1415 pub fn idx_key(idx: i64) {
1416 "" + idx
1417 }
1418
1419 pub fn level_text(level: i64) {
1420 "" + level + " level"
1421 }
1422
1423 pub fn gold_text(currency) {
1424 "" + currency.gold
1425 }
1426 "#
1427 .to_vec(),
1428 )?;
1429
1430 let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
1431 let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1432 let result = unsafe { &*idx_key(7) };
1433 assert!(matches!(result, Dynamic::StringBuf(_)));
1434 assert_eq!(result.as_str(), "7");
1435
1436 let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
1437 let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1438 let result = unsafe { &*level_text(12) };
1439 assert_eq!(result.as_str(), "12 level");
1440
1441 let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
1442 let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1443 let currency = dynamic::map!("gold"=> 345i64);
1444 let result = unsafe { &*gold_text(¤cy) };
1445 assert_eq!(result.as_str(), "345");
1446 Ok(())
1447 }
1448
1449 #[test]
1450 fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
1451 let vm = Vm::with_all()?;
1452 vm.import_code(
1453 "vm_string_concat_to_i64",
1454 br#"
1455 pub fn run(idx: i64) {
1456 ("" + idx) as i64
1457 }
1458 "#
1459 .to_vec(),
1460 )?;
1461
1462 let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
1463 assert_eq!(compiled.ret_ty(), &Type::I64);
1464 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1465 assert_eq!(run(7), 7);
1466 Ok(())
1467 }
1468
1469 #[test]
1470 fn casts_dynamic_string_numbers_to_ints_and_floats() -> anyhow::Result<()> {
1471 let vm = Vm::with_all()?;
1472 vm.import_code(
1473 "vm_string_number_casts",
1474 br#"
1475 pub fn limit_i64(req) {
1476 req["@query"].limit as i64
1477 }
1478
1479 pub fn limit_i32(req) {
1480 req["@query"].limit as i32
1481 }
1482
1483 pub fn price_f64(req) {
1484 req["@query"].price as f64
1485 }
1486
1487 pub fn price_f32(req) {
1488 req["@query"].price as f32
1489 }
1490
1491 pub fn literal_i64() {
1492 "42" as i64
1493 }
1494
1495 pub fn literal_f64() {
1496 "3.5" as f64
1497 }
1498
1499 pub fn bad_number(req) {
1500 req["@query"].bad as i64
1501 }
1502 "#
1503 .to_vec(),
1504 )?;
1505
1506 let req = dynamic::map!("@query"=> dynamic::map!("limit"=> "50", "price"=> "3.5", "bad"=> "nope"));
1507
1508 let limit_i64 = vm.get_fn("vm_string_number_casts::limit_i64", &[Type::Any])?;
1509 assert_eq!(limit_i64.ret_ty(), &Type::I64);
1510 let limit_i64: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(limit_i64.ptr()) };
1511 assert_eq!(limit_i64(&req), 50);
1512
1513 let limit_i32 = vm.get_fn("vm_string_number_casts::limit_i32", &[Type::Any])?;
1514 assert_eq!(limit_i32.ret_ty(), &Type::I32);
1515 let limit_i32: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(limit_i32.ptr()) };
1516 assert_eq!(limit_i32(&req), 50);
1517
1518 let price_f64 = vm.get_fn("vm_string_number_casts::price_f64", &[Type::Any])?;
1519 assert_eq!(price_f64.ret_ty(), &Type::F64);
1520 let price_f64: extern "C" fn(*const Dynamic) -> f64 = unsafe { std::mem::transmute(price_f64.ptr()) };
1521 assert_eq!(price_f64(&req), 3.5);
1522
1523 let price_f32 = vm.get_fn("vm_string_number_casts::price_f32", &[Type::Any])?;
1524 assert_eq!(price_f32.ret_ty(), &Type::F32);
1525 let price_f32: extern "C" fn(*const Dynamic) -> f32 = unsafe { std::mem::transmute(price_f32.ptr()) };
1526 assert_eq!(price_f32(&req), 3.5);
1527
1528 let literal_i64 = vm.get_fn("vm_string_number_casts::literal_i64", &[])?;
1529 assert_eq!(literal_i64.ret_ty(), &Type::I64);
1530 let literal_i64: extern "C" fn() -> i64 = unsafe { std::mem::transmute(literal_i64.ptr()) };
1531 assert_eq!(literal_i64(), 42);
1532
1533 let literal_f64 = vm.get_fn("vm_string_number_casts::literal_f64", &[])?;
1534 assert_eq!(literal_f64.ret_ty(), &Type::F64);
1535 let literal_f64: extern "C" fn() -> f64 = unsafe { std::mem::transmute(literal_f64.ptr()) };
1536 assert_eq!(literal_f64(), 3.5);
1537
1538 let bad_number = vm.get_fn("vm_string_number_casts::bad_number", &[Type::Any])?;
1539 assert_eq!(bad_number.ret_ty(), &Type::I64);
1540 let bad_number: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(bad_number.ptr()) };
1541 assert_eq!(bad_number(&req), 0);
1542 Ok(())
1543 }
1544
1545 #[test]
1546 fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
1547 let vm = Vm::with_all()?;
1548 vm.import_code(
1549 "vm_return_integer_widths",
1550 br#"
1551 pub fn selected(flag, slot) {
1552 if flag {
1553 return slot;
1554 }
1555 0
1556 }
1557 "#
1558 .to_vec(),
1559 )?;
1560
1561 let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
1562 assert_eq!(compiled.ret_ty(), &Type::I64);
1563 let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1564
1565 assert_eq!(selected(true, 7), 7);
1566 assert_eq!(selected(false, 7), 0);
1567 Ok(())
1568 }
1569
1570 #[test]
1571 fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
1572 let vm = Vm::with_all()?;
1573 vm.import_code(
1574 "vm_root_contains_condition",
1575 br#"
1576 pub fn exists(user_id) {
1577 if root::contains("redis/user/" + user_id) {
1578 return 1;
1579 }
1580 0
1581 }
1582 "#
1583 .to_vec(),
1584 )?;
1585
1586 assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
1587 let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
1588 assert_eq!(compiled.ret_ty(), &Type::I64);
1589 Ok(())
1590 }
1591
1592 #[test]
1593 fn root_add_map_can_be_printed() -> anyhow::Result<()> {
1594 let vm = Vm::with_all()?;
1595 assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
1596 vm.import_code(
1597 "vm_root_add_map_print",
1598 br#"
1599 pub fn run() {
1600 print(root::add_map("local/world_handlers/til_map_novicevillage"));
1601 }
1602 "#
1603 .to_vec(),
1604 )?;
1605
1606 let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
1607 assert!(compiled.ret_ty().is_void());
1608 Ok(())
1609 }
1610
1611 #[test]
1612 fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
1613 let vm = Vm::with_all()?;
1614 vm.import_code(
1615 "vm_std_log",
1616 br#"
1617 pub fn run(value) {
1618 log({ ok: true, value: value });
1619 }
1620 "#
1621 .to_vec(),
1622 )?;
1623
1624 let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
1625 assert!(compiled.ret_ty().is_void());
1626 let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
1627 let value = Dynamic::from(7i64);
1628 run(&value);
1629 Ok(())
1630 }
1631
1632 #[test]
1633 fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
1634 let vm = Vm::with_all()?;
1635 vm.import_code(
1636 "vm_unary_not_any_loop_var",
1637 br#"
1638 pub fn count_missing(flags) {
1639 let missing = 0;
1640 for exists in flags {
1641 if !exists {
1642 missing = missing + 1;
1643 }
1644 }
1645 missing
1646 }
1647 "#
1648 .to_vec(),
1649 )?;
1650
1651 let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
1652 assert_eq!(compiled.ret_ty(), &Type::I64);
1653 Ok(())
1654 }
1655
1656 #[test]
1657 fn closure_literal_can_be_called_immediately() -> anyhow::Result<()> {
1658 let vm = Vm::with_all()?;
1659 vm.import_code(
1660 "vm_closure_immediate_call",
1661 br#"
1662 pub fn no_args() {
1663 let r = || { 1i32 }();
1664 r
1665 }
1666
1667 pub fn with_arg() {
1668 |value: i32| { value + 1i32 }(2i32)
1669 }
1670 "#
1671 .to_vec(),
1672 )?;
1673
1674 let compiled = vm.get_fn("vm_closure_immediate_call::no_args", &[])?;
1675 assert_eq!(compiled.ret_ty(), &Type::I32);
1676 let no_args: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1677 assert_eq!(no_args(), 1);
1678
1679 let compiled = vm.get_fn("vm_closure_immediate_call::with_arg", &[])?;
1680 assert_eq!(compiled.ret_ty(), &Type::I32);
1681 let with_arg: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1682 assert_eq!(with_arg(), 3);
1683 Ok(())
1684 }
1685
1686 #[test]
1687 fn small_expression_calls_keep_direct_semantics() -> anyhow::Result<()> {
1688 let vm = Vm::with_all()?;
1689 vm.import_code(
1690 "vm_small_expression_inline",
1691 br#"
1692 pub fn add_i64(left: i64, right: i64) {
1693 left + right
1694 }
1695
1696 pub fn normal_caller() {
1697 add_i64(1i64, 2i64)
1698 }
1699
1700 pub fn closure_caller() {
1701 let add = |left: i64, right: i64| { left + right };
1702 add(add_i64(1i64, 2i64), 4i64)
1703 }
1704
1705 pub fn closure_assignment() {
1706 let acc = 0i64;
1707 let add = |left: i64, right: i64| { left + right };
1708 acc = add(acc, 4i64);
1709 acc
1710 }
1711 "#
1712 .to_vec(),
1713 )?;
1714
1715 let compiled = vm.get_fn("vm_small_expression_inline::normal_caller", &[])?;
1716 assert_eq!(compiled.ret_ty(), &Type::I64);
1717 let normal_caller: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1718 assert_eq!(normal_caller(), 3);
1719
1720 let compiled = vm.get_fn("vm_small_expression_inline::closure_caller", &[])?;
1721 assert_eq!(compiled.ret_ty(), &Type::Any);
1722 let closure_caller: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1723 let result = unsafe { &*closure_caller() };
1724 assert_eq!(result.as_int(), Some(7));
1725
1726 let compiled = vm.get_fn("vm_small_expression_inline::closure_assignment", &[])?;
1727 assert_eq!(compiled.ret_ty(), &Type::I64);
1728 let closure_assignment: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1729 assert_eq!(closure_assignment(), 4);
1730 Ok(())
1731 }
1732
1733 #[test]
1734 fn nested_closure_captures_outer_closure_arg() -> anyhow::Result<()> {
1735 let vm = Vm::with_all()?;
1736 vm.import_code(
1737 "vm_nested_closure_capture",
1738 br#"
1739 pub fn run() {
1740 let reference_label = "reference";
1741 |path: string| {
1742 let upload_done = |uploaded: bool| {
1743 if uploaded {
1744 reference_label + ":" + path
1745 } else {
1746 "missing"
1747 }
1748 };
1749 upload_done(true)
1750 }("reference.png")
1751 }
1752 "#
1753 .to_vec(),
1754 )?;
1755
1756 let compiled = vm.get_fn("vm_nested_closure_capture::run", &[])?;
1757 assert_eq!(compiled.ret_ty(), &Type::Any);
1758 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1759 let result = unsafe { &*run() };
1760 assert_eq!(result.as_str(), "reference:reference.png");
1761 Ok(())
1762 }
1763
1764 #[test]
1765 fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1766 let vm = Vm::with_all()?;
1767 vm.import_code(
1768 "vm_semicolon_tail_void",
1769 br#"
1770 pub fn send_role_select(idx, account_id, selected_slot) {
1771 root::send("local/ui/send_dialog", {
1772 idx: idx,
1773 account_id: account_id,
1774 selected_slot: selected_slot
1775 });
1776 }
1777 "#
1778 .to_vec(),
1779 )?;
1780
1781 let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1782 assert_eq!(compiled.ret_ty(), &Type::Void);
1783 Ok(())
1784 }
1785
1786 #[test]
1787 fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1788 let vm = Vm::with_all()?;
1789 vm.import_code(
1790 "vm_bare_return_conflict",
1791 br#"
1792 pub fn run(flag) {
1793 if flag {
1794 return;
1795 }
1796 1
1797 }
1798 "#
1799 .to_vec(),
1800 )?;
1801
1802 let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1803 Ok(_) => panic!("expected mismatched return types to fail"),
1804 Err(err) => err,
1805 };
1806 assert!(format!("{err:#}").contains("返回类型不一致"));
1807 Ok(())
1808 }
1809
1810 #[test]
1811 fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1812 let vm = Vm::with_all()?;
1813 vm.import_code(
1814 "vm_root_get_dynamic_concat",
1815 br#"
1816 pub fn get_action(req) {
1817 root::get("local/game/panel_actions/" + req.idx)
1818 }
1819 "#
1820 .to_vec(),
1821 )?;
1822
1823 root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1824 let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1825 assert_eq!(compiled.ret_ty(), &Type::Any);
1826 let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1827 let req = dynamic::map!("idx"=> 7i64);
1828 let result = unsafe { &*get_action(&req) };
1829
1830 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1831 Ok(())
1832 }
1833
1834 #[test]
1835 fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1836 let vm = Vm::with_all()?;
1837 vm.import_code(
1838 "vm_registered_panel_action",
1839 br#"
1840 pub fn panel_action(req) {
1841 root::get("local/game/panel_actions/" + req.idx)
1842 }
1843
1844 pub fn register() {
1845 root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1846 }
1847 "#
1848 .to_vec(),
1849 )?;
1850
1851 let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1852 assert_eq!(compiled.ret_ty(), &Type::Bool);
1853 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1854 assert!(register());
1855 Ok(())
1856 }
1857
1858 #[test]
1859 fn std_spawn_runs_named_function_with_tuple_args() -> anyhow::Result<()> {
1860 let zero_path = "local/vm_std_spawn/zero";
1861 let sum_path = "local/vm_std_spawn/sum";
1862 let closure_path = "local/vm_std_spawn/closure";
1863 let closure_vars_path = "local/vm_std_spawn/closure_vars";
1864 let _ = root::remove(zero_path);
1865 let _ = root::remove(sum_path);
1866 let _ = root::remove(closure_path);
1867 let _ = root::remove(closure_vars_path);
1868 let vm = Vm::with_all()?;
1869 vm.import_code(
1870 "vm_std_spawn",
1871 br#"
1872 pub fn zero() {
1873 root::add("local/vm_std_spawn/zero", 1);
1874 }
1875
1876 pub fn job(left, right) {
1877 root::add("local/vm_std_spawn/sum", left + right);
1878 }
1879
1880 pub fn start_zero() {
1881 spawn("vm_std_spawn::zero", ())
1882 }
1883
1884 pub fn start_sum() {
1885 spawn("vm_std_spawn::job", (10, 20))
1886 }
1887
1888 pub fn start_closure() {
1889 spawn(|x, y| {
1890 root::add("local/vm_std_spawn/closure", x + y);
1891 }, (3, 4))
1892 }
1893
1894 pub fn start_closure_vars() {
1895 let x = 5;
1896 let y = 6;
1897 spawn(|left, right| {
1898 root::add("local/vm_std_spawn/closure_vars", left + right);
1899 }, (x, y))
1900 }
1901 "#
1902 .to_vec(),
1903 )?;
1904
1905 let compiled = vm.get_fn("vm_std_spawn::start_zero", &[])?;
1906 assert_eq!(compiled.ret_ty(), &Type::Bool);
1907 let start_zero: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1908 assert!(start_zero());
1909
1910 let compiled = vm.get_fn("vm_std_spawn::start_sum", &[])?;
1911 assert_eq!(compiled.ret_ty(), &Type::Bool);
1912 let start_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1913 assert!(start_sum());
1914
1915 let compiled = vm.get_fn("vm_std_spawn::start_closure", &[])?;
1916 assert_eq!(compiled.ret_ty(), &Type::Bool);
1917 let start_closure: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1918 assert!(start_closure());
1919
1920 let compiled = vm.get_fn("vm_std_spawn::start_closure_vars", &[])?;
1921 assert_eq!(compiled.ret_ty(), &Type::Bool);
1922 let start_closure_vars: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1923 assert!(start_closure_vars());
1924
1925 for _ in 0..50 {
1926 let zero_done = root::get(zero_path).ok().and_then(|value| value.as_int()) == Some(1);
1927 let sum_done = root::get(sum_path).ok().and_then(|value| value.as_int()) == Some(30);
1928 let closure_done = root::get(closure_path).ok().and_then(|value| value.as_int()) == Some(7);
1929 let closure_vars_done = root::get(closure_vars_path).ok().and_then(|value| value.as_int()) == Some(11);
1930 if zero_done && sum_done && closure_done && closure_vars_done {
1931 return Ok(());
1932 }
1933 std::thread::sleep(std::time::Duration::from_millis(10));
1934 }
1935
1936 anyhow::bail!("spawned jobs did not write expected results");
1937 }
1938
1939 #[test]
1940 fn native_can_save_and_later_call_closure_callback() -> anyhow::Result<()> {
1941 static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
1942
1943 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1944 if callback.is_null() {
1945 return false;
1946 }
1947 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1948 return false;
1949 };
1950 *SAVED_CALLBACK.lock() = Some(callback);
1951 true
1952 }
1953
1954 let path = "local/vm_callback/result";
1955 let _ = root::remove(path);
1956 *SAVED_CALLBACK.lock() = None;
1957
1958 let vm = Vm::with_all()?;
1959 vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
1960 vm.import_code(
1961 "vm_callback",
1962 br#"
1963 pub fn register() {
1964 let n = 41;
1965 callback_test::save(|| {
1966 root::add("local/vm_callback/result", n + 1);
1967 true
1968 })
1969 }
1970 "#
1971 .to_vec(),
1972 )?;
1973
1974 let compiled = vm.get_fn("vm_callback::register", &[])?;
1975 assert_eq!(compiled.ret_ty(), &Type::Bool);
1976 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1977 assert!(register());
1978 assert!(root::get(path).is_err());
1979
1980 let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
1981 let result = callback.call0()?;
1982 assert_eq!(result.as_bool(), Some(true));
1983 assert_eq!(root::get(path)?.as_int(), Some(42));
1984 Ok(())
1985 }
1986
1987 #[test]
1988 fn closure_captures_share_state_between_callbacks() -> anyhow::Result<()> {
1989 static SAVED_CALLBACKS: parking_lot::Mutex<Vec<ZustCallback>> = parking_lot::Mutex::new(Vec::new());
1990
1991 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1992 if callback.is_null() {
1993 return false;
1994 }
1995 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1996 return false;
1997 };
1998 SAVED_CALLBACKS.lock().push(callback);
1999 true
2000 }
2001
2002 SAVED_CALLBACKS.lock().clear();
2003
2004 let vm = Vm::with_all()?;
2005 vm.add_native_module_ptr("capture_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
2006 vm.import_code(
2007 "vm_shared_capture",
2008 br#"
2009 pub fn register() {
2010 let state = {};
2011 state.drag_kind = 0;
2012 capture_test::save(|| {
2013 state.drag_kind = 2;
2014 true
2015 });
2016 capture_test::save(|| {
2017 state.drag_kind
2018 })
2019 }
2020 "#
2021 .to_vec(),
2022 )?;
2023
2024 let register = vm.get_fn("vm_shared_capture::register", &[])?;
2025 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2026 assert!(register());
2027
2028 let (writer, reader) = {
2029 let saved = SAVED_CALLBACKS.lock();
2030 assert_eq!(saved.len(), 2);
2031 (saved[0].clone(), saved[1].clone())
2032 };
2033 assert_eq!(reader.call0()?.as_int(), Some(0));
2034 assert_eq!(writer.call0()?.as_bool(), Some(true));
2035 assert_eq!(reader.call0()?.as_int(), Some(2));
2036 Ok(())
2037 }
2038
2039 #[test]
2040 fn native_can_save_and_later_call_named_function_callback() -> anyhow::Result<()> {
2041 static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2042
2043 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
2044 if callback.is_null() {
2045 return false;
2046 }
2047 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2048 return false;
2049 };
2050 *SAVED_CALLBACK.lock() = Some(callback);
2051 true
2052 }
2053
2054 let path = "local/vm_named_callback/result";
2055 let _ = root::remove(path);
2056 *SAVED_CALLBACK.lock() = None;
2057
2058 let vm = Vm::with_all()?;
2059 vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
2060 vm.import_code(
2061 "vm_named_callback",
2062 br#"
2063 pub fn on_result() {
2064 root::add("local/vm_named_callback/result", "done");
2065 true
2066 }
2067
2068 pub fn register() {
2069 callback_test::save(on_result)
2070 }
2071 "#
2072 .to_vec(),
2073 )?;
2074
2075 let register = vm.get_fn("vm_named_callback::register", &[])?;
2076 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2077 assert!(register());
2078 assert!(root::get(path).is_err());
2079
2080 let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
2081 assert_eq!(callback.call1(dynamic::map!("text"=> "done"))?.as_bool(), Some(true));
2082 assert_eq!(root::get(path)?.as_str(), "done");
2083 Ok(())
2084 }
2085
2086 #[test]
2087 fn native_callback_can_receive_later_dynamic_args() -> anyhow::Result<()> {
2088 static SAVED_PATH_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2089 static SAVED_SUM_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2090
2091 extern "C" fn save_path_callback(callback: *const Dynamic) -> bool {
2092 if callback.is_null() {
2093 return false;
2094 }
2095 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2096 return false;
2097 };
2098 *SAVED_PATH_CALLBACK.lock() = Some(callback);
2099 true
2100 }
2101
2102 extern "C" fn save_sum_callback(callback: *const Dynamic) -> bool {
2103 if callback.is_null() {
2104 return false;
2105 }
2106 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2107 return false;
2108 };
2109 *SAVED_SUM_CALLBACK.lock() = Some(callback);
2110 true
2111 }
2112
2113 let path_result = "local/vm_callback/path";
2114 let sum_result = "local/vm_callback/sum8";
2115 let _ = root::remove(path_result);
2116 let _ = root::remove(sum_result);
2117 *SAVED_PATH_CALLBACK.lock() = None;
2118 *SAVED_SUM_CALLBACK.lock() = None;
2119
2120 let vm = Vm::with_all()?;
2121 vm.add_native_module_ptr("callback_test", "save_path", &[Type::Any], Type::Bool, save_path_callback as *const u8)?;
2122 vm.add_native_module_ptr("callback_test", "save_sum", &[Type::Any], Type::Bool, save_sum_callback as *const u8)?;
2123 vm.import_code(
2124 "vm_callback_args",
2125 br#"
2126 pub fn register_path() {
2127 let key = "local/vm_callback/path";
2128 callback_test::save_path(|path| {
2129 root::add(key, path);
2130 true
2131 })
2132 }
2133
2134 pub fn register_sum() {
2135 callback_test::save_sum(|a, b, c, d, e, f, g, h| {
2136 root::add("local/vm_callback/sum8", a + b + c + d + e + f + g + h);
2137 true
2138 })
2139 }
2140 "#
2141 .to_vec(),
2142 )?;
2143
2144 let register_path = vm.get_fn("vm_callback_args::register_path", &[])?;
2145 let register_path: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_path.ptr()) };
2146 assert!(register_path());
2147
2148 let register_sum = vm.get_fn("vm_callback_args::register_sum", &[])?;
2149 let register_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_sum.ptr()) };
2150 assert!(register_sum());
2151
2152 let path_callback = SAVED_PATH_CALLBACK.lock().clone().expect("path callback should be saved");
2153 assert_eq!(path_callback.call1(Dynamic::from("picked.txt"))?.as_bool(), Some(true));
2154 assert_eq!(root::get(path_result)?.as_str(), "picked.txt");
2155
2156 let sum_callback = SAVED_SUM_CALLBACK.lock().clone().expect("sum callback should be saved");
2157 let sum_args = (1i64..=8).map(Dynamic::from).collect();
2158 assert_eq!(sum_callback.call(sum_args)?.as_bool(), Some(true));
2159 assert_eq!(root::get(sum_result)?.as_int(), Some(36));
2160 Ok(())
2161 }
2162
2163 #[test]
2164 fn callback_with_16_explicit_args_and_captures() -> anyhow::Result<()> {
2165 static SAVED_SUM16: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2166
2167 extern "C" fn save_sum16(callback: *const Dynamic) -> bool {
2168 if callback.is_null() {
2169 return false;
2170 }
2171 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2172 return false;
2173 };
2174 *SAVED_SUM16.lock() = Some(callback);
2175 true
2176 }
2177
2178 let sum16_path = "local/vm_callback/sum16";
2179 let _ = root::remove(sum16_path);
2180 *SAVED_SUM16.lock() = None;
2181
2182 let vm = Vm::with_all()?;
2183 vm.add_native_module_ptr("callback_test", "save_sum16", &[Type::Any], Type::Bool, save_sum16 as *const u8)?;
2184 vm.import_code(
2185 "vm_callback_16_args",
2186 br#"
2187 pub fn register_sum16() {
2188 let prefix = "sum=";
2189 callback_test::save_sum16(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2190 let total = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p;
2191 root::add("local/vm_callback/sum16", prefix + total);
2192 true
2193 })
2194 }
2195 "#
2196 .to_vec(),
2197 )?;
2198
2199 let register = vm.get_fn("vm_callback_16_args::register_sum16", &[])?;
2200 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2201 assert!(register());
2202
2203 let callback = SAVED_SUM16.lock().clone().expect("sum16 callback saved");
2204 let args: Vec<Dynamic> = (1i64..=16).map(Dynamic::from).collect();
2205 assert_eq!(callback.call(args)?.as_bool(), Some(true));
2206 assert_eq!(root::get(sum16_path)?.as_str(), "sum=136");
2207 Ok(())
2208 }
2209
2210 #[test]
2211 fn spawn_closure_with_16_args() -> anyhow::Result<()> {
2212 let spawn16_path = "local/vm_spawn/spawn16";
2213 let _ = root::remove(spawn16_path);
2214
2215 let vm = Vm::with_all()?;
2216 vm.import_code(
2217 "vm_spawn_16_args",
2218 br#"
2219 pub fn start_spawn16() {
2220 spawn(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2221 root::add("local/vm_spawn/spawn16", a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p);
2222 }, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
2223 }
2224 "#
2225 .to_vec(),
2226 )?;
2227
2228 let compiled = vm.get_fn("vm_spawn_16_args::start_spawn16", &[])?;
2229 assert_eq!(compiled.ret_ty(), &Type::Bool);
2230 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2231 assert!(start());
2232
2233 for _ in 0..50 {
2234 if root::get(spawn16_path).ok().and_then(|v| v.as_int()) == Some(136) {
2235 return Ok(());
2236 }
2237 std::thread::sleep(std::time::Duration::from_millis(10));
2238 }
2239 anyhow::bail!("spawned job did not write expected result");
2240 }
2241
2242 #[test]
2243 fn spawn_native_closure_avoids_any_boxing() -> anyhow::Result<()> {
2244 let nat_path = "local/vm_spawn_native/result";
2245 let _ = root::remove(nat_path);
2246 let vm = Vm::with_all()?;
2247 vm.import_code(
2248 "vm_spawn_native",
2249 br#"
2250 pub fn start() {
2251 spawn(|x: i64, y: i64| {
2252 root::add("local/vm_spawn_native/result", x + y);
2253 }, (10i64, 20i64))
2254 }
2255 "#
2256 .to_vec(),
2257 )?;
2258 let compiled = vm.get_fn("vm_spawn_native::start", &[])?;
2259 assert_eq!(compiled.ret_ty(), &Type::Bool);
2260 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2261 assert!(start());
2262 for _ in 0..50 {
2263 if root::get(nat_path).ok().and_then(|v| v.as_int()) == Some(30) {
2264 return Ok(());
2265 }
2266 std::thread::sleep(std::time::Duration::from_millis(10));
2267 }
2268 anyhow::bail!("spawned native closure did not write expected result");
2269 }
2270
2271 #[test]
2272 fn multi_level_nested_closure_captures() -> anyhow::Result<()> {
2273 let vm = Vm::with_all()?;
2274 vm.import_code(
2275 "vm_multi_level_captures",
2276 br#"
2277 pub fn run() {
2278 let level1 = "L1";
2279 let level2 = "L2";
2280 |path: string| {
2281 let level3 = "L3";
2282 let inner = |suffix: string| {
2283 let level4 = "L4";
2284 |flag: bool| {
2285 if flag {
2286 level1 + "." + level2 + "." + level3 + "." + level4 + "." + path + suffix
2287 } else {
2288 "off"
2289 }
2290 }(true)
2291 };
2292 inner(".ext")
2293 }("file.txt")
2294 }
2295 "#
2296 .to_vec(),
2297 )?;
2298
2299 let compiled = vm.get_fn("vm_multi_level_captures::run", &[])?;
2300 assert_eq!(compiled.ret_ty(), &Type::Any);
2301 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2302 let result = unsafe { &*run() };
2303 assert_eq!(result.as_str(), "L1.L2.L3.L4.file.txt.ext");
2304 Ok(())
2305 }
2306
2307 #[test]
2308 fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
2309 let vm = Vm::with_all()?;
2310 vm.import_code(
2311 "vm_registered_string_concat",
2312 br#"
2313 pub fn send_panel(idx: i64) {
2314 let idx_key = "" + idx;
2315 idx_key
2316 }
2317 "#
2318 .to_vec(),
2319 )?;
2320
2321 assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
2322 Ok(())
2323 }
2324
2325 #[test]
2326 fn dynamic_method_error_reports_source_location() -> anyhow::Result<()> {
2327 let vm = Vm::with_all()?;
2328 vm.import_code(
2329 "vm_bad_dynamic_method",
2330 br#"
2331 pub fn main(value) {
2332 let out = "";
2333 out = out + value.fetch("name");
2334 }
2335 "#
2336 .to_vec(),
2337 )?;
2338
2339 let err = vm.get_fn_ptr("vm_bad_dynamic_method::main", &[Type::Any]).expect_err("bad dynamic method should fail to compile");
2340 let msg = format!("{err:#}");
2341 assert!(msg.contains("vm_bad_dynamic_method:4:"), "{msg}");
2342 assert!(msg.contains("`Any.fetch` 不是成员函数"), "{msg}");
2343 assert!(msg.contains(r#"out = out + value.fetch("name");"#), "{msg}");
2344 Ok(())
2345 }
2346
2347 #[test]
2348 fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
2349 fn echo_handler(msg: Dynamic) -> Dynamic {
2350 dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
2351 }
2352
2353 let vm = Vm::with_all()?;
2354 vm.import_code(
2355 "vm_root_send_idx_return",
2356 br#"
2357 pub fn call(req) {
2358 root::send_idx("local/send_idx_return_handlers", 0, req)
2359 }
2360 "#
2361 .to_vec(),
2362 )?;
2363
2364 root::add_list("local/send_idx_return_handlers")?;
2365 let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
2366 mount.push(name, root::Object::Native(echo_handler))?;
2367
2368 assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
2369 let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
2370 assert_eq!(compiled.ret_ty(), &Type::Any);
2371 let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2372 let req = dynamic::map!("id"=> 42i64);
2373 let result = unsafe { &*call(&req) };
2374
2375 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
2376 assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
2377 Ok(())
2378 }
2379
2380 #[test]
2381 fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
2382 let vm = Vm::with_all()?;
2383 vm.import_code(
2384 "vm_public_hotspots",
2385 br#"
2386 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2387 {
2388 path: action_map_path,
2389 panel_id: panel_id,
2390 action_id: action_id,
2391 id: hotspot.id
2392 }
2393 }
2394
2395 pub fn public_hotspots(idx, panel_id, hotspots) {
2396 let idx_key = "" + idx;
2397 let action_map_path = "local/game/panel_actions/" + idx_key;
2398
2399 let existing_action_map = root::get(action_map_path);
2400 if !existing_action_map.is_map() {
2401 root::add_map(action_map_path);
2402 }
2403
2404 if hotspots.is_map() {
2405 let public_items = {};
2406 for action_id in hotspots.keys() {
2407 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2408 }
2409 return public_items;
2410 }
2411
2412 let public_items = [];
2413 let i = 0;
2414 while i < hotspots.len() {
2415 let hotspot = hotspots.get_idx(i);
2416 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2417 public_items.push(item);
2418 i = i + 1;
2419 }
2420
2421 public_items
2422 }
2423 "#
2424 .to_vec(),
2425 )?;
2426
2427 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
2428 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
2429 Ok(())
2430 }
2431
2432 #[test]
2433 fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
2434 let vm = Vm::with_all()?;
2435 vm.import_code(
2436 "vm_send_panel_public_hotspots",
2437 br#"
2438 pub fn ok(value) {
2439 value
2440 }
2441
2442 pub fn panel_from_node(req) {
2443 {
2444 panel_id: req.panel_id,
2445 hotspots: req.hotspots
2446 }
2447 }
2448
2449 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2450 {
2451 path: action_map_path,
2452 panel_id: panel_id,
2453 action_id: action_id,
2454 id: hotspot.id
2455 }
2456 }
2457
2458 pub fn public_hotspots(idx, panel_id, hotspots) {
2459 let idx_key = "" + idx;
2460 let action_map_path = "local/game/panel_actions/" + idx_key;
2461
2462 let existing_action_map = root::get(action_map_path);
2463 if !existing_action_map.is_map() {
2464 root::add_map(action_map_path);
2465 }
2466
2467 if hotspots.is_map() {
2468 let public_items = {};
2469 for action_id in hotspots.keys() {
2470 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2471 }
2472 return public_items;
2473 }
2474
2475 let public_items = [];
2476 let i = 0;
2477 while i < hotspots.len() {
2478 let hotspot = hotspots.get_idx(i);
2479 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2480 public_items.push(item);
2481 i = i + 1;
2482 }
2483
2484 public_items
2485 }
2486
2487 pub fn send_panel(req) {
2488 let panel = req.panel;
2489 if !panel.is_map() {
2490 panel = panel_from_node(req);
2491 }
2492 if !panel.is_map() {
2493 return ok({
2494 id: 4,
2495 type: "panel_rejected",
2496 reason: "invalid panel"
2497 });
2498 }
2499 panel.id = 4;
2500 panel.idx = req.idx;
2501 if !panel.contains("type") {
2502 panel.type = "panel";
2503 }
2504 if panel.contains("hotspots") {
2505 panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
2506 }
2507 root::send_idx("local/ws", req.idx, panel);
2508 ok({
2509 id: 4,
2510 type: "panel",
2511 panel_id: panel.panel_id
2512 })
2513 }
2514 "#
2515 .to_vec(),
2516 )?;
2517
2518 let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
2519 assert_eq!(compiled.ret_ty(), &Type::Any);
2520 let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2521 let req = dynamic::map!(
2522 "idx"=> 7i64,
2523 "panel"=> dynamic::map!(
2524 "panel_id"=> "main",
2525 "hotspots"=> dynamic::map!(
2526 "open"=> dynamic::map!("id"=> "open")
2527 )
2528 )
2529 );
2530 let result = unsafe { &*send_panel(&req) };
2531
2532 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
2533 assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
2534 Ok(())
2535 }
2536
2537 #[test]
2538 fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
2539 let vm = Vm::with_all()?;
2540 vm.import_code(
2541 "vm_string_concat_map_key",
2542 br##"
2543 pub fn write_action(action_map, panel_id, action_id, action) {
2544 action_map[panel_id + "#" + action_id] = action;
2545 action_map[panel_id + "#" + action_id]
2546 }
2547 "##
2548 .to_vec(),
2549 )?;
2550
2551 let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
2552 let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2553 let action_map = dynamic::map!();
2554 let panel_id: Dynamic = "panel".into();
2555 let action_id: Dynamic = "open".into();
2556 let action = dynamic::map!("id"=> "open");
2557
2558 let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
2559
2560 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2561 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()));
2562 Ok(())
2563 }
2564
2565 #[test]
2566 fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
2567 let vm = Vm::with_all()?;
2568 vm.import_code(
2569 "vm_get_key_string_concat_key",
2570 br##"
2571 pub fn read_action(action_map, panel_id, action_id) {
2572 let action_key = panel_id + "#" + action_id;
2573 action_map.get_key(action_key)
2574 }
2575 "##
2576 .to_vec(),
2577 )?;
2578
2579 let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2580 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2581 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2582 let panel_id: Dynamic = "panel".into();
2583 let action_id: Dynamic = "open".into();
2584
2585 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2586
2587 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2588 Ok(())
2589 }
2590
2591 #[test]
2592 fn map_get_alias_matches_get_key() -> anyhow::Result<()> {
2593 let vm = Vm::with_all()?;
2594 vm.import_code(
2595 "vm_map_get_alias",
2596 br#"
2597 pub fn read_name(data) {
2598 data.get("name")
2599 }
2600
2601 pub fn read_missing(data) {
2602 data.get("missing")
2603 }
2604 "#
2605 .to_vec(),
2606 )?;
2607
2608 let data = dynamic::map!("name"=> "zust");
2609
2610 let compiled = vm.get_fn("vm_map_get_alias::read_name", &[Type::Any])?;
2611 assert_eq!(compiled.ret_ty(), &Type::Any);
2612 let read_name: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2613 let result = unsafe { &*read_name(&data) };
2614 assert_eq!(result.as_str(), "zust");
2615
2616 let compiled = vm.get_fn("vm_map_get_alias::read_missing", &[Type::Any])?;
2617 assert_eq!(compiled.ret_ty(), &Type::Any);
2618 let read_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2619 let result = unsafe { &*read_missing(&data) };
2620 assert!(result.is_null());
2621 Ok(())
2622 }
2623
2624 #[test]
2625 fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
2626 let vm = Vm::with_all()?;
2627 vm.import_code(
2628 "vm_get_key_helper_string_key",
2629 br##"
2630 pub fn make_action_key(panel_id, action_id) {
2631 panel_id + "#" + action_id
2632 }
2633
2634 pub fn read_action(action_map, panel_id, action_id) {
2635 let action_key = make_action_key(panel_id, action_id);
2636 let action = action_map.get_key(action_key);
2637 action
2638 }
2639 "##
2640 .to_vec(),
2641 )?;
2642
2643 let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2644 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2645 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2646 let panel_id: Dynamic = "panel".into();
2647 let action_id: Dynamic = "open".into();
2648
2649 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2650
2651 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2652 Ok(())
2653 }
2654
2655 #[test]
2656 fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
2657 let vm = Vm::with_all()?;
2658 vm.import_code(
2659 "vm_del_key_string_key",
2660 br##"
2661 pub fn remove_action(action_map, panel_id, action_id) {
2662 let action_key = panel_id + "#" + action_id;
2663 let removed = action_map.del_key(action_key);
2664 [removed, action_map.get_key(action_key)]
2665 }
2666 "##
2667 .to_vec(),
2668 )?;
2669
2670 let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
2671 let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2672 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2673 let panel_id: Dynamic = "panel".into();
2674 let action_id: Dynamic = "open".into();
2675
2676 let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
2677
2678 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
2679 assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
2680 assert!(action_map.get_dynamic("panel#open").is_none());
2681 Ok(())
2682 }
2683
2684 #[test]
2685 fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
2686 let vm = Vm::with_all()?;
2687 vm.import_code(
2688 "vm_dynamic_field_or",
2689 r#"
2690 pub fn direct_next() {
2691 let choice = {
2692 label: "颜色",
2693 next: "color"
2694 };
2695 choice.next
2696 }
2697
2698 pub fn bracket_next() {
2699 let choice = {
2700 label: "颜色",
2701 next: "color"
2702 };
2703 choice["next"]
2704 }
2705 "#
2706 .as_bytes()
2707 .to_vec(),
2708 )?;
2709
2710 let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
2711 assert_eq!(compiled.ret_ty(), &Type::Any);
2712 let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2713 assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
2714
2715 let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
2716 assert_eq!(compiled.ret_ty(), &Type::Any);
2717 let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2718 assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
2719 Ok(())
2720 }
2721
2722 #[test]
2723 fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
2724 let vm = Vm::with_all()?;
2725 vm.import_code(
2726 "vm_if_empty_object_branch",
2727 r#"
2728 pub fn first_note(steps) {
2729 let first = if steps.len() > 0 { steps[0] } else { {} };
2730 let first_note = if first.contains("note") { first.note } else { "fallback" };
2731 first_note
2732 }
2733
2734 pub fn first_ja(steps) {
2735 let first = if steps.len() > 0 { steps[0] } else { {} };
2736 if first.contains("ja") { first.ja } else { "すみません" }
2737 }
2738
2739 pub fn assign_first_note(steps) {
2740 let first = {};
2741 first = if steps.len() > 0 { steps[0] } else { {} };
2742 if first.contains("note") { first.note } else { "fallback" }
2743 }
2744 "#
2745 .as_bytes()
2746 .to_vec(),
2747 )?;
2748
2749 let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
2750 assert_eq!(compiled.ret_ty(), &Type::Str);
2751 let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2752
2753 let empty_steps = Dynamic::list(Vec::new());
2754 assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
2755
2756 let mut step = std::collections::BTreeMap::new();
2757 step.insert("note".into(), "hello".into());
2758 let steps = Dynamic::list(vec![Dynamic::map(step)]);
2759 assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
2760
2761 let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
2762 assert_eq!(compiled.ret_ty(), &Type::Any);
2763 let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2764 assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
2765
2766 let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
2767 assert_eq!(compiled.ret_ty(), &Type::Any);
2768 let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2769 assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
2770 assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
2771 Ok(())
2772 }
2773
2774 #[test]
2775 fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
2776 let vm = Vm::with_all()?;
2777 vm.import_code(
2778 "vm_tail_list_literal",
2779 r#"
2780 pub fn numbers() {
2781 [1, 2, 3]
2782 }
2783
2784 pub fn maps() {
2785 [
2786 {note: "first"},
2787 {note: "second"}
2788 ]
2789 }
2790
2791 pub fn object_with_maps() {
2792 {
2793 steps: [
2794 {note: "first"},
2795 {note: "second"}
2796 ]
2797 }
2798 }
2799
2800 pub fn return_maps() {
2801 return [
2802 {note: "first"},
2803 {note: "second"}
2804 ];
2805 }
2806
2807 pub fn return_maps_without_semicolon() {
2808 return [
2809 {note: "first"},
2810 {note: "second"}
2811 ]
2812 }
2813
2814 pub fn tail_bare_variable() {
2815 let value = [
2816 {note: "first"},
2817 {note: "second"}
2818 ];
2819 value
2820 }
2821
2822 pub fn return_bare_variable_without_semicolon() {
2823 let value = [
2824 {note: "first"},
2825 {note: "second"}
2826 ];
2827 return value
2828 }
2829
2830 pub fn tail_object_variable() {
2831 let result = {
2832 steps: [
2833 {note: "first"},
2834 {note: "second"}
2835 ]
2836 };
2837 result
2838 }
2839 "#
2840 .as_bytes()
2841 .to_vec(),
2842 )?;
2843
2844 let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
2845 assert_eq!(compiled.ret_ty(), &Type::Any);
2846 let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2847 let result = unsafe { &*numbers() };
2848 assert_eq!(result.len(), 3);
2849 assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
2850
2851 let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
2852 assert_eq!(compiled.ret_ty(), &Type::Any);
2853 let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2854 let result = unsafe { &*maps() };
2855 assert_eq!(result.len(), 2);
2856 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2857
2858 let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
2859 assert_eq!(compiled.ret_ty(), &Type::Any);
2860 let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2861 let result = unsafe { &*object_with_maps() };
2862 let steps = result.get_dynamic("steps").expect("steps");
2863 assert_eq!(steps.len(), 2);
2864 assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2865
2866 let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
2867 assert_eq!(compiled.ret_ty(), &Type::Any);
2868 let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2869 let result = unsafe { &*return_maps() };
2870 assert_eq!(result.len(), 2);
2871 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2872
2873 let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
2874 assert_eq!(compiled.ret_ty(), &Type::Any);
2875 let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2876 let result = unsafe { &*return_maps_without_semicolon() };
2877 assert_eq!(result.len(), 2);
2878 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2879
2880 let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
2881 assert_eq!(compiled.ret_ty(), &Type::Any);
2882 let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2883 let result = unsafe { &*tail_bare_variable() };
2884 assert_eq!(result.len(), 2);
2885 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2886
2887 let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
2888 assert_eq!(compiled.ret_ty(), &Type::Any);
2889 let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2890 let result = unsafe { &*return_bare_variable_without_semicolon() };
2891 assert_eq!(result.len(), 2);
2892 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2893
2894 let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
2895 assert_eq!(compiled.ret_ty(), &Type::Any);
2896 let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2897 let result = unsafe { &*tail_object_variable() };
2898 let steps = result.get_dynamic("steps").expect("steps");
2899 assert_eq!(steps.len(), 2);
2900 assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2901 Ok(())
2902 }
2903
2904 #[test]
2905 fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
2906 let vm = Vm::with_all()?;
2907 vm.import_code(
2908 "vm_returned_list_get_idx",
2909 r#"
2910 pub fn ids() {
2911 [
2912 "base",
2913 "2",
2914 "3"
2915 ]
2916 }
2917
2918 pub fn combinations() {
2919 let result = [];
2920 let values = ids();
2921 let idx = 0;
2922 while idx < values.len() {
2923 result.push(values.get_idx(idx));
2924 idx = idx + 1;
2925 }
2926 result
2927 }
2928 "#
2929 .as_bytes()
2930 .to_vec(),
2931 )?;
2932
2933 let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
2934 assert_eq!(compiled.ret_ty(), &Type::Any);
2935 let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2936 let result = unsafe { &*combinations() };
2937
2938 assert_eq!(result.len(), 3);
2939 assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
2940 assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
2941 Ok(())
2942 }
2943
2944 #[test]
2945 fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
2946 fn extra_page_literal(depth: usize) -> String {
2947 let mut value = "{leaf: \"done\"}".to_string();
2948 for idx in 0..depth {
2949 value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
2950 }
2951 value
2952 }
2953
2954 let extra = extra_page_literal(48);
2955 let code = format!(
2956 r#"
2957 pub fn script() {{
2958 return [
2959 {{ja: "一つ目", note: "first", extra: {extra}}},
2960 {{ja: "二つ目", note: "second", extra: {extra}}},
2961 {{ja: "三つ目", note: "third", extra: {extra}}}
2962 ]
2963 }}
2964 "#
2965 );
2966
2967 let vm = Vm::with_all()?;
2968 vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
2969 let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
2970 assert_eq!(compiled.ret_ty(), &Type::Any);
2971 let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2972 let result = unsafe { &*script() };
2973 assert_eq!(result.len(), 3);
2974 assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
2975 Ok(())
2976 }
2977
2978 #[test]
2979 fn native_import_uses_owning_vm() -> anyhow::Result<()> {
2980 let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
2981 std::fs::write(&module_path, "pub fn value() { 41 }")?;
2982 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
2983
2984 let vm1 = Vm::with_all()?;
2985 vm1.import_code(
2986 "vm_import_owner",
2987 format!(
2988 r#"
2989 pub fn run() {{
2990 import("vm_imported_owner", "{module_path}");
2991 }}
2992 "#
2993 )
2994 .into_bytes(),
2995 )?;
2996 let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
2997
2998 let vm2 = Vm::with_all()?;
2999 vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
3000 let _ = vm2.get_fn("vm_import_other::run", &[])?;
3001
3002 let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
3003 run();
3004
3005 assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
3006 assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
3007 Ok(())
3008 }
3009
3010 #[test]
3011 fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
3012 let vm = Vm::with_all()?;
3013 vm.import_code(
3014 "vm_object_last_call_field",
3015 r#"
3016 pub fn extra_page() {
3017 {
3018 title: "extra",
3019 pages: [
3020 {note: "nested"}
3021 ]
3022 }
3023 }
3024
3025 pub fn data() {
3026 return [
3027 {
3028 note: "first",
3029 choices: ["a", "b"],
3030 extras: extra_page()
3031 },
3032 {
3033 note: "second",
3034 choices: ["c"],
3035 extras: extra_page()
3036 }
3037 ]
3038 }
3039 "#
3040 .as_bytes()
3041 .to_vec(),
3042 )?;
3043
3044 let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
3045 assert_eq!(compiled.ret_ty(), &Type::Any);
3046 let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3047 let result = unsafe { &*data() };
3048 assert_eq!(result.len(), 2);
3049 let first = result.get_idx(0).expect("first step");
3050 assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
3051 Ok(())
3052 }
3053
3054 #[test]
3055 fn string_return_survives_scope_exit() -> anyhow::Result<()> {
3056 let vm = Vm::with_all()?;
3057 vm.import_code(
3058 "vm_string_return_scope",
3059 r#"
3060 pub fn source_root() {
3061 "../assets/character/男主角换装"
3062 }
3063
3064 pub fn binary_root() {
3065 "character_binary/男主角换装"
3066 }
3067
3068 pub fn runtime_binary_url() {
3069 "/" + binary_root()
3070 }
3071
3072 pub fn action_groups() {
3073 let root = source_root();
3074 let binary_url = runtime_binary_url();
3075 let binary_root = binary_root();
3076 [
3077 {
3078 id: "field_bottom",
3079 source_spine: root + "/战斗外/boy_b.spine",
3080 skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
3081 export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
3082 }
3083 ]
3084 }
3085 "#
3086 .as_bytes()
3087 .to_vec(),
3088 )?;
3089
3090 let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
3091 assert_eq!(compiled.ret_ty(), &Type::Str);
3092 let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3093 let source_root = unsafe { &*source_root() };
3094 assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
3095
3096 let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
3097 assert_eq!(compiled.ret_ty(), &Type::Any);
3098 let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3099 let groups = unsafe { &*action_groups() };
3100 let first = groups.get_idx(0).expect("first action group");
3101 assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
3102 assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
3103 Ok(())
3104 }
3105
3106 #[test]
3107 fn dynamic_string_add_uses_any_binary_fast_path() -> anyhow::Result<()> {
3108 let vm = Vm::with_all()?;
3109 vm.import_code(
3110 "vm_dynamic_string_add",
3111 br#"
3112 pub fn concat(left, right) {
3113 left + right
3114 }
3115 "#
3116 .to_vec(),
3117 )?;
3118
3119 let compiled = vm.get_fn("vm_dynamic_string_add::concat", &[Type::Any, Type::Any])?;
3120 assert_eq!(compiled.ret_ty(), &Type::Any);
3121 let concat: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3122 let left = Dynamic::from("hello");
3123 let right = Dynamic::from(" world");
3124 let result = unsafe { &*concat(&left, &right) };
3125 assert_eq!(result.as_str(), "hello world");
3126 Ok(())
3127 }
3128
3129 #[test]
3130 fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
3131 let vm = Vm::with_all()?;
3132 let model_count = 180;
3133 let combination_count = 90;
3134 let models = (0..model_count)
3135 .map(|idx| {
3136 format!(
3137 r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
3138 {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
3139 {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
3140 {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
3141 ]}}"#
3142 )
3143 })
3144 .collect::<Vec<_>>()
3145 .join(",\n");
3146 let combinations = (0..combination_count).map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#)).collect::<Vec<_>>().join(",\n");
3147 let code = format!(
3148 r#"
3149 pub fn source_root() {{
3150 "/美术资源/角色/少年/默认"
3151 }}
3152
3153 pub fn runtime_boy_url() {{
3154 "/cdn/runtime/角色/少年/少年.model.json"
3155 }}
3156
3157 pub fn parts() {{
3158 [
3159 {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
3160 {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
3161 {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
3162 ]
3163 }}
3164
3165 pub fn action_groups() {{
3166 {{
3167 idle: [
3168 {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
3169 {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
3170 ],
3171 move: [
3172 {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
3173 {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
3174 ]
3175 }}
3176 }}
3177
3178 pub fn default_model() {{
3179 {{
3180 id: "runtime_boy",
3181 name: "运行时少年",
3182 skins: [
3183 {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
3184 {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
3185 ],
3186 models: [
3187 {models}
3188 ]
3189 }}
3190 }}
3191
3192 pub fn first_nine_combinations() {{
3193 [
3194 {combinations}
3195 ]
3196 }}
3197
3198 pub fn config() {{
3199 {{
3200 source_root: source_root(),
3201 runtime_boy_url: runtime_boy_url(),
3202 parts: parts(),
3203 action_groups: action_groups(),
3204 default_model: default_model(),
3205 first_nine_combinations: first_nine_combinations()
3206 }}
3207 }}
3208
3209 pub fn start() {{
3210 root::add("local/vm_large_inline_call_object/config", {{
3211 source_root: source_root(),
3212 runtime_boy_url: runtime_boy_url(),
3213 parts: parts(),
3214 action_groups: action_groups(),
3215 default_model: default_model(),
3216 first_nine_combinations: first_nine_combinations()
3217 }})
3218 }}
3219 "#
3220 );
3221 vm.import_code("vm_large_inline_call_object", code.into_bytes())?;
3222
3223 let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
3224 assert_eq!(compiled.ret_ty(), &Type::Any);
3225 let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3226 let result = unsafe { &*config() };
3227 assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
3228 assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3229
3230 let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
3231 assert_eq!(compiled.ret_ty(), &Type::Bool);
3232 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3233 assert!(start());
3234 let saved = root::get("local/vm_large_inline_call_object/config")?;
3235 assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3236 Ok(())
3237 }
3238
3239 #[cfg(feature = "http")]
3240 #[test]
3241 fn http_serve_accepts_inline_config_map() -> anyhow::Result<()> {
3242 let vm = Vm::with_all()?;
3243 vm.import_code(
3244 "vm_http_serve_inline_config",
3245 br#"
3246 pub fn start() {
3247 let server = http::serve({host: "127.0.0.1:5192"});
3248 server
3249 }
3250 "#
3251 .to_vec(),
3252 )?;
3253
3254 let compiled = vm.get_fn("vm_http_serve_inline_config::start", &[])?;
3255 assert_eq!(compiled.ret_ty(), &Type::Any);
3256 Ok(())
3257 }
3258
3259 #[cfg(feature = "http")]
3260 #[test]
3261 fn http_serve_accepts_variable_and_quoted_static_key() -> anyhow::Result<()> {
3262 let vm = Vm::with_all()?;
3263 vm.import_code(
3264 "vm_http_serve_quoted_static",
3265 br#"
3266 pub fn start(server_addr) {
3267 let http_server = http::serve({
3268 host: server_addr,
3269 ws: true,
3270 upload: "upload",
3271 "static": {
3272 path: "/",
3273 dir: "public/local"
3274 }
3275 });
3276 http_server
3277 }
3278 "#
3279 .to_vec(),
3280 )?;
3281
3282 let compiled = vm.get_fn("vm_http_serve_quoted_static::start", &[Type::Any])?;
3283 assert_eq!(compiled.ret_ty(), &Type::Any);
3284 Ok(())
3285 }
3286
3287 #[cfg(all(feature = "http", feature = "llm"))]
3288 #[test]
3289 fn oss_helpers_accept_explicit_config() -> anyhow::Result<()> {
3290 let vm = Vm::with_all()?;
3291 vm.import_code(
3292 "vm_oss_explicit_config",
3293 br#"
3294 pub fn upload(oss, bytes) {
3295 oss::upload(oss, "llm/input/audio.wav", bytes)
3296 }
3297
3298 pub fn http_upload(oss, bytes) {
3299 http::upload(oss, "uploads/input.bin", bytes)
3300 }
3301
3302 pub fn link(oss, uploaded) {
3303 oss::signed_url(oss, {oss_url: uploaded, expires: 3600})
3304 }
3305 "#
3306 .to_vec(),
3307 )?;
3308
3309 assert_eq!(vm.get_fn("vm_oss_explicit_config::upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3310 assert_eq!(vm.get_fn("vm_oss_explicit_config::http_upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3311 assert_eq!(vm.get_fn("vm_oss_explicit_config::link", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3312 Ok(())
3313 }
3314
3315 #[cfg(feature = "http")]
3316 #[test]
3317 fn load_script_accepts_http_serve_inline_config() -> anyhow::Result<()> {
3318 let vm = Vm::with_all()?;
3319 let (_fn_ptr, ty) = vm.load(
3320 br#"
3321 let server_addr = "127.0.0.1:5192";
3322 let http_server = http::serve({
3323 host: server_addr,
3324 ws: true,
3325 upload: "upload",
3326 "static": {
3327 path: "/",
3328 dir: "public/local"
3329 }
3330 });
3331 http_server
3332 "#
3333 .to_vec(),
3334 "arg".into(),
3335 )?;
3336
3337 assert_eq!(ty, Type::Any);
3338 Ok(())
3339 }
3340
3341 #[test]
3342 fn load_script_resolves_import_before_compile() -> anyhow::Result<()> {
3343 let module_path = std::env::temp_dir().join(format!("zust_vm_load_import_{}.zs", std::process::id()));
3344 std::fs::write(&module_path, "pub fn init() { return {ok: true}; }")?;
3345 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
3346
3347 let vm = Vm::with_all()?;
3348 let (_fn_ptr, ty) = vm.load(
3349 format!(
3350 r#"
3351 import("create_scene", "{module_path}");
3352 create_scene::init();
3353 "#
3354 )
3355 .into_bytes(),
3356 "req".into(),
3357 )?;
3358
3359 assert_eq!(ty, Type::Void);
3360 Ok(())
3361 }
3362
3363 #[test]
3364 fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
3365 let vm = Vm::with_all()?;
3366 vm.import_code(
3367 "vm_gpu_layout",
3368 br#"
3369 pub struct Params {
3370 a: u32,
3371 b: u32,
3372 c: u32,
3373 }
3374 "#
3375 .to_vec(),
3376 )?;
3377
3378 let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
3379 assert_eq!(layout.size, 16);
3380 assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
3381
3382 let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
3383 let bytes = layout.pack_map(&value)?;
3384 assert_eq!(bytes.len(), 16);
3385 assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
3386 assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
3387 assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
3388
3389 let read = layout.unpack_map(&bytes)?;
3390 assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
3391 assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
3392 assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
3393 Ok(())
3394 }
3395
3396 #[test]
3397 fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
3398 let vm = Vm::with_all()?;
3399 vm.import_code(
3400 "vm_root_clone_bridge",
3401 br#"
3402 pub fn add_then_reuse(arg) {
3403 let user = {
3404 address: "test-wallet",
3405 points: 20
3406 };
3407 root::add("local/root-clone-bridge-user", user);
3408 user.points = user.points - 7;
3409 root::add("local/root-clone-bridge-user", user);
3410 {
3411 user: user,
3412 points: user.points
3413 }
3414 }
3415
3416 pub fn clone_then_mutate(arg) {
3417 let user = {
3418 profile: {
3419 points: 20
3420 }
3421 };
3422 let copied = user.clone();
3423 copied.profile.points = 13;
3424 user
3425 }
3426 "#
3427 .to_vec(),
3428 )?;
3429
3430 let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
3431 assert_eq!(compiled.ret_ty(), &Type::Any);
3432 let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3433 let arg = Dynamic::Null;
3434 let result = add_then_reuse(&arg);
3435 let result = unsafe { &*result };
3436
3437 assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
3438 let mut json = String::new();
3439 result.to_json(&mut json);
3440 assert!(json.contains("\"points\": 13"));
3441
3442 let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
3443 let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
3444 let result = clone_then_mutate(&arg);
3445 let result = unsafe { &*result };
3446 assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
3447 Ok(())
3448 }
3449
3450 struct CounterForTypedReceiver {
3451 value: i64,
3452 }
3453
3454 extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
3455 unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
3456 }
3457
3458 struct NavMapForFunctionArg;
3459
3460 extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
3461 Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
3462 }
3463
3464 #[derive(Debug, Default)]
3465 struct PropertyForwardingObject {
3466 values: parking_lot::RwLock<BTreeMap<String, Dynamic>>,
3467 }
3468
3469 impl CustomProperty for PropertyForwardingObject {
3470 fn get_key(&self, key: &str) -> Option<Dynamic> {
3471 self.values.read().get(key).cloned()
3472 }
3473
3474 fn set_key(&self, key: &str, value: Dynamic) -> bool {
3475 self.values.write().insert(key.to_string(), value);
3476 true
3477 }
3478 }
3479
3480 extern "C" fn property_forwarding_object_new() -> *const Dynamic {
3481 Box::into_raw(Box::new(Dynamic::custom_with_properties(PropertyForwardingObject::default())))
3482 }
3483
3484 #[test]
3485 fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
3486 let vm = Vm::with_all()?;
3487 vm.add_empty_type("Counter")?;
3488 let counter_ty = vm.get_symbol("Counter", Vec::new())?;
3489 vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
3490 vm.import_code(
3491 "vm_typed_receiver_method",
3492 br#"
3493 pub fn run(value) {
3494 value::<Counter>::get()
3495 }
3496 "#
3497 .to_vec(),
3498 )?;
3499
3500 let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
3501 assert_eq!(compiled.ret_ty(), &Type::I64);
3502 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3503 let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
3504
3505 assert_eq!(run(&value), 42);
3506 Ok(())
3507 }
3508
3509 #[test]
3510 fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3511 let vm = Vm::with_all()?;
3512 vm.add_empty_type("NavMap")?;
3513 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3514 vm.import_code(
3515 "vm_native_custom_arg",
3516 br#"
3517 pub fn add_nav_spawns(world, navmap) {
3518 navmap
3519 }
3520
3521 pub fn run(world) {
3522 let navmap = NavMap::new();
3523 let with_spawns = add_nav_spawns(world, navmap);
3524 with_spawns
3525 }
3526 "#
3527 .to_vec(),
3528 )?;
3529
3530 let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
3531 assert_eq!(compiled.ret_ty(), &Type::Any);
3532 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3533 let world = Dynamic::Null;
3534 let result = run(&world);
3535 let result = unsafe { &*result };
3536
3537 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3538 Ok(())
3539 }
3540
3541 #[test]
3542 fn any_field_assignment_forwards_to_custom_properties() -> anyhow::Result<()> {
3543 let vm = Vm::with_all()?;
3544 vm.add_empty_type("Dialog")?;
3545 vm.add_native_method_ptr("Dialog", "new", &[], Type::Any, property_forwarding_object_new as *const u8)?;
3546 vm.import_code(
3547 "vm_custom_property_forwarding",
3548 br#"
3549 pub fn run() {
3550 let dialog = Dialog::new();
3551 dialog.file_mode = 3;
3552 dialog.file_mode
3553 }
3554 "#
3555 .to_vec(),
3556 )?;
3557
3558 let compiled = vm.get_fn("vm_custom_property_forwarding::run", &[])?;
3559 assert_eq!(compiled.ret_ty(), &Type::Any);
3560 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3561 let result = unsafe { &*run() };
3562
3563 assert_eq!(result.as_int(), Some(3));
3564 Ok(())
3565 }
3566
3567 #[test]
3568 fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3569 let vm = Vm::with_all()?;
3570 vm.add_empty_type("NavMap")?;
3571 let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
3572 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3573 vm.import_code(
3574 "vm_native_custom_typed_arg",
3575 br#"
3576 pub fn add_nav_spawns(world, navmap) {
3577 navmap
3578 }
3579
3580 pub fn run(world) {
3581 let navmap: NavMap = NavMap::new();
3582 let with_spawns = add_nav_spawns(world, navmap);
3583 with_spawns
3584 }
3585 "#
3586 .to_vec(),
3587 )?;
3588
3589 let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
3590 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3591 let world = Dynamic::Null;
3592 let result = run(&world);
3593 let result = unsafe { &*result };
3594
3595 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3596 Ok(())
3597 }
3598
3599 #[test]
3602 fn dynamic_type_checks_on_null_and_primitive_values() -> anyhow::Result<()> {
3603 let vm = Vm::with_all()?;
3604 vm.import_code(
3605 "vm_dynamic_type_checks",
3606 br#"
3607 pub fn is_list_on_int() {
3608 let x = 42i64;
3609 x.is_list()
3610 }
3611
3612 pub fn is_map_on_int() {
3613 let x = 42i64;
3614 x.is_map()
3615 }
3616
3617 pub fn is_null_on_int() {
3618 let x = 42i64;
3619 x.is_null()
3620 }
3621 "#
3622 .to_vec(),
3623 )?;
3624
3625 let compiled = vm.get_fn("vm_dynamic_type_checks::is_list_on_int", &[])?;
3626 assert_eq!(compiled.ret_ty(), &Type::Bool);
3627 let is_list_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3628 assert!(!is_list_on_int());
3629
3630 let compiled = vm.get_fn("vm_dynamic_type_checks::is_map_on_int", &[])?;
3631 assert_eq!(compiled.ret_ty(), &Type::Bool);
3632 let is_map_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3633 assert!(!is_map_on_int());
3634
3635 let compiled = vm.get_fn("vm_dynamic_type_checks::is_null_on_int", &[])?;
3636 assert_eq!(compiled.ret_ty(), &Type::Bool);
3637 let is_null_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3638 assert!(!is_null_on_int());
3639 Ok(())
3640 }
3641
3642 #[test]
3643 fn void_and_null_are_false_in_boolean_context() -> anyhow::Result<()> {
3644 let vm = Vm::with_all()?;
3645 vm.import_code(
3646 "vm_void_bool_context",
3647 br#"
3648 pub fn run() {
3649 let items = [1i32, 2i32];
3650 let ok1 = !(items.push(3i32) && false);
3651 let ok2 = !(true && items.push(4i32));
3652 let ok3 = null || true;
3653 let ok4 = null || items.len() == 4;
3654 ok1 && ok2 && ok3 && ok4
3655 }
3656 "#
3657 .to_vec(),
3658 )?;
3659
3660 let compiled = vm.get_fn("vm_void_bool_context::run", &[])?;
3661 assert_eq!(compiled.ret_ty(), &Type::Bool);
3662 let run: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3663 assert!(run());
3664 Ok(())
3665 }
3666
3667 #[test]
3668 fn empty_for_loop_range_has_zero_iterations() -> anyhow::Result<()> {
3669 let vm = Vm::with_all()?;
3670 vm.import_code(
3671 "vm_empty_for_range",
3672 br#"
3673 pub fn empty_exclusive() {
3674 let count = 0i32;
3675 for i in 0..0 {
3676 count += i;
3677 }
3678 count
3679 }
3680
3681 pub fn single_inclusive_iteration() {
3682 let count = 0i32;
3683 for i in 5..=5 {
3684 count += i;
3685 }
3686 count
3687 }
3688 "#
3689 .to_vec(),
3690 )?;
3691
3692 let compiled = vm.get_fn("vm_empty_for_range::empty_exclusive", &[])?;
3694 assert_eq!(compiled.ret_ty(), &Type::I64);
3695 let empty_exclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3696 assert_eq!(empty_exclusive(), 0);
3697
3698 let compiled = vm.get_fn("vm_empty_for_range::single_inclusive_iteration", &[])?;
3699 assert_eq!(compiled.ret_ty(), &Type::I64);
3700 let single_inclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3701 assert_eq!(single_inclusive(), 5);
3702 Ok(())
3703 }
3704
3705 #[test]
3706 fn for_loop_range_accepts_dynamic_i64_bounds() -> anyhow::Result<()> {
3707 let vm = Vm::with_all()?;
3708 vm.import_code(
3709 "vm_dynamic_for_range",
3710 br#"
3711 pub fn main() {
3712 let view = {};
3713 view.grid_min_x = -2i64;
3714 view.grid_max_x = 2i64;
3715
3716 let end_x = view.grid_max_x + 1i64;
3717 let count = 0i64;
3718
3719 for x in view.grid_min_x..end_x {
3720 count += 1i64;
3721 }
3722
3723 count
3724 }
3725 "#
3726 .to_vec(),
3727 )?;
3728
3729 let compiled = vm.get_fn("vm_dynamic_for_range::main", &[])?;
3730 assert_eq!(compiled.ret_ty(), &Type::I64);
3731 let main: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3732 assert_eq!(main(), 5);
3733 Ok(())
3734 }
3735
3736 #[test]
3737 fn map_contains_key_on_non_existent_and_nested_keys() -> anyhow::Result<()> {
3738 let vm = Vm::with_all()?;
3739 vm.import_code(
3740 "vm_map_contains",
3741 br#"
3742 pub fn contains_existing(data) {
3743 data.contains("name")
3744 }
3745
3746 pub fn contains_missing(data) {
3747 data.contains("nothing")
3748 }
3749 "#
3750 .to_vec(),
3751 )?;
3752
3753 let compiled = vm.get_fn("vm_map_contains::contains_existing", &[Type::Any])?;
3754 assert_eq!(compiled.ret_ty(), &Type::Bool);
3755 let contains_existing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3756 let data = dynamic::map!("name"=> "test");
3757 assert!(contains_existing(&data));
3758
3759 let compiled = vm.get_fn("vm_map_contains::contains_missing", &[Type::Any])?;
3760 assert_eq!(compiled.ret_ty(), &Type::Bool);
3761 let contains_missing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3762 assert!(!contains_missing(&data));
3763 Ok(())
3764 }
3765
3766 #[test]
3767 fn list_pop_on_empty_list_returns_null() -> anyhow::Result<()> {
3768 let vm = Vm::with_all()?;
3769 vm.import_code(
3770 "vm_pop_empty",
3771 br#"
3772 pub fn pop_new_list() {
3773 let items = [];
3774 let value = items.pop();
3775 let still_empty = items.len() == 0;
3776 {value: value, empty: still_empty}
3777 }
3778
3779 pub fn pop_until_empty() {
3780 let items = [1i64, 2i64];
3781 items.pop();
3782 let last = items.pop();
3783 let drained = items.pop();
3784 {last: last, drained: drained}
3785 }
3786 "#
3787 .to_vec(),
3788 )?;
3789
3790 let compiled = vm.get_fn("vm_pop_empty::pop_new_list", &[])?;
3791 assert_eq!(compiled.ret_ty(), &Type::Any);
3792 let pop_new_list: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3793 let result = unsafe { &*pop_new_list() };
3794 assert!(result.get_dynamic("value").is_some_and(|v| v.is_null()));
3795 assert_eq!(result.get_dynamic("empty").and_then(|v| v.as_bool()), Some(true));
3796
3797 let compiled = vm.get_fn("vm_pop_empty::pop_until_empty", &[])?;
3798 assert_eq!(compiled.ret_ty(), &Type::Any);
3799 let pop_until_empty: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3800 let result = unsafe { &*pop_until_empty() };
3801 assert_eq!(result.get_dynamic("last").and_then(|v| v.as_int()), Some(1));
3802 assert!(result.get_dynamic("drained").is_some_and(|v| v.is_null()));
3803 Ok(())
3804 }
3805
3806 #[test]
3807 fn void_function_with_multiple_code_paths() -> anyhow::Result<()> {
3808 let vm = Vm::with_all()?;
3809 vm.import_code(
3810 "vm_void_multi_path",
3811 br#"
3812 pub fn log_if_positive(value: i64) {
3813 if value > 0 {
3814 print(value);
3815 return;
3816 }
3817 if value < 0 {
3818 print(-value);
3819 return;
3820 }
3821 print(0);
3822 }
3823 "#
3824 .to_vec(),
3825 )?;
3826
3827 let compiled = vm.get_fn("vm_void_multi_path::log_if_positive", &[Type::I64])?;
3828 assert!(compiled.ret_ty().is_void());
3829 Ok(())
3830 }
3831
3832 #[test]
3833 fn any_method_call_chain_on_returned_dynamic_value() -> anyhow::Result<()> {
3834 let vm = Vm::with_all()?;
3835 vm.import_code(
3836 "vm_any_method_chain",
3837 br#"
3838 pub fn get_tags(data) {
3839 let tags = data.tags;
3840 if tags.is_list() {
3841 return tags.len();
3842 }
3843 0
3844 }
3845 "#
3846 .to_vec(),
3847 )?;
3848
3849 let compiled = vm.get_fn("vm_any_method_chain::get_tags", &[Type::Any])?;
3850 assert_eq!(compiled.ret_ty(), &Type::I64);
3851 let get_tags: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3852 let data = dynamic::map!("tags"=> Dynamic::list(vec!["a".into(), "b".into(), "c".into()]));
3853 assert_eq!(get_tags(&data), 3);
3854
3855 let empty_data = Dynamic::Null;
3856 assert_eq!(get_tags(&empty_data), 0);
3857 Ok(())
3858 }
3859
3860 #[test]
3861 fn infers_any_arg_function_return_before_body_compile() -> anyhow::Result<()> {
3862 let vm = Vm::with_all()?;
3863 vm.import_code(
3864 "vm_infer_any_arg_return",
3865 br#"
3866 pub fn caller(candidate) {
3867 let center = polygon_center(candidate.visualPolygon);
3868 center[0]
3869 }
3870
3871 pub fn polygon_center(point_list) {
3872 let total_x = 0;
3873 let total_y = 0;
3874 let count = 0;
3875 if point_list.is_list() {
3876 for point in point_list {
3877 if point.is_list() && point.len() >= 2 {
3878 total_x += point[0];
3879 total_y += point[1];
3880 count += 1;
3881 }
3882 }
3883 }
3884 if count == 0 {
3885 return [0, 0];
3886 }
3887 [total_x / count, total_y / count]
3888 }
3889 "#
3890 .to_vec(),
3891 )?;
3892
3893 let compiled = vm.get_fn("vm_infer_any_arg_return::caller", &[Type::Any])?;
3894 assert_eq!(compiled.ret_ty(), &Type::Any);
3895 let caller: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3896 let candidate = dynamic::map!(
3897 "visualPolygon"=> Dynamic::list(vec![
3898 Dynamic::list(vec![2i64.into(), 4i64.into()]),
3899 Dynamic::list(vec![6i64.into(), 8i64.into()]),
3900 ])
3901 );
3902 let result = unsafe { &*caller(&candidate) };
3903 assert_eq!(result.as_int(), Some(4));
3904 Ok(())
3905 }
3906
3907 #[test]
3908 fn recursive_factorial_keeps_static_return_type() -> anyhow::Result<()> {
3909 let vm = Vm::with_all()?;
3910 vm.import_code(
3911 "vm_recursive_factorial",
3912 br#"
3913 fn factorial(n: i64) {
3914 if n <= 1 {
3915 return 1;
3916 }
3917 n * factorial(n - 1)
3918 }
3919
3920 pub fn run(n: i64) {
3921 factorial(n)
3922 }
3923 "#
3924 .to_vec(),
3925 )?;
3926
3927 let compiled = vm.get_fn("vm_recursive_factorial::run", &[Type::I64])?;
3928 assert_eq!(compiled.ret_ty(), &Type::I64);
3929 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3930 assert_eq!(run(5), 120);
3931 Ok(())
3932 }
3933
3934 #[test]
3935 fn explicit_const_generic_function_calls_generate_distinct_variants() -> anyhow::Result<()> {
3936 let vm = Vm::with_all()?;
3937 vm.import_code(
3938 "vm_generic_const_variants",
3939 br#"
3940 fn value<N>() {
3941 N
3942 }
3943
3944 pub fn two() {
3945 value::<2>()
3946 }
3947
3948 pub fn three() {
3949 value::<3>()
3950 }
3951 "#
3952 .to_vec(),
3953 )?;
3954
3955 let compiled = vm.get_fn("vm_generic_const_variants::two", &[])?;
3956 assert_eq!(compiled.ret_ty(), &Type::I32);
3957 let two: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3958 assert_eq!(two(), 2);
3959
3960 let compiled = vm.get_fn("vm_generic_const_variants::three", &[])?;
3961 assert_eq!(compiled.ret_ty(), &Type::I32);
3962 let three: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3963 assert_eq!(three(), 3);
3964 Ok(())
3965 }
3966
3967 #[test]
3968 fn generic_function_body_resolves_private_generic_helper_after_import() -> anyhow::Result<()> {
3969 let vm = Vm::with_all()?;
3970 vm.import_code(
3971 "vm_generic_private_helper",
3972 br#"
3973 fn helper<N>() {
3974 N
3975 }
3976
3977 pub fn bench<N>() {
3978 helper::<N>()
3979 }
3980 "#
3981 .to_vec(),
3982 )?;
3983
3984 let compiled = vm.get_fn_with_params("vm_generic_private_helper::bench", &[], &[Type::ConstInt(7)])?;
3985 assert_eq!(compiled.ret_ty(), &Type::I32);
3986 let run: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3987 assert_eq!(run(), 7);
3988 Ok(())
3989 }
3990
3991 #[test]
3992 fn const_generic_repeat_array_initializes_all_items() -> anyhow::Result<()> {
3993 let vm = Vm::with_all()?;
3994 vm.import_code(
3995 "vm_generic_repeat_array",
3996 br#"
3997 fn bench<N>() {
3998 let is_prime = [true; N];
3999 is_prime[0] = false;
4000 is_prime[1] = false;
4001 let count = 0i64;
4002 for p in 2i64..N {
4003 if is_prime[p] == true {
4004 count = count + 1;
4005 let step = p;
4006 let j = p * p;
4007 while j < N {
4008 is_prime[j] = false;
4009 j = j + step;
4010 }
4011 }
4012 }
4013 count
4014 }
4015
4016 pub fn run() {
4017 bench::<10>()
4018 }
4019
4020 pub fn run_1000() {
4021 bench::<1000>()
4022 }
4023
4024 pub fn run_100000() {
4025 bench::<100000>()
4026 }
4027 "#
4028 .to_vec(),
4029 )?;
4030
4031 let compiled = vm.get_fn("vm_generic_repeat_array::run", &[])?;
4032 assert_eq!(compiled.ret_ty(), &Type::I64);
4033 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4034 assert_eq!(run(), 4);
4035
4036 let compiled = vm.get_fn("vm_generic_repeat_array::run_1000", &[])?;
4037 assert_eq!(compiled.ret_ty(), &Type::I64);
4038 let run_1000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4039 assert_eq!(run_1000(), 168);
4040
4041 let compiled = vm.get_fn("vm_generic_repeat_array::run_100000", &[])?;
4042 assert_eq!(compiled.ret_ty(), &Type::I64);
4043 let run_100000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4044 assert_eq!(run_100000(), 9592);
4045 Ok(())
4046 }
4047
4048 #[test]
4049 fn repeat_array_initializes_scalar_patterns() -> anyhow::Result<()> {
4050 let vm = Vm::with_all()?;
4051 vm.import_code(
4052 "vm_repeat_scalar_patterns",
4053 br#"
4054 pub fn count_true() {
4055 let items = [true; 100000];
4056 let count = 0i64;
4057 for idx in 0i64..100000 {
4058 if items[idx] == true {
4059 count = count + 1;
4060 }
4061 }
4062 count
4063 }
4064
4065 pub fn i32_pair() {
4066 let items = [-7i32; 1000];
4067 items[0i64] + items[999i64]
4068 }
4069
4070 pub fn i64_pair() {
4071 let items = [1234567890123i64; 1000];
4072 items[0i64] + items[999i64]
4073 }
4074
4075 pub fn f64_pair() {
4076 let items = [1.5f64; 1000];
4077 items[0i64] + items[999i64]
4078 }
4079 "#
4080 .to_vec(),
4081 )?;
4082
4083 let compiled = vm.get_fn("vm_repeat_scalar_patterns::count_true", &[])?;
4084 assert_eq!(compiled.ret_ty(), &Type::I64);
4085 let count_true: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4086 assert_eq!(count_true(), 100000);
4087
4088 let compiled = vm.get_fn("vm_repeat_scalar_patterns::i32_pair", &[])?;
4089 assert_eq!(compiled.ret_ty(), &Type::I32);
4090 let i32_pair: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4091 assert_eq!(i32_pair(), -14);
4092
4093 let compiled = vm.get_fn("vm_repeat_scalar_patterns::i64_pair", &[])?;
4094 assert_eq!(compiled.ret_ty(), &Type::I64);
4095 let i64_pair: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4096 assert_eq!(i64_pair(), 2469135780246);
4097
4098 let compiled = vm.get_fn("vm_repeat_scalar_patterns::f64_pair", &[])?;
4099 assert_eq!(compiled.ret_ty(), &Type::F64);
4100 let f64_pair: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4101 assert_eq!(f64_pair(), 3.0);
4102 Ok(())
4103 }
4104
4105 #[test]
4106 fn bool_array_store_normalizes_condition_values() -> anyhow::Result<()> {
4107 let vm = Vm::with_all()?;
4108 vm.import_code(
4109 "vm_bool_array_store",
4110 br#"
4111 pub fn run() {
4112 let items = [false; 4];
4113 items[1] = 3i64 > 2i64;
4114 items[2] = 3i64 < 2i64;
4115 if items[1] == true && items[2] == false {
4116 1i64
4117 } else {
4118 0i64
4119 }
4120 }
4121 "#
4122 .to_vec(),
4123 )?;
4124
4125 let compiled = vm.get_fn("vm_bool_array_store::run", &[])?;
4126 assert_eq!(compiled.ret_ty(), &Type::I64);
4127 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4128 assert_eq!(run(), 1);
4129 Ok(())
4130 }
4131
4132 #[test]
4133 fn bool_array_large_sequential_writes() -> anyhow::Result<()> {
4134 let vm = Vm::with_all()?;
4135 vm.import_code(
4136 "vm_bool_array_large_writes",
4137 br#"
4138 pub fn run() {
4139 let items = [true; 100000];
4140 for idx in 0i64..100000 {
4141 items[idx] = false;
4142 }
4143 let count = 0i64;
4144 for idx in 0i64..100000 {
4145 if items[idx] == false {
4146 count = count + 1;
4147 }
4148 }
4149 count
4150 }
4151 "#
4152 .to_vec(),
4153 )?;
4154
4155 let compiled = vm.get_fn("vm_bool_array_large_writes::run", &[])?;
4156 assert_eq!(compiled.ret_ty(), &Type::I64);
4157 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4158 assert_eq!(run(), 100000);
4159 Ok(())
4160 }
4161
4162 #[test]
4163 fn bool_array_sieve_style_indices_stay_in_bounds() -> anyhow::Result<()> {
4164 let vm = Vm::with_all()?;
4165 vm.import_code(
4166 "vm_bool_array_sieve_indices",
4167 br#"
4168 pub fn run() {
4169 let items = [true; 100000];
4170 let writes = 0i64;
4171 for p in 2i64..100000 {
4172 let step = p;
4173 let j = p * p;
4174 while j < 100000 {
4175 items[j] = false;
4176 writes = writes + 1;
4177 j = j + step;
4178 }
4179 }
4180 writes
4181 }
4182 "#
4183 .to_vec(),
4184 )?;
4185
4186 let compiled = vm.get_fn("vm_bool_array_sieve_indices::run", &[])?;
4187 assert_eq!(compiled.ret_ty(), &Type::I64);
4188 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4189 assert!(run() > 0);
4190 Ok(())
4191 }
4192
4193 #[test]
4194 fn sieve_style_indices_compute_in_bounds_without_array_write() -> anyhow::Result<()> {
4195 let vm = Vm::with_all()?;
4196 vm.import_code(
4197 "vm_sieve_indices_no_write",
4198 br#"
4199 pub fn run() {
4200 let max_j = 0i64;
4201 for p in 2i64..100000 {
4202 let step = p;
4203 let j = p * p;
4204 while j < 100000 {
4205 if j < 0i64 {
4206 return -1i64;
4207 }
4208 if j > max_j {
4209 max_j = j;
4210 }
4211 j = j + step;
4212 }
4213 }
4214 max_j
4215 }
4216 "#
4217 .to_vec(),
4218 )?;
4219
4220 let compiled = vm.get_fn("vm_sieve_indices_no_write::run", &[])?;
4221 assert_eq!(compiled.ret_ty(), &Type::I64);
4222 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4223 assert_eq!(run(), 99999);
4224 Ok(())
4225 }
4226
4227 #[test]
4228 fn dynamic_list_index_sum_uses_static_accumulator_type() -> anyhow::Result<()> {
4229 let vm = Vm::with_all()?;
4230 vm.import_code(
4231 "vm_dynamic_index_sum",
4232 br#"
4233 pub fn sum_list(n: i64) {
4234 let l = [];
4235 for i in 0..n {
4236 l.push(i);
4237 }
4238 let sum = 0i64;
4239 for j in 0..n {
4240 sum = sum + l[j];
4241 }
4242 sum
4243 }
4244 "#
4245 .to_vec(),
4246 )?;
4247
4248 let compiled = vm.get_fn("vm_dynamic_index_sum::sum_list", &[Type::I64])?;
4249 let sum_list_id = vm.jit.write().compiler.symbols.get_id("vm_dynamic_index_sum::sum_list")?;
4250 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_list_id, &[], &[Type::I64]);
4251 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I64)), "local type hints: {:?}", hints);
4252 assert_eq!(compiled.ret_ty(), &Type::I64);
4253 let sum_list: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4254 assert_eq!(sum_list(1000), 499500);
4255 Ok(())
4256 }
4257
4258 #[test]
4259 fn loop_pushed_list_is_typed_vector() -> anyhow::Result<()> {
4260 let vm = Vm::with_all()?;
4261 vm.import_code(
4262 "vm_loop_pushed_list",
4263 br#"
4264 pub fn make(n: i64) {
4265 let l = [];
4266 for i in 0..n {
4267 l.push(i);
4268 }
4269 l
4270 }
4271 "#
4272 .to_vec(),
4273 )?;
4274 let compiled = vm.get_fn("vm_loop_pushed_list::make", &[Type::I64])?;
4275 let make: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4276 let result = unsafe { &*make(3) };
4277 assert!(matches!(result, Dynamic::VecI64(v) if v == &vec![0, 1, 2]), "expected flat VecI64, got: {:?}", result);
4278 Ok(())
4279 }
4280
4281 #[test]
4282 fn inferred_empty_list_uses_typed_dynamic_vector() -> anyhow::Result<()> {
4283 let vm = Vm::with_all()?;
4284 vm.import_code(
4285 "vm_inferred_typed_list",
4286 br#"
4287 pub fn make() {
4288 let l = [];
4289 l.push(1i64);
4290 l
4291 }
4292 "#
4293 .to_vec(),
4294 )?;
4295
4296 let compiled = vm.get_fn("vm_inferred_typed_list::make", &[])?;
4297 assert_eq!(compiled.ret_ty(), &Type::Any);
4298 let make: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4299 let result = unsafe { &*make() };
4300 assert!(matches!(result, Dynamic::VecI64(values) if values == &vec![1]), "result: {:?}", result);
4301 Ok(())
4302 }
4303
4304 #[test]
4305 fn for_in_iterates_list_filled_in_same_function() -> anyhow::Result<()> {
4306 let vm = Vm::with_all()?;
4307 vm.import_code(
4308 "vm_for_in_local_pushed_list",
4309 br#"
4310 pub fn sum_i32_items() {
4311 let items = [];
4312 items.push(6000i32);
4313 items.push(4000i32);
4314 let total = 0i32;
4315 for item in items {
4316 total += item;
4317 }
4318 total
4319 }
4320
4321 pub fn sum_split_bps() {
4322 let splits = [];
4323 splits.push({ bps: "6000" });
4324 splits.push({ bps: 4000 });
4325 let total = 0i32;
4326 let count = 0i32;
4327 for split in splits {
4328 total += split.bps as i32;
4329 count += 1i32;
4330 }
4331 total + count
4332 }
4333 "#
4334 .to_vec(),
4335 )?;
4336
4337 let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_i32_items", &[])?;
4338 assert_eq!(compiled.ret_ty(), &Type::I32);
4339 let sum_i32_items: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4340 assert_eq!(sum_i32_items(), 10000);
4341
4342 let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_split_bps", &[])?;
4343 assert_eq!(compiled.ret_ty(), &Type::I32);
4344 let sum_split_bps: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4345 assert_eq!(sum_split_bps(), 10002);
4346 Ok(())
4347 }
4348
4349 #[test]
4350 fn inferred_list_shortcuts_cover_scalar_types() -> anyhow::Result<()> {
4351 let vm = Vm::with_all()?;
4352 vm.import_code(
4353 "vm_inferred_list_shortcuts",
4354 br#"
4355 pub fn second_bool() {
4356 let l = [];
4357 l.push(true);
4358 l.push(false);
4359 l[1]
4360 }
4361
4362 pub fn first_u8() {
4363 let l = [];
4364 l.push(7u8);
4365 l[0]
4366 }
4367
4368 pub fn sum_u8_for_in() {
4369 let l = [];
4370 l.push(7u8);
4371 l.push(8u8);
4372 let sum = 0i64;
4373 for item in l {
4374 sum = sum + item as i64;
4375 }
4376 sum
4377 }
4378
4379 pub fn count_bool_for_in() {
4380 let l = [];
4381 l.push(true);
4382 l.push(false);
4383 l.push(true);
4384 let count = 0i64;
4385 for item in l {
4386 if item {
4387 count += 1i64;
4388 }
4389 }
4390 count
4391 }
4392
4393 pub fn sum_i32(n: i64) {
4394 let l = [];
4395 for i in 0..n {
4396 l.push(i as i32);
4397 }
4398 let sum = 0i32;
4399 for j in 0..n {
4400 sum = sum + l[j];
4401 }
4402 sum
4403 }
4404
4405 pub fn sum_f32(n: i64) {
4406 let l = [];
4407 for i in 0..n {
4408 l.push(i as f32);
4409 }
4410 let sum = 0f32;
4411 for j in 0..n {
4412 sum = sum + l[j];
4413 }
4414 sum
4415 }
4416
4417 pub fn second_str() {
4418 let l = [];
4419 l.push("first");
4420 l.push("second");
4421 l[1]
4422 }
4423 "#
4424 .to_vec(),
4425 )?;
4426
4427 let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_bool", &[])?;
4428 let second_bool_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_bool")?;
4429 let hints = vm.jit.write().compiler.inferred_local_type_hints(second_bool_id, &[], &[]);
4430 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Bool)), "bool local type hints: {:?}", hints);
4431 assert_eq!(compiled.ret_ty(), &Type::Bool);
4432 let second_bool: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4433 assert!(!second_bool());
4434
4435 let compiled = vm.get_fn("vm_inferred_list_shortcuts::first_u8", &[])?;
4436 let first_u8_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::first_u8")?;
4437 let hints = vm.jit.write().compiler.inferred_local_type_hints(first_u8_id, &[], &[]);
4438 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::U8)), "u8 local type hints: {:?}", hints);
4439 assert_eq!(compiled.ret_ty(), &Type::U8);
4440 let first_u8: extern "C" fn() -> u8 = unsafe { std::mem::transmute(compiled.ptr()) };
4441 assert_eq!(first_u8(), 7);
4442
4443 let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_u8_for_in", &[])?;
4444 assert_eq!(compiled.ret_ty(), &Type::I64);
4445 let sum_u8_for_in: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4446 assert_eq!(sum_u8_for_in(), 15);
4447
4448 let compiled = vm.get_fn("vm_inferred_list_shortcuts::count_bool_for_in", &[])?;
4449 assert_eq!(compiled.ret_ty(), &Type::I64);
4450 let count_bool_for_in: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4451 assert_eq!(count_bool_for_in(), 2);
4452
4453 let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_i32", &[Type::I64])?;
4454 let sum_i32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_i32")?;
4455 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_i32_id, &[], &[Type::I64]);
4456 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I32)), "i32 local type hints: {:?}", hints);
4457 assert_eq!(compiled.ret_ty(), &Type::I32);
4458 let sum_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4459 assert_eq!(sum_i32(100), 4950);
4460
4461 let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_f32", &[Type::I64])?;
4462 let sum_f32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_f32")?;
4463 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_f32_id, &[], &[Type::I64]);
4464 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::F32)), "f32 local type hints: {:?}", hints);
4465 assert_eq!(compiled.ret_ty(), &Type::F32);
4466 let sum_f32: extern "C" fn(i64) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
4467 assert_eq!(sum_f32(10), 45.0);
4468
4469 let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_str", &[])?;
4470 let second_str_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_str")?;
4471 let hints = vm.jit.write().compiler.inferred_local_type_hints(second_str_id, &[], &[]);
4472 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Str)), "str local type hints: {:?}", hints);
4473 assert_eq!(compiled.ret_ty(), &Type::Str);
4474 let second_str: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4475 let result = unsafe { &*second_str() };
4476 assert_eq!(result.as_str(), "second");
4477 Ok(())
4478 }
4479
4480 #[test]
4481 fn inferred_list_supports_bracket_set_idx() -> anyhow::Result<()> {
4482 let vm = Vm::with_all()?;
4483 vm.import_code(
4484 "vm_inferred_list_set_idx",
4485 br#"
4486 pub fn swap_first_two() {
4487 let items = [];
4488 items.push(1i64);
4489 items.push(2i64);
4490 let j = 0i64;
4491 let a = items[j];
4492 let b = items[j + 1];
4493 items[j] = b;
4494 items[j + 1] = a;
4495 items[0] * 10i64 + items[1]
4496 }
4497
4498 pub fn replace_string() {
4499 let items = [];
4500 items.push("old");
4501 items[0] = "new";
4502 items[0]
4503 }
4504 "#
4505 .to_vec(),
4506 )?;
4507
4508 let compiled = vm.get_fn("vm_inferred_list_set_idx::swap_first_two", &[])?;
4509 assert_eq!(compiled.ret_ty(), &Type::I64);
4510 let swap_first_two: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4511 assert_eq!(swap_first_two(), 21);
4512
4513 let compiled = vm.get_fn("vm_inferred_list_set_idx::replace_string", &[])?;
4514 assert_eq!(compiled.ret_ty(), &Type::Str);
4515 let replace_string: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4516 let result = unsafe { &*replace_string() };
4517 assert_eq!(result.as_str(), "new");
4518 Ok(())
4519 }
4520
4521 #[test]
4522 fn root_get_returns_null_for_missing_key_which_compares_correctly() -> anyhow::Result<()> {
4523 let vm = Vm::with_all()?;
4524 vm.import_code(
4525 "vm_root_get_missing",
4526 br#"
4527 pub fn check_missing() {
4528 let existing = root::get("local/vm_root_get_missing_test");
4529 if existing.is_map() {
4530 return false;
4531 }
4532 true
4533 }
4534 "#
4535 .to_vec(),
4536 )?;
4537
4538 let compiled = vm.get_fn("vm_root_get_missing::check_missing", &[])?;
4539 assert_eq!(compiled.ret_ty(), &Type::Bool);
4540 let check_missing: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4541 assert!(check_missing());
4542 Ok(())
4543 }
4544
4545 #[test]
4546 fn map_get_key_on_null_map_returns_null() -> anyhow::Result<()> {
4547 let vm = Vm::with_all()?;
4548 vm.import_code(
4549 "vm_get_key_null_map",
4550 br#"
4551 pub fn get_key_null(data) {
4552 data.get_key("missing")
4553 }
4554 "#
4555 .to_vec(),
4556 )?;
4557
4558 let compiled = vm.get_fn("vm_get_key_null_map::get_key_null", &[Type::Any])?;
4559 assert_eq!(compiled.ret_ty(), &Type::Any);
4560 let get_key_null: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4561
4562 let data_map = dynamic::map!("exists"=> 1i64);
4563 let missing = unsafe { &*get_key_null(&data_map) };
4564 assert!(missing.is_null());
4565
4566 let null = Dynamic::Null;
4567 let result = unsafe { &*get_key_null(&null) };
4568 assert!(result.is_null());
4569 Ok(())
4570 }
4571
4572 #[test]
4573 fn keys_on_empty_map_returns_empty_list() -> anyhow::Result<()> {
4574 let vm = Vm::with_all()?;
4575 vm.import_code(
4576 "vm_keys_empty_map",
4577 br#"
4578 pub fn empty_map_keys() {
4579 let data = {};
4580 data.keys().len()
4581 }
4582 "#
4583 .to_vec(),
4584 )?;
4585
4586 let compiled = vm.get_fn("vm_keys_empty_map::empty_map_keys", &[])?;
4587 assert_eq!(compiled.ret_ty(), &Type::I32);
4588 let empty_map_keys: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4589 assert_eq!(empty_map_keys(), 0);
4590 Ok(())
4591 }
4592
4593 #[test]
4594 fn cast_between_all_integer_widths() -> anyhow::Result<()> {
4595 let vm = Vm::with_all()?;
4596 vm.import_code(
4597 "vm_cast_integer_widths",
4598 br#"
4599 pub fn i64_to_i32(value: i64) {
4600 value as i32
4601 }
4602
4603 pub fn i32_to_i64(value: i32) {
4604 value as i64
4605 }
4606
4607 pub fn u32_to_i64(value: u32) {
4608 value as i64
4609 }
4610 "#
4611 .to_vec(),
4612 )?;
4613
4614 let compiled = vm.get_fn("vm_cast_integer_widths::i64_to_i32", &[Type::I64])?;
4615 assert_eq!(compiled.ret_ty(), &Type::I32);
4616 let i64_to_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4617 assert_eq!(i64_to_i32(42), 42);
4618
4619 let compiled = vm.get_fn("vm_cast_integer_widths::i32_to_i64", &[Type::I32])?;
4620 assert_eq!(compiled.ret_ty(), &Type::I64);
4621 let i32_to_i64: extern "C" fn(i32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4622 assert_eq!(i32_to_i64(-1), -1);
4623
4624 let compiled = vm.get_fn("vm_cast_integer_widths::u32_to_i64", &[Type::U32])?;
4625 assert_eq!(compiled.ret_ty(), &Type::I64);
4626 let u32_to_i64: extern "C" fn(u32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4627 assert_eq!(u32_to_i64(42), 42);
4628 Ok(())
4629 }
4630
4631 #[test]
4632 fn boolean_literals_in_complex_expression_trees() -> anyhow::Result<()> {
4633 let vm = Vm::with_all()?;
4634 vm.import_code(
4635 "vm_complex_boolean",
4636 br#"
4637 pub fn exclusive_or(a: bool, b: bool) {
4638 (a && !b) || (!a && b)
4639 }
4640
4641 pub fn implies(a: bool, b: bool) {
4642 !a || b
4643 }
4644 "#
4645 .to_vec(),
4646 )?;
4647
4648 let compiled = vm.get_fn("vm_complex_boolean::exclusive_or", &[Type::Bool, Type::Bool])?;
4649 assert_eq!(compiled.ret_ty(), &Type::Bool);
4650 let exclusive_or: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4651 assert!(exclusive_or(true, false));
4652 assert!(exclusive_or(false, true));
4653 assert!(!exclusive_or(true, true));
4654 assert!(!exclusive_or(false, false));
4655
4656 let compiled = vm.get_fn("vm_complex_boolean::implies", &[Type::Bool, Type::Bool])?;
4657 assert_eq!(compiled.ret_ty(), &Type::Bool);
4658 let implies: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4659 assert!(implies(false, true));
4660 assert!(implies(false, false));
4661 assert!(implies(true, true));
4662 assert!(!implies(true, false));
4663 Ok(())
4664 }
4665
4666 #[test]
4667 fn concrete_struct_method_returning_self_type() -> anyhow::Result<()> {
4668 let vm = Vm::with_all()?;
4669 vm.import_code(
4670 "vm_struct_method_self",
4671 br#"
4672 pub struct Vec3 {
4673 x: f64,
4674 y: f64,
4675 z: f64,
4676 }
4677
4678 impl Vec3 {
4679 pub fn add(self: Vec3, other: Vec3) {
4680 Vec3{x: self.x + other.x, y: self.y + other.y, z: self.z + other.z}
4681 }
4682 }
4683
4684 pub fn run() {
4685 let v1 = Vec3{x: 1.0f64, y: 2.0f64, z: 3.0f64};
4686 let v2 = Vec3{x: 4.0f64, y: 5.0f64, z: 6.0f64};
4687 let sum = v1.add(v2);
4688 sum.x + sum.y + sum.z
4689 }
4690 "#
4691 .to_vec(),
4692 )?;
4693
4694 let compiled = vm.get_fn("vm_struct_method_self::run", &[])?;
4695 assert_eq!(compiled.ret_ty(), &Type::F64);
4696 let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4697 assert_eq!(run(), 21.0);
4698 Ok(())
4699 }
4700
4701 #[test]
4702 fn deep_nested_struct_access_with_multiple_field_levels() -> anyhow::Result<()> {
4703 let vm = Vm::with_all()?;
4704 vm.import_code(
4705 "vm_deep_nested_struct",
4706 br#"
4707 pub struct A {
4708 value: i64,
4709 }
4710
4711 pub struct B {
4712 a: A,
4713 }
4714
4715 pub struct C {
4716 b: B,
4717 }
4718
4719 pub fn direct_access() {
4720 let c = C{b: B{a: A{value: 99}}};
4721 c.b.a.value
4722 }
4723
4724 pub fn via_variable() {
4725 let c = C{b: B{a: A{value: 77}}};
4726 let b = c.b;
4727 let a = b.a;
4728 a.value
4729 }
4730 "#
4731 .to_vec(),
4732 )?;
4733
4734 let compiled = vm.get_fn("vm_deep_nested_struct::direct_access", &[])?;
4735 assert_eq!(compiled.ret_ty(), &Type::I64);
4736 let direct_access: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4737 assert_eq!(direct_access(), 99);
4738
4739 let compiled = vm.get_fn("vm_deep_nested_struct::via_variable", &[])?;
4740 assert_eq!(compiled.ret_ty(), &Type::I64);
4741 let via_variable: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4742 assert_eq!(via_variable(), 77);
4743 Ok(())
4744 }
4745
4746 #[test]
4747 fn array_index_with_dynamic_value_via_method() -> anyhow::Result<()> {
4748 let vm = Vm::with_all()?;
4749 vm.import_code(
4750 "vm_array_idx_dynamic",
4751 br#"
4752 pub fn get_by_idx(list, idx) {
4753 list.get_idx(idx)
4754 }
4755 "#
4756 .to_vec(),
4757 )?;
4758
4759 let compiled = vm.get_fn("vm_array_idx_dynamic::get_by_idx", &[Type::Any, Type::I64])?;
4760 assert_eq!(compiled.ret_ty(), &Type::Any);
4761 let get_by_idx: extern "C" fn(*const Dynamic, i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4762
4763 let list = Dynamic::list(vec!["a".into(), "b".into()]);
4764 let first = unsafe { &*get_by_idx(&list, 0) };
4765 assert_eq!(first.as_str(), "a");
4766
4767 let out = unsafe { &*get_by_idx(&list, 10) };
4768 assert!(out.is_null());
4769 Ok(())
4770 }
4771
4772 #[test]
4773 fn dynamic_field_access_with_optional_or_fallback() -> anyhow::Result<()> {
4774 let vm = Vm::with_all()?;
4775 vm.import_code(
4776 "vm_dynamic_or_fallback",
4777 br#"
4778 pub fn with_fallback(data) {
4779 if data.contains("name") { data.name } else { "unknown" }
4780 }
4781
4782 pub fn with_fallback_missing(data) {
4783 if data.contains("nickname") { data.nickname } else { "unnamed" }
4784 }
4785 "#
4786 .to_vec(),
4787 )?;
4788
4789 let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback", &[Type::Any])?;
4790 assert_eq!(compiled.ret_ty(), &Type::Any);
4791 let with_fallback: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4792 let data = dynamic::map!("name"=> "Alice");
4793 let result = unsafe { &*with_fallback(&data) };
4794 assert_eq!(result.as_str(), "Alice");
4795
4796 let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback_missing", &[Type::Any])?;
4797 let with_fallback_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4798 let result = unsafe { &*with_fallback_missing(&data) };
4799 assert_eq!(result.as_str(), "unnamed");
4800 Ok(())
4801 }
4802
4803 #[test]
4804 fn for_in_loop_iterates_over_list_and_map_directly() -> anyhow::Result<()> {
4805 let vm = Vm::with_all()?;
4806 vm.import_code(
4807 "vm_for_in_collection",
4808 br#"
4809 pub fn sum_list(items) {
4810 let total = 0i64;
4811 for item in items {
4812 total = total + 1;
4813 }
4814 total
4815 }
4816
4817 pub fn count_map_keys(data) {
4818 let count = 0i64;
4819 for key in data.keys() {
4820 count = count + 1;
4821 }
4822 count
4823 }
4824
4825 pub fn for_in_list_works(items) {
4826 let exists = false;
4827 for item in items {
4828 exists = true;
4829 }
4830 exists
4831 }
4832
4833 pub fn for_in_map_values_works(data) {
4834 let exists = false;
4835 for value in data {
4836 exists = true;
4837 }
4838 exists
4839 }
4840 "#
4841 .to_vec(),
4842 )?;
4843
4844 let compiled = vm.get_fn("vm_for_in_collection::sum_list", &[Type::Any])?;
4845 assert_eq!(compiled.ret_ty(), &Type::I64);
4846 let sum_list: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4847 let items = Dynamic::list(vec![Dynamic::from(1i64), Dynamic::from(2i64), Dynamic::from(3i64)]);
4848 assert_eq!(sum_list(&items), 3);
4849
4850 let data = dynamic::map!("x"=> 1i64, "y"=> 2i64);
4851 let compiled = vm.get_fn("vm_for_in_collection::count_map_keys", &[Type::Any])?;
4852 assert_eq!(compiled.ret_ty(), &Type::I64);
4853 let count_map_keys: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4854 assert_eq!(count_map_keys(&data), 2);
4855
4856 let compiled = vm.get_fn("vm_for_in_collection::for_in_list_works", &[Type::Any])?;
4857 assert_eq!(compiled.ret_ty(), &Type::Bool);
4858 let for_in_list_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4859 let empty = Dynamic::list(Vec::new());
4860 assert!(!for_in_list_works(&empty));
4861 assert!(for_in_list_works(&items));
4862
4863 let compiled = vm.get_fn("vm_for_in_collection::for_in_map_values_works", &[Type::Any])?;
4864 assert_eq!(compiled.ret_ty(), &Type::Bool);
4865 let for_in_map_values_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4866 let empty_map = dynamic::map!();
4867 assert!(!for_in_map_values_works(&empty_map));
4868 assert!(for_in_map_values_works(&data));
4869
4870 Ok(())
4871 }
4872
4873 #[test]
4874 fn concurrent_100_threads_no_memory_leak() -> anyhow::Result<()> {
4875 let vm = Vm::with_all()?;
4876 vm.import_code(
4877 "vm_stress",
4878 br#"
4879 pub fn heavy_alloc(idx: i64) {
4880 let items = [];
4881 let i = 0;
4882 while i < 50 {
4883 items.push({
4884 id: i + idx,
4885 name: "item-" + i,
4886 tags: ["tag-a", "tag-b", "tag-c"],
4887 meta: {
4888 created: 1234567890i64,
4889 score: (i * 3.14f64) as i64,
4890 extra: "prefix/" + i + "/" + idx
4891 }
4892 });
4893 i = i + 1;
4894 }
4895 items
4896 }
4897
4898 pub fn string_concat_stress() {
4899 let i = 0;
4900 let result = "";
4901 while i < 200 {
4902 result = result + "data-" + i + ",";
4903 i = i + 1;
4904 }
4905 result
4906 }
4907 "#
4908 .to_vec(),
4909 )?;
4910
4911 let (heavy_ptr, _) = vm.get_fn_ptr("vm_stress::heavy_alloc", &[Type::I64])?;
4912 let (concat_ptr, _) = vm.get_fn_ptr("vm_stress::string_concat_stress", &[])?;
4913
4914 let threads: usize = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4).max(100);
4915 let iters_per_thread = 200;
4916 let total_calls = threads * iters_per_thread * 2;
4917
4918 let before = current_rss_kb();
4919 eprintln!("threads={threads} iters_per_thread={iters_per_thread} total_calls={total_calls} rss_before={before}KB");
4920
4921 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4923 let r1 = current_rss_kb();
4924 eprintln!("rss_after_round1={r1}KB");
4925
4926 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4928 let r2 = current_rss_kb();
4929 eprintln!("rss_after_round2={r2}KB");
4930
4931 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4933 let r3 = current_rss_kb();
4934 eprintln!("rss_after_round3={r3}KB");
4935
4936 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4938 let r4 = current_rss_kb();
4939 eprintln!("rss_after_round4={r4}KB");
4940
4941 let d12 = r2.saturating_sub(r1);
4943 let d23 = r3.saturating_sub(r2);
4944 let d34 = r4.saturating_sub(r3);
4945 eprintln!("delta_r1→r2={d12}KB delta_r2→r3={d23}KB delta_r3→r4={d34}KB");
4946
4947 let max_growth_kb = 20 * 1024;
4949 assert!(d34 < max_growth_kb, "memory keeps growing after allocator warm-up: round1={r1} round2={r2} round3={r3} round4={r4} delta12={d12}KB delta23={d23}KB delta34={d34}KB (max stable growth={max_growth_kb}KB)");
4950
4951 Ok(())
4952 }
4953
4954 fn run_stress_round(threads: usize, iters: usize, heavy_ptr: usize, concat_ptr: usize) {
4955 std::thread::scope(|scope| {
4956 let mut handles = Vec::with_capacity(threads);
4957 for t in 0..threads {
4958 let heavy_ptr = heavy_ptr;
4959 let concat_ptr = concat_ptr;
4960 handles.push(scope.spawn(move || {
4961 let heavy_fn: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(heavy_ptr as *const u8) };
4962 let concat_fn: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(concat_ptr as *const u8) };
4963 for i in 0..iters {
4964 let r_ptr = heavy_fn((t * iters + i) as i64);
4966 assert!(!r_ptr.is_null());
4967 unsafe {
4968 let r = &*r_ptr;
4969 assert!(r.len() > 0, "heavy_alloc returned empty list");
4970 drop(Box::from_raw(r_ptr as *mut Dynamic));
4971 }
4972
4973 let s_ptr = concat_fn();
4975 assert!(!s_ptr.is_null());
4976 unsafe {
4977 let s = &*s_ptr;
4978 assert!(s.len() > 0, "string_concat_stress returned empty");
4979 drop(Box::from_raw(s_ptr as *mut Dynamic));
4980 }
4981 }
4982 }));
4983 }
4984 for h in handles {
4985 h.join().unwrap();
4986 }
4987 });
4988 }
4989
4990 fn current_rss_kb() -> u64 {
4991 let pid = std::process::id();
4993 if let Ok(output) = std::process::Command::new("ps").args(["-p", &pid.to_string(), "-o", "rss="]).output() {
4994 if let Ok(s) = String::from_utf8(output.stdout) {
4995 if let Some(kb) = s.trim().parse::<u64>().ok() {
4996 return kb;
4997 }
4998 }
4999 }
5000 if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
5002 let parts: Vec<&str> = statm.split_whitespace().collect();
5003 if let Some(rss_pages) = parts.get(1).and_then(|s| s.parse::<u64>().ok()) {
5004 return rss_pages * 4; }
5006 }
5007 0
5008 }
5009}