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 nested_closure_captures_outer_closure_arg() -> anyhow::Result<()> {
1688 let vm = Vm::with_all()?;
1689 vm.import_code(
1690 "vm_nested_closure_capture",
1691 br#"
1692 pub fn run() {
1693 let reference_label = "reference";
1694 |path: string| {
1695 let upload_done = |uploaded: bool| {
1696 if uploaded {
1697 reference_label + ":" + path
1698 } else {
1699 "missing"
1700 }
1701 };
1702 upload_done(true)
1703 }("reference.png")
1704 }
1705 "#
1706 .to_vec(),
1707 )?;
1708
1709 let compiled = vm.get_fn("vm_nested_closure_capture::run", &[])?;
1710 assert_eq!(compiled.ret_ty(), &Type::Any);
1711 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1712 let result = unsafe { &*run() };
1713 assert_eq!(result.as_str(), "reference:reference.png");
1714 Ok(())
1715 }
1716
1717 #[test]
1718 fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1719 let vm = Vm::with_all()?;
1720 vm.import_code(
1721 "vm_semicolon_tail_void",
1722 br#"
1723 pub fn send_role_select(idx, account_id, selected_slot) {
1724 root::send("local/ui/send_dialog", {
1725 idx: idx,
1726 account_id: account_id,
1727 selected_slot: selected_slot
1728 });
1729 }
1730 "#
1731 .to_vec(),
1732 )?;
1733
1734 let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1735 assert_eq!(compiled.ret_ty(), &Type::Void);
1736 Ok(())
1737 }
1738
1739 #[test]
1740 fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1741 let vm = Vm::with_all()?;
1742 vm.import_code(
1743 "vm_bare_return_conflict",
1744 br#"
1745 pub fn run(flag) {
1746 if flag {
1747 return;
1748 }
1749 1
1750 }
1751 "#
1752 .to_vec(),
1753 )?;
1754
1755 let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1756 Ok(_) => panic!("expected mismatched return types to fail"),
1757 Err(err) => err,
1758 };
1759 assert!(format!("{err:#}").contains("返回类型不一致"));
1760 Ok(())
1761 }
1762
1763 #[test]
1764 fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1765 let vm = Vm::with_all()?;
1766 vm.import_code(
1767 "vm_root_get_dynamic_concat",
1768 br#"
1769 pub fn get_action(req) {
1770 root::get("local/game/panel_actions/" + req.idx)
1771 }
1772 "#
1773 .to_vec(),
1774 )?;
1775
1776 root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1777 let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1778 assert_eq!(compiled.ret_ty(), &Type::Any);
1779 let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1780 let req = dynamic::map!("idx"=> 7i64);
1781 let result = unsafe { &*get_action(&req) };
1782
1783 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1784 Ok(())
1785 }
1786
1787 #[test]
1788 fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1789 let vm = Vm::with_all()?;
1790 vm.import_code(
1791 "vm_registered_panel_action",
1792 br#"
1793 pub fn panel_action(req) {
1794 root::get("local/game/panel_actions/" + req.idx)
1795 }
1796
1797 pub fn register() {
1798 root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1799 }
1800 "#
1801 .to_vec(),
1802 )?;
1803
1804 let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1805 assert_eq!(compiled.ret_ty(), &Type::Bool);
1806 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1807 assert!(register());
1808 Ok(())
1809 }
1810
1811 #[test]
1812 fn std_spawn_runs_named_function_with_tuple_args() -> anyhow::Result<()> {
1813 let zero_path = "local/vm_std_spawn/zero";
1814 let sum_path = "local/vm_std_spawn/sum";
1815 let closure_path = "local/vm_std_spawn/closure";
1816 let closure_vars_path = "local/vm_std_spawn/closure_vars";
1817 let _ = root::remove(zero_path);
1818 let _ = root::remove(sum_path);
1819 let _ = root::remove(closure_path);
1820 let _ = root::remove(closure_vars_path);
1821 let vm = Vm::with_all()?;
1822 vm.import_code(
1823 "vm_std_spawn",
1824 br#"
1825 pub fn zero() {
1826 root::add("local/vm_std_spawn/zero", 1);
1827 }
1828
1829 pub fn job(left, right) {
1830 root::add("local/vm_std_spawn/sum", left + right);
1831 }
1832
1833 pub fn start_zero() {
1834 spawn("vm_std_spawn::zero", ())
1835 }
1836
1837 pub fn start_sum() {
1838 spawn("vm_std_spawn::job", (10, 20))
1839 }
1840
1841 pub fn start_closure() {
1842 spawn(|x, y| {
1843 root::add("local/vm_std_spawn/closure", x + y);
1844 }, (3, 4))
1845 }
1846
1847 pub fn start_closure_vars() {
1848 let x = 5;
1849 let y = 6;
1850 spawn(|left, right| {
1851 root::add("local/vm_std_spawn/closure_vars", left + right);
1852 }, (x, y))
1853 }
1854 "#
1855 .to_vec(),
1856 )?;
1857
1858 let compiled = vm.get_fn("vm_std_spawn::start_zero", &[])?;
1859 assert_eq!(compiled.ret_ty(), &Type::Bool);
1860 let start_zero: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1861 assert!(start_zero());
1862
1863 let compiled = vm.get_fn("vm_std_spawn::start_sum", &[])?;
1864 assert_eq!(compiled.ret_ty(), &Type::Bool);
1865 let start_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1866 assert!(start_sum());
1867
1868 let compiled = vm.get_fn("vm_std_spawn::start_closure", &[])?;
1869 assert_eq!(compiled.ret_ty(), &Type::Bool);
1870 let start_closure: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1871 assert!(start_closure());
1872
1873 let compiled = vm.get_fn("vm_std_spawn::start_closure_vars", &[])?;
1874 assert_eq!(compiled.ret_ty(), &Type::Bool);
1875 let start_closure_vars: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1876 assert!(start_closure_vars());
1877
1878 for _ in 0..50 {
1879 let zero_done = root::get(zero_path).ok().and_then(|value| value.as_int()) == Some(1);
1880 let sum_done = root::get(sum_path).ok().and_then(|value| value.as_int()) == Some(30);
1881 let closure_done = root::get(closure_path).ok().and_then(|value| value.as_int()) == Some(7);
1882 let closure_vars_done = root::get(closure_vars_path).ok().and_then(|value| value.as_int()) == Some(11);
1883 if zero_done && sum_done && closure_done && closure_vars_done {
1884 return Ok(());
1885 }
1886 std::thread::sleep(std::time::Duration::from_millis(10));
1887 }
1888
1889 anyhow::bail!("spawned jobs did not write expected results");
1890 }
1891
1892 #[test]
1893 fn native_can_save_and_later_call_closure_callback() -> anyhow::Result<()> {
1894 static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
1895
1896 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1897 if callback.is_null() {
1898 return false;
1899 }
1900 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1901 return false;
1902 };
1903 *SAVED_CALLBACK.lock() = Some(callback);
1904 true
1905 }
1906
1907 let path = "local/vm_callback/result";
1908 let _ = root::remove(path);
1909 *SAVED_CALLBACK.lock() = None;
1910
1911 let vm = Vm::with_all()?;
1912 vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
1913 vm.import_code(
1914 "vm_callback",
1915 br#"
1916 pub fn register() {
1917 let n = 41;
1918 callback_test::save(|| {
1919 root::add("local/vm_callback/result", n + 1);
1920 true
1921 })
1922 }
1923 "#
1924 .to_vec(),
1925 )?;
1926
1927 let compiled = vm.get_fn("vm_callback::register", &[])?;
1928 assert_eq!(compiled.ret_ty(), &Type::Bool);
1929 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1930 assert!(register());
1931 assert!(root::get(path).is_err());
1932
1933 let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
1934 let result = callback.call0()?;
1935 assert_eq!(result.as_bool(), Some(true));
1936 assert_eq!(root::get(path)?.as_int(), Some(42));
1937 Ok(())
1938 }
1939
1940 #[test]
1941 fn closure_captures_share_state_between_callbacks() -> anyhow::Result<()> {
1942 static SAVED_CALLBACKS: parking_lot::Mutex<Vec<ZustCallback>> = parking_lot::Mutex::new(Vec::new());
1943
1944 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1945 if callback.is_null() {
1946 return false;
1947 }
1948 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
1949 return false;
1950 };
1951 SAVED_CALLBACKS.lock().push(callback);
1952 true
1953 }
1954
1955 SAVED_CALLBACKS.lock().clear();
1956
1957 let vm = Vm::with_all()?;
1958 vm.add_native_module_ptr("capture_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
1959 vm.import_code(
1960 "vm_shared_capture",
1961 br#"
1962 pub fn register() {
1963 let state = {};
1964 state.drag_kind = 0;
1965 capture_test::save(|| {
1966 state.drag_kind = 2;
1967 true
1968 });
1969 capture_test::save(|| {
1970 state.drag_kind
1971 })
1972 }
1973 "#
1974 .to_vec(),
1975 )?;
1976
1977 let register = vm.get_fn("vm_shared_capture::register", &[])?;
1978 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
1979 assert!(register());
1980
1981 let (writer, reader) = {
1982 let saved = SAVED_CALLBACKS.lock();
1983 assert_eq!(saved.len(), 2);
1984 (saved[0].clone(), saved[1].clone())
1985 };
1986 assert_eq!(reader.call0()?.as_int(), Some(0));
1987 assert_eq!(writer.call0()?.as_bool(), Some(true));
1988 assert_eq!(reader.call0()?.as_int(), Some(2));
1989 Ok(())
1990 }
1991
1992 #[test]
1993 fn native_can_save_and_later_call_named_function_callback() -> anyhow::Result<()> {
1994 static SAVED_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
1995
1996 extern "C" fn save_callback(callback: *const Dynamic) -> bool {
1997 if callback.is_null() {
1998 return false;
1999 }
2000 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2001 return false;
2002 };
2003 *SAVED_CALLBACK.lock() = Some(callback);
2004 true
2005 }
2006
2007 let path = "local/vm_named_callback/result";
2008 let _ = root::remove(path);
2009 *SAVED_CALLBACK.lock() = None;
2010
2011 let vm = Vm::with_all()?;
2012 vm.add_native_module_ptr("callback_test", "save", &[Type::Any], Type::Bool, save_callback as *const u8)?;
2013 vm.import_code(
2014 "vm_named_callback",
2015 br#"
2016 pub fn on_result() {
2017 root::add("local/vm_named_callback/result", "done");
2018 true
2019 }
2020
2021 pub fn register() {
2022 callback_test::save(on_result)
2023 }
2024 "#
2025 .to_vec(),
2026 )?;
2027
2028 let register = vm.get_fn("vm_named_callback::register", &[])?;
2029 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2030 assert!(register());
2031 assert!(root::get(path).is_err());
2032
2033 let callback = SAVED_CALLBACK.lock().clone().expect("callback should be saved");
2034 assert_eq!(callback.call1(dynamic::map!("text"=> "done"))?.as_bool(), Some(true));
2035 assert_eq!(root::get(path)?.as_str(), "done");
2036 Ok(())
2037 }
2038
2039 #[test]
2040 fn native_callback_can_receive_later_dynamic_args() -> anyhow::Result<()> {
2041 static SAVED_PATH_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2042 static SAVED_SUM_CALLBACK: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2043
2044 extern "C" fn save_path_callback(callback: *const Dynamic) -> bool {
2045 if callback.is_null() {
2046 return false;
2047 }
2048 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2049 return false;
2050 };
2051 *SAVED_PATH_CALLBACK.lock() = Some(callback);
2052 true
2053 }
2054
2055 extern "C" fn save_sum_callback(callback: *const Dynamic) -> bool {
2056 if callback.is_null() {
2057 return false;
2058 }
2059 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2060 return false;
2061 };
2062 *SAVED_SUM_CALLBACK.lock() = Some(callback);
2063 true
2064 }
2065
2066 let path_result = "local/vm_callback/path";
2067 let sum_result = "local/vm_callback/sum8";
2068 let _ = root::remove(path_result);
2069 let _ = root::remove(sum_result);
2070 *SAVED_PATH_CALLBACK.lock() = None;
2071 *SAVED_SUM_CALLBACK.lock() = None;
2072
2073 let vm = Vm::with_all()?;
2074 vm.add_native_module_ptr("callback_test", "save_path", &[Type::Any], Type::Bool, save_path_callback as *const u8)?;
2075 vm.add_native_module_ptr("callback_test", "save_sum", &[Type::Any], Type::Bool, save_sum_callback as *const u8)?;
2076 vm.import_code(
2077 "vm_callback_args",
2078 br#"
2079 pub fn register_path() {
2080 let key = "local/vm_callback/path";
2081 callback_test::save_path(|path| {
2082 root::add(key, path);
2083 true
2084 })
2085 }
2086
2087 pub fn register_sum() {
2088 callback_test::save_sum(|a, b, c, d, e, f, g, h| {
2089 root::add("local/vm_callback/sum8", a + b + c + d + e + f + g + h);
2090 true
2091 })
2092 }
2093 "#
2094 .to_vec(),
2095 )?;
2096
2097 let register_path = vm.get_fn("vm_callback_args::register_path", &[])?;
2098 let register_path: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_path.ptr()) };
2099 assert!(register_path());
2100
2101 let register_sum = vm.get_fn("vm_callback_args::register_sum", &[])?;
2102 let register_sum: extern "C" fn() -> bool = unsafe { std::mem::transmute(register_sum.ptr()) };
2103 assert!(register_sum());
2104
2105 let path_callback = SAVED_PATH_CALLBACK.lock().clone().expect("path callback should be saved");
2106 assert_eq!(path_callback.call1(Dynamic::from("picked.txt"))?.as_bool(), Some(true));
2107 assert_eq!(root::get(path_result)?.as_str(), "picked.txt");
2108
2109 let sum_callback = SAVED_SUM_CALLBACK.lock().clone().expect("sum callback should be saved");
2110 let sum_args = (1i64..=8).map(Dynamic::from).collect();
2111 assert_eq!(sum_callback.call(sum_args)?.as_bool(), Some(true));
2112 assert_eq!(root::get(sum_result)?.as_int(), Some(36));
2113 Ok(())
2114 }
2115
2116 #[test]
2117 fn callback_with_16_explicit_args_and_captures() -> anyhow::Result<()> {
2118 static SAVED_SUM16: parking_lot::Mutex<Option<ZustCallback>> = parking_lot::Mutex::new(None);
2119
2120 extern "C" fn save_sum16(callback: *const Dynamic) -> bool {
2121 if callback.is_null() {
2122 return false;
2123 }
2124 let Some(callback) = (unsafe { &*callback }).as_custom::<ZustCallback>().cloned() else {
2125 return false;
2126 };
2127 *SAVED_SUM16.lock() = Some(callback);
2128 true
2129 }
2130
2131 let sum16_path = "local/vm_callback/sum16";
2132 let _ = root::remove(sum16_path);
2133 *SAVED_SUM16.lock() = None;
2134
2135 let vm = Vm::with_all()?;
2136 vm.add_native_module_ptr("callback_test", "save_sum16", &[Type::Any], Type::Bool, save_sum16 as *const u8)?;
2137 vm.import_code(
2138 "vm_callback_16_args",
2139 br#"
2140 pub fn register_sum16() {
2141 let prefix = "sum=";
2142 callback_test::save_sum16(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2143 let total = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p;
2144 root::add("local/vm_callback/sum16", prefix + total);
2145 true
2146 })
2147 }
2148 "#
2149 .to_vec(),
2150 )?;
2151
2152 let register = vm.get_fn("vm_callback_16_args::register_sum16", &[])?;
2153 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(register.ptr()) };
2154 assert!(register());
2155
2156 let callback = SAVED_SUM16.lock().clone().expect("sum16 callback saved");
2157 let args: Vec<Dynamic> = (1i64..=16).map(Dynamic::from).collect();
2158 assert_eq!(callback.call(args)?.as_bool(), Some(true));
2159 assert_eq!(root::get(sum16_path)?.as_str(), "sum=136");
2160 Ok(())
2161 }
2162
2163 #[test]
2164 fn spawn_closure_with_16_args() -> anyhow::Result<()> {
2165 let spawn16_path = "local/vm_spawn/spawn16";
2166 let _ = root::remove(spawn16_path);
2167
2168 let vm = Vm::with_all()?;
2169 vm.import_code(
2170 "vm_spawn_16_args",
2171 br#"
2172 pub fn start_spawn16() {
2173 spawn(|a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p| {
2174 root::add("local/vm_spawn/spawn16", a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p);
2175 }, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
2176 }
2177 "#
2178 .to_vec(),
2179 )?;
2180
2181 let compiled = vm.get_fn("vm_spawn_16_args::start_spawn16", &[])?;
2182 assert_eq!(compiled.ret_ty(), &Type::Bool);
2183 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2184 assert!(start());
2185
2186 for _ in 0..50 {
2187 if root::get(spawn16_path).ok().and_then(|v| v.as_int()) == Some(136) {
2188 return Ok(());
2189 }
2190 std::thread::sleep(std::time::Duration::from_millis(10));
2191 }
2192 anyhow::bail!("spawned job did not write expected result");
2193 }
2194
2195 #[test]
2196 fn spawn_native_closure_avoids_any_boxing() -> anyhow::Result<()> {
2197 let nat_path = "local/vm_spawn_native/result";
2198 let _ = root::remove(nat_path);
2199 let vm = Vm::with_all()?;
2200 vm.import_code(
2201 "vm_spawn_native",
2202 br#"
2203 pub fn start() {
2204 spawn(|x: i64, y: i64| {
2205 root::add("local/vm_spawn_native/result", x + y);
2206 }, (10i64, 20i64))
2207 }
2208 "#
2209 .to_vec(),
2210 )?;
2211 let compiled = vm.get_fn("vm_spawn_native::start", &[])?;
2212 assert_eq!(compiled.ret_ty(), &Type::Bool);
2213 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2214 assert!(start());
2215 for _ in 0..50 {
2216 if root::get(nat_path).ok().and_then(|v| v.as_int()) == Some(30) {
2217 return Ok(());
2218 }
2219 std::thread::sleep(std::time::Duration::from_millis(10));
2220 }
2221 anyhow::bail!("spawned native closure did not write expected result");
2222 }
2223
2224 #[test]
2225 fn multi_level_nested_closure_captures() -> anyhow::Result<()> {
2226 let vm = Vm::with_all()?;
2227 vm.import_code(
2228 "vm_multi_level_captures",
2229 br#"
2230 pub fn run() {
2231 let level1 = "L1";
2232 let level2 = "L2";
2233 |path: string| {
2234 let level3 = "L3";
2235 let inner = |suffix: string| {
2236 let level4 = "L4";
2237 |flag: bool| {
2238 if flag {
2239 level1 + "." + level2 + "." + level3 + "." + level4 + "." + path + suffix
2240 } else {
2241 "off"
2242 }
2243 }(true)
2244 };
2245 inner(".ext")
2246 }("file.txt")
2247 }
2248 "#
2249 .to_vec(),
2250 )?;
2251
2252 let compiled = vm.get_fn("vm_multi_level_captures::run", &[])?;
2253 assert_eq!(compiled.ret_ty(), &Type::Any);
2254 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2255 let result = unsafe { &*run() };
2256 assert_eq!(result.as_str(), "L1.L2.L3.L4.file.txt.ext");
2257 Ok(())
2258 }
2259
2260 #[test]
2261 fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
2262 let vm = Vm::with_all()?;
2263 vm.import_code(
2264 "vm_registered_string_concat",
2265 br#"
2266 pub fn send_panel(idx: i64) {
2267 let idx_key = "" + idx;
2268 idx_key
2269 }
2270 "#
2271 .to_vec(),
2272 )?;
2273
2274 assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
2275 Ok(())
2276 }
2277
2278 #[test]
2279 fn dynamic_method_error_reports_source_location() -> anyhow::Result<()> {
2280 let vm = Vm::with_all()?;
2281 vm.import_code(
2282 "vm_bad_dynamic_method",
2283 br#"
2284 pub fn main(value) {
2285 let out = "";
2286 out = out + value.fetch("name");
2287 }
2288 "#
2289 .to_vec(),
2290 )?;
2291
2292 let err = vm.get_fn_ptr("vm_bad_dynamic_method::main", &[Type::Any]).expect_err("bad dynamic method should fail to compile");
2293 let msg = format!("{err:#}");
2294 assert!(msg.contains("vm_bad_dynamic_method:4:"), "{msg}");
2295 assert!(msg.contains("`Any.fetch` 不是成员函数"), "{msg}");
2296 assert!(msg.contains(r#"out = out + value.fetch("name");"#), "{msg}");
2297 Ok(())
2298 }
2299
2300 #[test]
2301 fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
2302 fn echo_handler(msg: Dynamic) -> Dynamic {
2303 dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
2304 }
2305
2306 let vm = Vm::with_all()?;
2307 vm.import_code(
2308 "vm_root_send_idx_return",
2309 br#"
2310 pub fn call(req) {
2311 root::send_idx("local/send_idx_return_handlers", 0, req)
2312 }
2313 "#
2314 .to_vec(),
2315 )?;
2316
2317 root::add_list("local/send_idx_return_handlers")?;
2318 let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
2319 mount.push(name, root::Object::Native(echo_handler))?;
2320
2321 assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
2322 let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
2323 assert_eq!(compiled.ret_ty(), &Type::Any);
2324 let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2325 let req = dynamic::map!("id"=> 42i64);
2326 let result = unsafe { &*call(&req) };
2327
2328 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
2329 assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
2330 Ok(())
2331 }
2332
2333 #[test]
2334 fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
2335 let vm = Vm::with_all()?;
2336 vm.import_code(
2337 "vm_public_hotspots",
2338 br#"
2339 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2340 {
2341 path: action_map_path,
2342 panel_id: panel_id,
2343 action_id: action_id,
2344 id: hotspot.id
2345 }
2346 }
2347
2348 pub fn public_hotspots(idx, panel_id, hotspots) {
2349 let idx_key = "" + idx;
2350 let action_map_path = "local/game/panel_actions/" + idx_key;
2351
2352 let existing_action_map = root::get(action_map_path);
2353 if !existing_action_map.is_map() {
2354 root::add_map(action_map_path);
2355 }
2356
2357 if hotspots.is_map() {
2358 let public_items = {};
2359 for action_id in hotspots.keys() {
2360 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2361 }
2362 return public_items;
2363 }
2364
2365 let public_items = [];
2366 let i = 0;
2367 while i < hotspots.len() {
2368 let hotspot = hotspots.get_idx(i);
2369 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2370 public_items.push(item);
2371 i = i + 1;
2372 }
2373
2374 public_items
2375 }
2376 "#
2377 .to_vec(),
2378 )?;
2379
2380 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
2381 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
2382 Ok(())
2383 }
2384
2385 #[test]
2386 fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
2387 let vm = Vm::with_all()?;
2388 vm.import_code(
2389 "vm_send_panel_public_hotspots",
2390 br#"
2391 pub fn ok(value) {
2392 value
2393 }
2394
2395 pub fn panel_from_node(req) {
2396 {
2397 panel_id: req.panel_id,
2398 hotspots: req.hotspots
2399 }
2400 }
2401
2402 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
2403 {
2404 path: action_map_path,
2405 panel_id: panel_id,
2406 action_id: action_id,
2407 id: hotspot.id
2408 }
2409 }
2410
2411 pub fn public_hotspots(idx, panel_id, hotspots) {
2412 let idx_key = "" + idx;
2413 let action_map_path = "local/game/panel_actions/" + idx_key;
2414
2415 let existing_action_map = root::get(action_map_path);
2416 if !existing_action_map.is_map() {
2417 root::add_map(action_map_path);
2418 }
2419
2420 if hotspots.is_map() {
2421 let public_items = {};
2422 for action_id in hotspots.keys() {
2423 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
2424 }
2425 return public_items;
2426 }
2427
2428 let public_items = [];
2429 let i = 0;
2430 while i < hotspots.len() {
2431 let hotspot = hotspots.get_idx(i);
2432 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
2433 public_items.push(item);
2434 i = i + 1;
2435 }
2436
2437 public_items
2438 }
2439
2440 pub fn send_panel(req) {
2441 let panel = req.panel;
2442 if !panel.is_map() {
2443 panel = panel_from_node(req);
2444 }
2445 if !panel.is_map() {
2446 return ok({
2447 id: 4,
2448 type: "panel_rejected",
2449 reason: "invalid panel"
2450 });
2451 }
2452 panel.id = 4;
2453 panel.idx = req.idx;
2454 if !panel.contains("type") {
2455 panel.type = "panel";
2456 }
2457 if panel.contains("hotspots") {
2458 panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
2459 }
2460 root::send_idx("local/ws", req.idx, panel);
2461 ok({
2462 id: 4,
2463 type: "panel",
2464 panel_id: panel.panel_id
2465 })
2466 }
2467 "#
2468 .to_vec(),
2469 )?;
2470
2471 let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
2472 assert_eq!(compiled.ret_ty(), &Type::Any);
2473 let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2474 let req = dynamic::map!(
2475 "idx"=> 7i64,
2476 "panel"=> dynamic::map!(
2477 "panel_id"=> "main",
2478 "hotspots"=> dynamic::map!(
2479 "open"=> dynamic::map!("id"=> "open")
2480 )
2481 )
2482 );
2483 let result = unsafe { &*send_panel(&req) };
2484
2485 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
2486 assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
2487 Ok(())
2488 }
2489
2490 #[test]
2491 fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
2492 let vm = Vm::with_all()?;
2493 vm.import_code(
2494 "vm_string_concat_map_key",
2495 br##"
2496 pub fn write_action(action_map, panel_id, action_id, action) {
2497 action_map[panel_id + "#" + action_id] = action;
2498 action_map[panel_id + "#" + action_id]
2499 }
2500 "##
2501 .to_vec(),
2502 )?;
2503
2504 let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
2505 let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2506 let action_map = dynamic::map!();
2507 let panel_id: Dynamic = "panel".into();
2508 let action_id: Dynamic = "open".into();
2509 let action = dynamic::map!("id"=> "open");
2510
2511 let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
2512
2513 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2514 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()));
2515 Ok(())
2516 }
2517
2518 #[test]
2519 fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
2520 let vm = Vm::with_all()?;
2521 vm.import_code(
2522 "vm_get_key_string_concat_key",
2523 br##"
2524 pub fn read_action(action_map, panel_id, action_id) {
2525 let action_key = panel_id + "#" + action_id;
2526 action_map.get_key(action_key)
2527 }
2528 "##
2529 .to_vec(),
2530 )?;
2531
2532 let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2533 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2534 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2535 let panel_id: Dynamic = "panel".into();
2536 let action_id: Dynamic = "open".into();
2537
2538 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2539
2540 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2541 Ok(())
2542 }
2543
2544 #[test]
2545 fn map_get_alias_matches_get_key() -> anyhow::Result<()> {
2546 let vm = Vm::with_all()?;
2547 vm.import_code(
2548 "vm_map_get_alias",
2549 br#"
2550 pub fn read_name(data) {
2551 data.get("name")
2552 }
2553
2554 pub fn read_missing(data) {
2555 data.get("missing")
2556 }
2557 "#
2558 .to_vec(),
2559 )?;
2560
2561 let data = dynamic::map!("name"=> "zust");
2562
2563 let compiled = vm.get_fn("vm_map_get_alias::read_name", &[Type::Any])?;
2564 assert_eq!(compiled.ret_ty(), &Type::Any);
2565 let read_name: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2566 let result = unsafe { &*read_name(&data) };
2567 assert_eq!(result.as_str(), "zust");
2568
2569 let compiled = vm.get_fn("vm_map_get_alias::read_missing", &[Type::Any])?;
2570 assert_eq!(compiled.ret_ty(), &Type::Any);
2571 let read_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2572 let result = unsafe { &*read_missing(&data) };
2573 assert!(result.is_null());
2574 Ok(())
2575 }
2576
2577 #[test]
2578 fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
2579 let vm = Vm::with_all()?;
2580 vm.import_code(
2581 "vm_get_key_helper_string_key",
2582 br##"
2583 pub fn make_action_key(panel_id, action_id) {
2584 panel_id + "#" + action_id
2585 }
2586
2587 pub fn read_action(action_map, panel_id, action_id) {
2588 let action_key = make_action_key(panel_id, action_id);
2589 let action = action_map.get_key(action_key);
2590 action
2591 }
2592 "##
2593 .to_vec(),
2594 )?;
2595
2596 let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
2597 let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2598 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2599 let panel_id: Dynamic = "panel".into();
2600 let action_id: Dynamic = "open".into();
2601
2602 let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
2603
2604 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
2605 Ok(())
2606 }
2607
2608 #[test]
2609 fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
2610 let vm = Vm::with_all()?;
2611 vm.import_code(
2612 "vm_del_key_string_key",
2613 br##"
2614 pub fn remove_action(action_map, panel_id, action_id) {
2615 let action_key = panel_id + "#" + action_id;
2616 let removed = action_map.del_key(action_key);
2617 [removed, action_map.get_key(action_key)]
2618 }
2619 "##
2620 .to_vec(),
2621 )?;
2622
2623 let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
2624 let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2625 let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
2626 let panel_id: Dynamic = "panel".into();
2627 let action_id: Dynamic = "open".into();
2628
2629 let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
2630
2631 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
2632 assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
2633 assert!(action_map.get_dynamic("panel#open").is_none());
2634 Ok(())
2635 }
2636
2637 #[test]
2638 fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
2639 let vm = Vm::with_all()?;
2640 vm.import_code(
2641 "vm_dynamic_field_or",
2642 r#"
2643 pub fn direct_next() {
2644 let choice = {
2645 label: "颜色",
2646 next: "color"
2647 };
2648 choice.next
2649 }
2650
2651 pub fn bracket_next() {
2652 let choice = {
2653 label: "颜色",
2654 next: "color"
2655 };
2656 choice["next"]
2657 }
2658 "#
2659 .as_bytes()
2660 .to_vec(),
2661 )?;
2662
2663 let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
2664 assert_eq!(compiled.ret_ty(), &Type::Any);
2665 let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2666 assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
2667
2668 let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
2669 assert_eq!(compiled.ret_ty(), &Type::Any);
2670 let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2671 assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
2672 Ok(())
2673 }
2674
2675 #[test]
2676 fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
2677 let vm = Vm::with_all()?;
2678 vm.import_code(
2679 "vm_if_empty_object_branch",
2680 r#"
2681 pub fn first_note(steps) {
2682 let first = if steps.len() > 0 { steps[0] } else { {} };
2683 let first_note = if first.contains("note") { first.note } else { "fallback" };
2684 first_note
2685 }
2686
2687 pub fn first_ja(steps) {
2688 let first = if steps.len() > 0 { steps[0] } else { {} };
2689 if first.contains("ja") { first.ja } else { "すみません" }
2690 }
2691
2692 pub fn assign_first_note(steps) {
2693 let first = {};
2694 first = if steps.len() > 0 { steps[0] } else { {} };
2695 if first.contains("note") { first.note } else { "fallback" }
2696 }
2697 "#
2698 .as_bytes()
2699 .to_vec(),
2700 )?;
2701
2702 let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
2703 assert_eq!(compiled.ret_ty(), &Type::Str);
2704 let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2705
2706 let empty_steps = Dynamic::list(Vec::new());
2707 assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
2708
2709 let mut step = std::collections::BTreeMap::new();
2710 step.insert("note".into(), "hello".into());
2711 let steps = Dynamic::list(vec![Dynamic::map(step)]);
2712 assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
2713
2714 let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
2715 assert_eq!(compiled.ret_ty(), &Type::Any);
2716 let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2717 assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
2718
2719 let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
2720 assert_eq!(compiled.ret_ty(), &Type::Any);
2721 let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2722 assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
2723 assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
2724 Ok(())
2725 }
2726
2727 #[test]
2728 fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
2729 let vm = Vm::with_all()?;
2730 vm.import_code(
2731 "vm_tail_list_literal",
2732 r#"
2733 pub fn numbers() {
2734 [1, 2, 3]
2735 }
2736
2737 pub fn maps() {
2738 [
2739 {note: "first"},
2740 {note: "second"}
2741 ]
2742 }
2743
2744 pub fn object_with_maps() {
2745 {
2746 steps: [
2747 {note: "first"},
2748 {note: "second"}
2749 ]
2750 }
2751 }
2752
2753 pub fn return_maps() {
2754 return [
2755 {note: "first"},
2756 {note: "second"}
2757 ];
2758 }
2759
2760 pub fn return_maps_without_semicolon() {
2761 return [
2762 {note: "first"},
2763 {note: "second"}
2764 ]
2765 }
2766
2767 pub fn tail_bare_variable() {
2768 let value = [
2769 {note: "first"},
2770 {note: "second"}
2771 ];
2772 value
2773 }
2774
2775 pub fn return_bare_variable_without_semicolon() {
2776 let value = [
2777 {note: "first"},
2778 {note: "second"}
2779 ];
2780 return value
2781 }
2782
2783 pub fn tail_object_variable() {
2784 let result = {
2785 steps: [
2786 {note: "first"},
2787 {note: "second"}
2788 ]
2789 };
2790 result
2791 }
2792 "#
2793 .as_bytes()
2794 .to_vec(),
2795 )?;
2796
2797 let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
2798 assert_eq!(compiled.ret_ty(), &Type::Any);
2799 let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2800 let result = unsafe { &*numbers() };
2801 assert_eq!(result.len(), 3);
2802 assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
2803
2804 let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
2805 assert_eq!(compiled.ret_ty(), &Type::Any);
2806 let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2807 let result = unsafe { &*maps() };
2808 assert_eq!(result.len(), 2);
2809 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2810
2811 let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
2812 assert_eq!(compiled.ret_ty(), &Type::Any);
2813 let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2814 let result = unsafe { &*object_with_maps() };
2815 let steps = result.get_dynamic("steps").expect("steps");
2816 assert_eq!(steps.len(), 2);
2817 assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2818
2819 let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
2820 assert_eq!(compiled.ret_ty(), &Type::Any);
2821 let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2822 let result = unsafe { &*return_maps() };
2823 assert_eq!(result.len(), 2);
2824 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2825
2826 let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
2827 assert_eq!(compiled.ret_ty(), &Type::Any);
2828 let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2829 let result = unsafe { &*return_maps_without_semicolon() };
2830 assert_eq!(result.len(), 2);
2831 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2832
2833 let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
2834 assert_eq!(compiled.ret_ty(), &Type::Any);
2835 let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2836 let result = unsafe { &*tail_bare_variable() };
2837 assert_eq!(result.len(), 2);
2838 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2839
2840 let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
2841 assert_eq!(compiled.ret_ty(), &Type::Any);
2842 let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2843 let result = unsafe { &*return_bare_variable_without_semicolon() };
2844 assert_eq!(result.len(), 2);
2845 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
2846
2847 let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
2848 assert_eq!(compiled.ret_ty(), &Type::Any);
2849 let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2850 let result = unsafe { &*tail_object_variable() };
2851 let steps = result.get_dynamic("steps").expect("steps");
2852 assert_eq!(steps.len(), 2);
2853 assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
2854 Ok(())
2855 }
2856
2857 #[test]
2858 fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
2859 let vm = Vm::with_all()?;
2860 vm.import_code(
2861 "vm_returned_list_get_idx",
2862 r#"
2863 pub fn ids() {
2864 [
2865 "base",
2866 "2",
2867 "3"
2868 ]
2869 }
2870
2871 pub fn combinations() {
2872 let result = [];
2873 let values = ids();
2874 let idx = 0;
2875 while idx < values.len() {
2876 result.push(values.get_idx(idx));
2877 idx = idx + 1;
2878 }
2879 result
2880 }
2881 "#
2882 .as_bytes()
2883 .to_vec(),
2884 )?;
2885
2886 let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
2887 assert_eq!(compiled.ret_ty(), &Type::Any);
2888 let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2889 let result = unsafe { &*combinations() };
2890
2891 assert_eq!(result.len(), 3);
2892 assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
2893 assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
2894 Ok(())
2895 }
2896
2897 #[test]
2898 fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
2899 fn extra_page_literal(depth: usize) -> String {
2900 let mut value = "{leaf: \"done\"}".to_string();
2901 for idx in 0..depth {
2902 value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
2903 }
2904 value
2905 }
2906
2907 let extra = extra_page_literal(48);
2908 let code = format!(
2909 r#"
2910 pub fn script() {{
2911 return [
2912 {{ja: "一つ目", note: "first", extra: {extra}}},
2913 {{ja: "二つ目", note: "second", extra: {extra}}},
2914 {{ja: "三つ目", note: "third", extra: {extra}}}
2915 ]
2916 }}
2917 "#
2918 );
2919
2920 let vm = Vm::with_all()?;
2921 vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
2922 let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
2923 assert_eq!(compiled.ret_ty(), &Type::Any);
2924 let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2925 let result = unsafe { &*script() };
2926 assert_eq!(result.len(), 3);
2927 assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
2928 Ok(())
2929 }
2930
2931 #[test]
2932 fn native_import_uses_owning_vm() -> anyhow::Result<()> {
2933 let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
2934 std::fs::write(&module_path, "pub fn value() { 41 }")?;
2935 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
2936
2937 let vm1 = Vm::with_all()?;
2938 vm1.import_code(
2939 "vm_import_owner",
2940 format!(
2941 r#"
2942 pub fn run() {{
2943 import("vm_imported_owner", "{module_path}");
2944 }}
2945 "#
2946 )
2947 .into_bytes(),
2948 )?;
2949 let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
2950
2951 let vm2 = Vm::with_all()?;
2952 vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
2953 let _ = vm2.get_fn("vm_import_other::run", &[])?;
2954
2955 let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
2956 run();
2957
2958 assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
2959 assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
2960 Ok(())
2961 }
2962
2963 #[test]
2964 fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
2965 let vm = Vm::with_all()?;
2966 vm.import_code(
2967 "vm_object_last_call_field",
2968 r#"
2969 pub fn extra_page() {
2970 {
2971 title: "extra",
2972 pages: [
2973 {note: "nested"}
2974 ]
2975 }
2976 }
2977
2978 pub fn data() {
2979 return [
2980 {
2981 note: "first",
2982 choices: ["a", "b"],
2983 extras: extra_page()
2984 },
2985 {
2986 note: "second",
2987 choices: ["c"],
2988 extras: extra_page()
2989 }
2990 ]
2991 }
2992 "#
2993 .as_bytes()
2994 .to_vec(),
2995 )?;
2996
2997 let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
2998 assert_eq!(compiled.ret_ty(), &Type::Any);
2999 let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3000 let result = unsafe { &*data() };
3001 assert_eq!(result.len(), 2);
3002 let first = result.get_idx(0).expect("first step");
3003 assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
3004 Ok(())
3005 }
3006
3007 #[test]
3008 fn string_return_survives_scope_exit() -> anyhow::Result<()> {
3009 let vm = Vm::with_all()?;
3010 vm.import_code(
3011 "vm_string_return_scope",
3012 r#"
3013 pub fn source_root() {
3014 "../assets/character/男主角换装"
3015 }
3016
3017 pub fn binary_root() {
3018 "character_binary/男主角换装"
3019 }
3020
3021 pub fn runtime_binary_url() {
3022 "/" + binary_root()
3023 }
3024
3025 pub fn action_groups() {
3026 let root = source_root();
3027 let binary_url = runtime_binary_url();
3028 let binary_root = binary_root();
3029 [
3030 {
3031 id: "field_bottom",
3032 source_spine: root + "/战斗外/boy_b.spine",
3033 skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
3034 export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
3035 }
3036 ]
3037 }
3038 "#
3039 .as_bytes()
3040 .to_vec(),
3041 )?;
3042
3043 let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
3044 assert_eq!(compiled.ret_ty(), &Type::Str);
3045 let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3046 let source_root = unsafe { &*source_root() };
3047 assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
3048
3049 let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
3050 assert_eq!(compiled.ret_ty(), &Type::Any);
3051 let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3052 let groups = unsafe { &*action_groups() };
3053 let first = groups.get_idx(0).expect("first action group");
3054 assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
3055 assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
3056 Ok(())
3057 }
3058
3059 #[test]
3060 fn dynamic_string_add_uses_any_binary_fast_path() -> anyhow::Result<()> {
3061 let vm = Vm::with_all()?;
3062 vm.import_code(
3063 "vm_dynamic_string_add",
3064 br#"
3065 pub fn concat(left, right) {
3066 left + right
3067 }
3068 "#
3069 .to_vec(),
3070 )?;
3071
3072 let compiled = vm.get_fn("vm_dynamic_string_add::concat", &[Type::Any, Type::Any])?;
3073 assert_eq!(compiled.ret_ty(), &Type::Any);
3074 let concat: extern "C" fn(*const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3075 let left = Dynamic::from("hello");
3076 let right = Dynamic::from(" world");
3077 let result = unsafe { &*concat(&left, &right) };
3078 assert_eq!(result.as_str(), "hello world");
3079 Ok(())
3080 }
3081
3082 #[test]
3083 fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
3084 let vm = Vm::with_all()?;
3085 let model_count = 180;
3086 let combination_count = 90;
3087 let models = (0..model_count)
3088 .map(|idx| {
3089 format!(
3090 r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
3091 {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
3092 {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
3093 {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
3094 ]}}"#
3095 )
3096 })
3097 .collect::<Vec<_>>()
3098 .join(",\n");
3099 let combinations = (0..combination_count).map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#)).collect::<Vec<_>>().join(",\n");
3100 let code = format!(
3101 r#"
3102 pub fn source_root() {{
3103 "/美术资源/角色/少年/默认"
3104 }}
3105
3106 pub fn runtime_boy_url() {{
3107 "/cdn/runtime/角色/少年/少年.model.json"
3108 }}
3109
3110 pub fn parts() {{
3111 [
3112 {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
3113 {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
3114 {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
3115 ]
3116 }}
3117
3118 pub fn action_groups() {{
3119 {{
3120 idle: [
3121 {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
3122 {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
3123 ],
3124 move: [
3125 {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
3126 {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
3127 ]
3128 }}
3129 }}
3130
3131 pub fn default_model() {{
3132 {{
3133 id: "runtime_boy",
3134 name: "运行时少年",
3135 skins: [
3136 {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
3137 {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
3138 ],
3139 models: [
3140 {models}
3141 ]
3142 }}
3143 }}
3144
3145 pub fn first_nine_combinations() {{
3146 [
3147 {combinations}
3148 ]
3149 }}
3150
3151 pub fn config() {{
3152 {{
3153 source_root: source_root(),
3154 runtime_boy_url: runtime_boy_url(),
3155 parts: parts(),
3156 action_groups: action_groups(),
3157 default_model: default_model(),
3158 first_nine_combinations: first_nine_combinations()
3159 }}
3160 }}
3161
3162 pub fn start() {{
3163 root::add("local/vm_large_inline_call_object/config", {{
3164 source_root: source_root(),
3165 runtime_boy_url: runtime_boy_url(),
3166 parts: parts(),
3167 action_groups: action_groups(),
3168 default_model: default_model(),
3169 first_nine_combinations: first_nine_combinations()
3170 }})
3171 }}
3172 "#
3173 );
3174 vm.import_code("vm_large_inline_call_object", code.into_bytes())?;
3175
3176 let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
3177 assert_eq!(compiled.ret_ty(), &Type::Any);
3178 let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3179 let result = unsafe { &*config() };
3180 assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
3181 assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3182
3183 let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
3184 assert_eq!(compiled.ret_ty(), &Type::Bool);
3185 let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3186 assert!(start());
3187 let saved = root::get("local/vm_large_inline_call_object/config")?;
3188 assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
3189 Ok(())
3190 }
3191
3192 #[cfg(feature = "http")]
3193 #[test]
3194 fn http_serve_accepts_inline_config_map() -> anyhow::Result<()> {
3195 let vm = Vm::with_all()?;
3196 vm.import_code(
3197 "vm_http_serve_inline_config",
3198 br#"
3199 pub fn start() {
3200 let server = http::serve({host: "127.0.0.1:5192"});
3201 server
3202 }
3203 "#
3204 .to_vec(),
3205 )?;
3206
3207 let compiled = vm.get_fn("vm_http_serve_inline_config::start", &[])?;
3208 assert_eq!(compiled.ret_ty(), &Type::Any);
3209 Ok(())
3210 }
3211
3212 #[cfg(feature = "http")]
3213 #[test]
3214 fn http_serve_accepts_variable_and_quoted_static_key() -> anyhow::Result<()> {
3215 let vm = Vm::with_all()?;
3216 vm.import_code(
3217 "vm_http_serve_quoted_static",
3218 br#"
3219 pub fn start(server_addr) {
3220 let http_server = http::serve({
3221 host: server_addr,
3222 ws: true,
3223 upload: "upload",
3224 "static": {
3225 path: "/",
3226 dir: "public/local"
3227 }
3228 });
3229 http_server
3230 }
3231 "#
3232 .to_vec(),
3233 )?;
3234
3235 let compiled = vm.get_fn("vm_http_serve_quoted_static::start", &[Type::Any])?;
3236 assert_eq!(compiled.ret_ty(), &Type::Any);
3237 Ok(())
3238 }
3239
3240 #[cfg(all(feature = "http", feature = "llm"))]
3241 #[test]
3242 fn oss_helpers_accept_explicit_config() -> anyhow::Result<()> {
3243 let vm = Vm::with_all()?;
3244 vm.import_code(
3245 "vm_oss_explicit_config",
3246 br#"
3247 pub fn upload(oss, bytes) {
3248 oss::upload(oss, "llm/input/audio.wav", bytes)
3249 }
3250
3251 pub fn http_upload(oss, bytes) {
3252 http::upload(oss, "uploads/input.bin", bytes)
3253 }
3254
3255 pub fn link(oss, uploaded) {
3256 oss::signed_url(oss, {oss_url: uploaded, expires: 3600})
3257 }
3258 "#
3259 .to_vec(),
3260 )?;
3261
3262 assert_eq!(vm.get_fn("vm_oss_explicit_config::upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3263 assert_eq!(vm.get_fn("vm_oss_explicit_config::http_upload", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3264 assert_eq!(vm.get_fn("vm_oss_explicit_config::link", &[Type::Any, Type::Any])?.ret_ty(), &Type::Any);
3265 Ok(())
3266 }
3267
3268 #[cfg(feature = "http")]
3269 #[test]
3270 fn load_script_accepts_http_serve_inline_config() -> anyhow::Result<()> {
3271 let vm = Vm::with_all()?;
3272 let (_fn_ptr, ty) = vm.load(
3273 br#"
3274 let server_addr = "127.0.0.1:5192";
3275 let http_server = http::serve({
3276 host: server_addr,
3277 ws: true,
3278 upload: "upload",
3279 "static": {
3280 path: "/",
3281 dir: "public/local"
3282 }
3283 });
3284 http_server
3285 "#
3286 .to_vec(),
3287 "arg".into(),
3288 )?;
3289
3290 assert_eq!(ty, Type::Any);
3291 Ok(())
3292 }
3293
3294 #[test]
3295 fn load_script_resolves_import_before_compile() -> anyhow::Result<()> {
3296 let module_path = std::env::temp_dir().join(format!("zust_vm_load_import_{}.zs", std::process::id()));
3297 std::fs::write(&module_path, "pub fn init() { return {ok: true}; }")?;
3298 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
3299
3300 let vm = Vm::with_all()?;
3301 let (_fn_ptr, ty) = vm.load(
3302 format!(
3303 r#"
3304 import("create_scene", "{module_path}");
3305 create_scene::init();
3306 "#
3307 )
3308 .into_bytes(),
3309 "req".into(),
3310 )?;
3311
3312 assert_eq!(ty, Type::Void);
3313 Ok(())
3314 }
3315
3316 #[test]
3317 fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
3318 let vm = Vm::with_all()?;
3319 vm.import_code(
3320 "vm_gpu_layout",
3321 br#"
3322 pub struct Params {
3323 a: u32,
3324 b: u32,
3325 c: u32,
3326 }
3327 "#
3328 .to_vec(),
3329 )?;
3330
3331 let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
3332 assert_eq!(layout.size, 16);
3333 assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
3334
3335 let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
3336 let bytes = layout.pack_map(&value)?;
3337 assert_eq!(bytes.len(), 16);
3338 assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
3339 assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
3340 assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
3341
3342 let read = layout.unpack_map(&bytes)?;
3343 assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
3344 assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
3345 assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
3346 Ok(())
3347 }
3348
3349 #[test]
3350 fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
3351 let vm = Vm::with_all()?;
3352 vm.import_code(
3353 "vm_root_clone_bridge",
3354 br#"
3355 pub fn add_then_reuse(arg) {
3356 let user = {
3357 address: "test-wallet",
3358 points: 20
3359 };
3360 root::add("local/root-clone-bridge-user", user);
3361 user.points = user.points - 7;
3362 root::add("local/root-clone-bridge-user", user);
3363 {
3364 user: user,
3365 points: user.points
3366 }
3367 }
3368
3369 pub fn clone_then_mutate(arg) {
3370 let user = {
3371 profile: {
3372 points: 20
3373 }
3374 };
3375 let copied = user.clone();
3376 copied.profile.points = 13;
3377 user
3378 }
3379 "#
3380 .to_vec(),
3381 )?;
3382
3383 let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
3384 assert_eq!(compiled.ret_ty(), &Type::Any);
3385 let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3386 let arg = Dynamic::Null;
3387 let result = add_then_reuse(&arg);
3388 let result = unsafe { &*result };
3389
3390 assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
3391 let mut json = String::new();
3392 result.to_json(&mut json);
3393 assert!(json.contains("\"points\": 13"));
3394
3395 let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
3396 let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
3397 let result = clone_then_mutate(&arg);
3398 let result = unsafe { &*result };
3399 assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
3400 Ok(())
3401 }
3402
3403 struct CounterForTypedReceiver {
3404 value: i64,
3405 }
3406
3407 extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
3408 unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
3409 }
3410
3411 struct NavMapForFunctionArg;
3412
3413 extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
3414 Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
3415 }
3416
3417 #[derive(Debug, Default)]
3418 struct PropertyForwardingObject {
3419 values: parking_lot::RwLock<BTreeMap<String, Dynamic>>,
3420 }
3421
3422 impl CustomProperty for PropertyForwardingObject {
3423 fn get_key(&self, key: &str) -> Option<Dynamic> {
3424 self.values.read().get(key).cloned()
3425 }
3426
3427 fn set_key(&self, key: &str, value: Dynamic) -> bool {
3428 self.values.write().insert(key.to_string(), value);
3429 true
3430 }
3431 }
3432
3433 extern "C" fn property_forwarding_object_new() -> *const Dynamic {
3434 Box::into_raw(Box::new(Dynamic::custom_with_properties(PropertyForwardingObject::default())))
3435 }
3436
3437 #[test]
3438 fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
3439 let vm = Vm::with_all()?;
3440 vm.add_empty_type("Counter")?;
3441 let counter_ty = vm.get_symbol("Counter", Vec::new())?;
3442 vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
3443 vm.import_code(
3444 "vm_typed_receiver_method",
3445 br#"
3446 pub fn run(value) {
3447 value::<Counter>::get()
3448 }
3449 "#
3450 .to_vec(),
3451 )?;
3452
3453 let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
3454 assert_eq!(compiled.ret_ty(), &Type::I64);
3455 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3456 let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
3457
3458 assert_eq!(run(&value), 42);
3459 Ok(())
3460 }
3461
3462 #[test]
3463 fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3464 let vm = Vm::with_all()?;
3465 vm.add_empty_type("NavMap")?;
3466 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3467 vm.import_code(
3468 "vm_native_custom_arg",
3469 br#"
3470 pub fn add_nav_spawns(world, navmap) {
3471 navmap
3472 }
3473
3474 pub fn run(world) {
3475 let navmap = NavMap::new();
3476 let with_spawns = add_nav_spawns(world, navmap);
3477 with_spawns
3478 }
3479 "#
3480 .to_vec(),
3481 )?;
3482
3483 let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
3484 assert_eq!(compiled.ret_ty(), &Type::Any);
3485 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3486 let world = Dynamic::Null;
3487 let result = run(&world);
3488 let result = unsafe { &*result };
3489
3490 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3491 Ok(())
3492 }
3493
3494 #[test]
3495 fn any_field_assignment_forwards_to_custom_properties() -> anyhow::Result<()> {
3496 let vm = Vm::with_all()?;
3497 vm.add_empty_type("Dialog")?;
3498 vm.add_native_method_ptr("Dialog", "new", &[], Type::Any, property_forwarding_object_new as *const u8)?;
3499 vm.import_code(
3500 "vm_custom_property_forwarding",
3501 br#"
3502 pub fn run() {
3503 let dialog = Dialog::new();
3504 dialog.file_mode = 3;
3505 dialog.file_mode
3506 }
3507 "#
3508 .to_vec(),
3509 )?;
3510
3511 let compiled = vm.get_fn("vm_custom_property_forwarding::run", &[])?;
3512 assert_eq!(compiled.ret_ty(), &Type::Any);
3513 let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3514 let result = unsafe { &*run() };
3515
3516 assert_eq!(result.as_int(), Some(3));
3517 Ok(())
3518 }
3519
3520 #[test]
3521 fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
3522 let vm = Vm::with_all()?;
3523 vm.add_empty_type("NavMap")?;
3524 let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
3525 vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
3526 vm.import_code(
3527 "vm_native_custom_typed_arg",
3528 br#"
3529 pub fn add_nav_spawns(world, navmap) {
3530 navmap
3531 }
3532
3533 pub fn run(world) {
3534 let navmap: NavMap = NavMap::new();
3535 let with_spawns = add_nav_spawns(world, navmap);
3536 with_spawns
3537 }
3538 "#
3539 .to_vec(),
3540 )?;
3541
3542 let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
3543 let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3544 let world = Dynamic::Null;
3545 let result = run(&world);
3546 let result = unsafe { &*result };
3547
3548 assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
3549 Ok(())
3550 }
3551
3552 #[test]
3555 fn dynamic_type_checks_on_null_and_primitive_values() -> anyhow::Result<()> {
3556 let vm = Vm::with_all()?;
3557 vm.import_code(
3558 "vm_dynamic_type_checks",
3559 br#"
3560 pub fn is_list_on_int() {
3561 let x = 42i64;
3562 x.is_list()
3563 }
3564
3565 pub fn is_map_on_int() {
3566 let x = 42i64;
3567 x.is_map()
3568 }
3569
3570 pub fn is_null_on_int() {
3571 let x = 42i64;
3572 x.is_null()
3573 }
3574 "#
3575 .to_vec(),
3576 )?;
3577
3578 let compiled = vm.get_fn("vm_dynamic_type_checks::is_list_on_int", &[])?;
3579 assert_eq!(compiled.ret_ty(), &Type::Bool);
3580 let is_list_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3581 assert!(!is_list_on_int());
3582
3583 let compiled = vm.get_fn("vm_dynamic_type_checks::is_map_on_int", &[])?;
3584 assert_eq!(compiled.ret_ty(), &Type::Bool);
3585 let is_map_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3586 assert!(!is_map_on_int());
3587
3588 let compiled = vm.get_fn("vm_dynamic_type_checks::is_null_on_int", &[])?;
3589 assert_eq!(compiled.ret_ty(), &Type::Bool);
3590 let is_null_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3591 assert!(!is_null_on_int());
3592 Ok(())
3593 }
3594
3595 #[test]
3596 fn void_and_null_are_false_in_boolean_context() -> anyhow::Result<()> {
3597 let vm = Vm::with_all()?;
3598 vm.import_code(
3599 "vm_void_bool_context",
3600 br#"
3601 pub fn run() {
3602 let items = [1i32, 2i32];
3603 let ok1 = !(items.push(3i32) && false);
3604 let ok2 = !(true && items.push(4i32));
3605 let ok3 = null || true;
3606 let ok4 = null || items.len() == 4;
3607 ok1 && ok2 && ok3 && ok4
3608 }
3609 "#
3610 .to_vec(),
3611 )?;
3612
3613 let compiled = vm.get_fn("vm_void_bool_context::run", &[])?;
3614 assert_eq!(compiled.ret_ty(), &Type::Bool);
3615 let run: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3616 assert!(run());
3617 Ok(())
3618 }
3619
3620 #[test]
3621 fn empty_for_loop_range_has_zero_iterations() -> anyhow::Result<()> {
3622 let vm = Vm::with_all()?;
3623 vm.import_code(
3624 "vm_empty_for_range",
3625 br#"
3626 pub fn empty_exclusive() {
3627 let count = 0i32;
3628 for i in 0..0 {
3629 count += i;
3630 }
3631 count
3632 }
3633
3634 pub fn single_inclusive_iteration() {
3635 let count = 0i32;
3636 for i in 5..=5 {
3637 count += i;
3638 }
3639 count
3640 }
3641 "#
3642 .to_vec(),
3643 )?;
3644
3645 let compiled = vm.get_fn("vm_empty_for_range::empty_exclusive", &[])?;
3647 assert_eq!(compiled.ret_ty(), &Type::I64);
3648 let empty_exclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3649 assert_eq!(empty_exclusive(), 0);
3650
3651 let compiled = vm.get_fn("vm_empty_for_range::single_inclusive_iteration", &[])?;
3652 assert_eq!(compiled.ret_ty(), &Type::I64);
3653 let single_inclusive: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3654 assert_eq!(single_inclusive(), 5);
3655 Ok(())
3656 }
3657
3658 #[test]
3659 fn for_loop_range_accepts_dynamic_i64_bounds() -> anyhow::Result<()> {
3660 let vm = Vm::with_all()?;
3661 vm.import_code(
3662 "vm_dynamic_for_range",
3663 br#"
3664 pub fn main() {
3665 let view = {};
3666 view.grid_min_x = -2i64;
3667 view.grid_max_x = 2i64;
3668
3669 let end_x = view.grid_max_x + 1i64;
3670 let count = 0i64;
3671
3672 for x in view.grid_min_x..end_x {
3673 count += 1i64;
3674 }
3675
3676 count
3677 }
3678 "#
3679 .to_vec(),
3680 )?;
3681
3682 let compiled = vm.get_fn("vm_dynamic_for_range::main", &[])?;
3683 assert_eq!(compiled.ret_ty(), &Type::I64);
3684 let main: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3685 assert_eq!(main(), 5);
3686 Ok(())
3687 }
3688
3689 #[test]
3690 fn map_contains_key_on_non_existent_and_nested_keys() -> anyhow::Result<()> {
3691 let vm = Vm::with_all()?;
3692 vm.import_code(
3693 "vm_map_contains",
3694 br#"
3695 pub fn contains_existing(data) {
3696 data.contains("name")
3697 }
3698
3699 pub fn contains_missing(data) {
3700 data.contains("nothing")
3701 }
3702 "#
3703 .to_vec(),
3704 )?;
3705
3706 let compiled = vm.get_fn("vm_map_contains::contains_existing", &[Type::Any])?;
3707 assert_eq!(compiled.ret_ty(), &Type::Bool);
3708 let contains_existing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3709 let data = dynamic::map!("name"=> "test");
3710 assert!(contains_existing(&data));
3711
3712 let compiled = vm.get_fn("vm_map_contains::contains_missing", &[Type::Any])?;
3713 assert_eq!(compiled.ret_ty(), &Type::Bool);
3714 let contains_missing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
3715 assert!(!contains_missing(&data));
3716 Ok(())
3717 }
3718
3719 #[test]
3720 fn list_pop_on_empty_list_returns_null() -> anyhow::Result<()> {
3721 let vm = Vm::with_all()?;
3722 vm.import_code(
3723 "vm_pop_empty",
3724 br#"
3725 pub fn pop_new_list() {
3726 let items = [];
3727 let value = items.pop();
3728 let still_empty = items.len() == 0;
3729 {value: value, empty: still_empty}
3730 }
3731
3732 pub fn pop_until_empty() {
3733 let items = [1i64, 2i64];
3734 items.pop();
3735 let last = items.pop();
3736 let drained = items.pop();
3737 {last: last, drained: drained}
3738 }
3739 "#
3740 .to_vec(),
3741 )?;
3742
3743 let compiled = vm.get_fn("vm_pop_empty::pop_new_list", &[])?;
3744 assert_eq!(compiled.ret_ty(), &Type::Any);
3745 let pop_new_list: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3746 let result = unsafe { &*pop_new_list() };
3747 assert!(result.get_dynamic("value").is_some_and(|v| v.is_null()));
3748 assert_eq!(result.get_dynamic("empty").and_then(|v| v.as_bool()), Some(true));
3749
3750 let compiled = vm.get_fn("vm_pop_empty::pop_until_empty", &[])?;
3751 assert_eq!(compiled.ret_ty(), &Type::Any);
3752 let pop_until_empty: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3753 let result = unsafe { &*pop_until_empty() };
3754 assert_eq!(result.get_dynamic("last").and_then(|v| v.as_int()), Some(1));
3755 assert!(result.get_dynamic("drained").is_some_and(|v| v.is_null()));
3756 Ok(())
3757 }
3758
3759 #[test]
3760 fn void_function_with_multiple_code_paths() -> anyhow::Result<()> {
3761 let vm = Vm::with_all()?;
3762 vm.import_code(
3763 "vm_void_multi_path",
3764 br#"
3765 pub fn log_if_positive(value: i64) {
3766 if value > 0 {
3767 print(value);
3768 return;
3769 }
3770 if value < 0 {
3771 print(-value);
3772 return;
3773 }
3774 print(0);
3775 }
3776 "#
3777 .to_vec(),
3778 )?;
3779
3780 let compiled = vm.get_fn("vm_void_multi_path::log_if_positive", &[Type::I64])?;
3781 assert!(compiled.ret_ty().is_void());
3782 Ok(())
3783 }
3784
3785 #[test]
3786 fn any_method_call_chain_on_returned_dynamic_value() -> anyhow::Result<()> {
3787 let vm = Vm::with_all()?;
3788 vm.import_code(
3789 "vm_any_method_chain",
3790 br#"
3791 pub fn get_tags(data) {
3792 let tags = data.tags;
3793 if tags.is_list() {
3794 return tags.len();
3795 }
3796 0
3797 }
3798 "#
3799 .to_vec(),
3800 )?;
3801
3802 let compiled = vm.get_fn("vm_any_method_chain::get_tags", &[Type::Any])?;
3803 assert_eq!(compiled.ret_ty(), &Type::I64);
3804 let get_tags: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3805 let data = dynamic::map!("tags"=> Dynamic::list(vec!["a".into(), "b".into(), "c".into()]));
3806 assert_eq!(get_tags(&data), 3);
3807
3808 let empty_data = Dynamic::Null;
3809 assert_eq!(get_tags(&empty_data), 0);
3810 Ok(())
3811 }
3812
3813 #[test]
3814 fn infers_any_arg_function_return_before_body_compile() -> anyhow::Result<()> {
3815 let vm = Vm::with_all()?;
3816 vm.import_code(
3817 "vm_infer_any_arg_return",
3818 br#"
3819 pub fn caller(candidate) {
3820 let center = polygon_center(candidate.visualPolygon);
3821 center[0]
3822 }
3823
3824 pub fn polygon_center(point_list) {
3825 let total_x = 0;
3826 let total_y = 0;
3827 let count = 0;
3828 if point_list.is_list() {
3829 for point in point_list {
3830 if point.is_list() && point.len() >= 2 {
3831 total_x += point[0];
3832 total_y += point[1];
3833 count += 1;
3834 }
3835 }
3836 }
3837 if count == 0 {
3838 return [0, 0];
3839 }
3840 [total_x / count, total_y / count]
3841 }
3842 "#
3843 .to_vec(),
3844 )?;
3845
3846 let compiled = vm.get_fn("vm_infer_any_arg_return::caller", &[Type::Any])?;
3847 assert_eq!(compiled.ret_ty(), &Type::Any);
3848 let caller: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
3849 let candidate = dynamic::map!(
3850 "visualPolygon"=> Dynamic::list(vec![
3851 Dynamic::list(vec![2i64.into(), 4i64.into()]),
3852 Dynamic::list(vec![6i64.into(), 8i64.into()]),
3853 ])
3854 );
3855 let result = unsafe { &*caller(&candidate) };
3856 assert_eq!(result.as_int(), Some(4));
3857 Ok(())
3858 }
3859
3860 #[test]
3861 fn recursive_factorial_keeps_static_return_type() -> anyhow::Result<()> {
3862 let vm = Vm::with_all()?;
3863 vm.import_code(
3864 "vm_recursive_factorial",
3865 br#"
3866 fn factorial(n: i64) {
3867 if n <= 1 {
3868 return 1;
3869 }
3870 n * factorial(n - 1)
3871 }
3872
3873 pub fn run(n: i64) {
3874 factorial(n)
3875 }
3876 "#
3877 .to_vec(),
3878 )?;
3879
3880 let compiled = vm.get_fn("vm_recursive_factorial::run", &[Type::I64])?;
3881 assert_eq!(compiled.ret_ty(), &Type::I64);
3882 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3883 assert_eq!(run(5), 120);
3884 Ok(())
3885 }
3886
3887 #[test]
3888 fn explicit_const_generic_function_calls_generate_distinct_variants() -> anyhow::Result<()> {
3889 let vm = Vm::with_all()?;
3890 vm.import_code(
3891 "vm_generic_const_variants",
3892 br#"
3893 fn value<N>() {
3894 N
3895 }
3896
3897 pub fn two() {
3898 value::<2>()
3899 }
3900
3901 pub fn three() {
3902 value::<3>()
3903 }
3904 "#
3905 .to_vec(),
3906 )?;
3907
3908 let compiled = vm.get_fn("vm_generic_const_variants::two", &[])?;
3909 assert_eq!(compiled.ret_ty(), &Type::I32);
3910 let two: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3911 assert_eq!(two(), 2);
3912
3913 let compiled = vm.get_fn("vm_generic_const_variants::three", &[])?;
3914 assert_eq!(compiled.ret_ty(), &Type::I32);
3915 let three: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3916 assert_eq!(three(), 3);
3917 Ok(())
3918 }
3919
3920 #[test]
3921 fn generic_function_body_resolves_private_generic_helper_after_import() -> anyhow::Result<()> {
3922 let vm = Vm::with_all()?;
3923 vm.import_code(
3924 "vm_generic_private_helper",
3925 br#"
3926 fn helper<N>() {
3927 N
3928 }
3929
3930 pub fn bench<N>() {
3931 helper::<N>()
3932 }
3933 "#
3934 .to_vec(),
3935 )?;
3936
3937 let compiled = vm.get_fn_with_params("vm_generic_private_helper::bench", &[], &[Type::ConstInt(7)])?;
3938 assert_eq!(compiled.ret_ty(), &Type::I32);
3939 let run: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
3940 assert_eq!(run(), 7);
3941 Ok(())
3942 }
3943
3944 #[test]
3945 fn const_generic_repeat_array_initializes_all_items() -> anyhow::Result<()> {
3946 let vm = Vm::with_all()?;
3947 vm.import_code(
3948 "vm_generic_repeat_array",
3949 br#"
3950 fn bench<N>() {
3951 let is_prime = [true; N];
3952 is_prime[0] = false;
3953 is_prime[1] = false;
3954 let count = 0i64;
3955 for p in 2i64..N {
3956 if is_prime[p] == true {
3957 count = count + 1;
3958 let step = p;
3959 let j = p * p;
3960 while j < N {
3961 is_prime[j] = false;
3962 j = j + step;
3963 }
3964 }
3965 }
3966 count
3967 }
3968
3969 pub fn run() {
3970 bench::<10>()
3971 }
3972
3973 pub fn run_1000() {
3974 bench::<1000>()
3975 }
3976
3977 pub fn run_100000() {
3978 bench::<100000>()
3979 }
3980 "#
3981 .to_vec(),
3982 )?;
3983
3984 let compiled = vm.get_fn("vm_generic_repeat_array::run", &[])?;
3985 assert_eq!(compiled.ret_ty(), &Type::I64);
3986 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3987 assert_eq!(run(), 4);
3988
3989 let compiled = vm.get_fn("vm_generic_repeat_array::run_1000", &[])?;
3990 assert_eq!(compiled.ret_ty(), &Type::I64);
3991 let run_1000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3992 assert_eq!(run_1000(), 168);
3993
3994 let compiled = vm.get_fn("vm_generic_repeat_array::run_100000", &[])?;
3995 assert_eq!(compiled.ret_ty(), &Type::I64);
3996 let run_100000: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
3997 assert_eq!(run_100000(), 9592);
3998 Ok(())
3999 }
4000
4001 #[test]
4002 fn repeat_array_initializes_scalar_patterns() -> anyhow::Result<()> {
4003 let vm = Vm::with_all()?;
4004 vm.import_code(
4005 "vm_repeat_scalar_patterns",
4006 br#"
4007 pub fn count_true() {
4008 let items = [true; 100000];
4009 let count = 0i64;
4010 for idx in 0i64..100000 {
4011 if items[idx] == true {
4012 count = count + 1;
4013 }
4014 }
4015 count
4016 }
4017
4018 pub fn i32_pair() {
4019 let items = [-7i32; 1000];
4020 items[0i64] + items[999i64]
4021 }
4022
4023 pub fn i64_pair() {
4024 let items = [1234567890123i64; 1000];
4025 items[0i64] + items[999i64]
4026 }
4027
4028 pub fn f64_pair() {
4029 let items = [1.5f64; 1000];
4030 items[0i64] + items[999i64]
4031 }
4032 "#
4033 .to_vec(),
4034 )?;
4035
4036 let compiled = vm.get_fn("vm_repeat_scalar_patterns::count_true", &[])?;
4037 assert_eq!(compiled.ret_ty(), &Type::I64);
4038 let count_true: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4039 assert_eq!(count_true(), 100000);
4040
4041 let compiled = vm.get_fn("vm_repeat_scalar_patterns::i32_pair", &[])?;
4042 assert_eq!(compiled.ret_ty(), &Type::I32);
4043 let i32_pair: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4044 assert_eq!(i32_pair(), -14);
4045
4046 let compiled = vm.get_fn("vm_repeat_scalar_patterns::i64_pair", &[])?;
4047 assert_eq!(compiled.ret_ty(), &Type::I64);
4048 let i64_pair: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4049 assert_eq!(i64_pair(), 2469135780246);
4050
4051 let compiled = vm.get_fn("vm_repeat_scalar_patterns::f64_pair", &[])?;
4052 assert_eq!(compiled.ret_ty(), &Type::F64);
4053 let f64_pair: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4054 assert_eq!(f64_pair(), 3.0);
4055 Ok(())
4056 }
4057
4058 #[test]
4059 fn bool_array_store_normalizes_condition_values() -> anyhow::Result<()> {
4060 let vm = Vm::with_all()?;
4061 vm.import_code(
4062 "vm_bool_array_store",
4063 br#"
4064 pub fn run() {
4065 let items = [false; 4];
4066 items[1] = 3i64 > 2i64;
4067 items[2] = 3i64 < 2i64;
4068 if items[1] == true && items[2] == false {
4069 1i64
4070 } else {
4071 0i64
4072 }
4073 }
4074 "#
4075 .to_vec(),
4076 )?;
4077
4078 let compiled = vm.get_fn("vm_bool_array_store::run", &[])?;
4079 assert_eq!(compiled.ret_ty(), &Type::I64);
4080 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4081 assert_eq!(run(), 1);
4082 Ok(())
4083 }
4084
4085 #[test]
4086 fn bool_array_large_sequential_writes() -> anyhow::Result<()> {
4087 let vm = Vm::with_all()?;
4088 vm.import_code(
4089 "vm_bool_array_large_writes",
4090 br#"
4091 pub fn run() {
4092 let items = [true; 100000];
4093 for idx in 0i64..100000 {
4094 items[idx] = false;
4095 }
4096 let count = 0i64;
4097 for idx in 0i64..100000 {
4098 if items[idx] == false {
4099 count = count + 1;
4100 }
4101 }
4102 count
4103 }
4104 "#
4105 .to_vec(),
4106 )?;
4107
4108 let compiled = vm.get_fn("vm_bool_array_large_writes::run", &[])?;
4109 assert_eq!(compiled.ret_ty(), &Type::I64);
4110 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4111 assert_eq!(run(), 100000);
4112 Ok(())
4113 }
4114
4115 #[test]
4116 fn bool_array_sieve_style_indices_stay_in_bounds() -> anyhow::Result<()> {
4117 let vm = Vm::with_all()?;
4118 vm.import_code(
4119 "vm_bool_array_sieve_indices",
4120 br#"
4121 pub fn run() {
4122 let items = [true; 100000];
4123 let writes = 0i64;
4124 for p in 2i64..100000 {
4125 let step = p;
4126 let j = p * p;
4127 while j < 100000 {
4128 items[j] = false;
4129 writes = writes + 1;
4130 j = j + step;
4131 }
4132 }
4133 writes
4134 }
4135 "#
4136 .to_vec(),
4137 )?;
4138
4139 let compiled = vm.get_fn("vm_bool_array_sieve_indices::run", &[])?;
4140 assert_eq!(compiled.ret_ty(), &Type::I64);
4141 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4142 assert!(run() > 0);
4143 Ok(())
4144 }
4145
4146 #[test]
4147 fn sieve_style_indices_compute_in_bounds_without_array_write() -> anyhow::Result<()> {
4148 let vm = Vm::with_all()?;
4149 vm.import_code(
4150 "vm_sieve_indices_no_write",
4151 br#"
4152 pub fn run() {
4153 let max_j = 0i64;
4154 for p in 2i64..100000 {
4155 let step = p;
4156 let j = p * p;
4157 while j < 100000 {
4158 if j < 0i64 {
4159 return -1i64;
4160 }
4161 if j > max_j {
4162 max_j = j;
4163 }
4164 j = j + step;
4165 }
4166 }
4167 max_j
4168 }
4169 "#
4170 .to_vec(),
4171 )?;
4172
4173 let compiled = vm.get_fn("vm_sieve_indices_no_write::run", &[])?;
4174 assert_eq!(compiled.ret_ty(), &Type::I64);
4175 let run: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4176 assert_eq!(run(), 99999);
4177 Ok(())
4178 }
4179
4180 #[test]
4181 fn dynamic_list_index_sum_uses_static_accumulator_type() -> anyhow::Result<()> {
4182 let vm = Vm::with_all()?;
4183 vm.import_code(
4184 "vm_dynamic_index_sum",
4185 br#"
4186 pub fn sum_list(n: i64) {
4187 let l = [];
4188 for i in 0..n {
4189 l.push(i);
4190 }
4191 let sum = 0i64;
4192 for j in 0..n {
4193 sum = sum + l[j];
4194 }
4195 sum
4196 }
4197 "#
4198 .to_vec(),
4199 )?;
4200
4201 let compiled = vm.get_fn("vm_dynamic_index_sum::sum_list", &[Type::I64])?;
4202 let sum_list_id = vm.jit.write().compiler.symbols.get_id("vm_dynamic_index_sum::sum_list")?;
4203 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_list_id, &[], &[Type::I64]);
4204 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I64)), "local type hints: {:?}", hints);
4205 assert_eq!(compiled.ret_ty(), &Type::I64);
4206 let sum_list: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4207 assert_eq!(sum_list(1000), 499500);
4208 Ok(())
4209 }
4210
4211 #[test]
4212 fn loop_pushed_list_is_typed_vector() -> anyhow::Result<()> {
4213 let vm = Vm::with_all()?;
4214 vm.import_code(
4215 "vm_loop_pushed_list",
4216 br#"
4217 pub fn make(n: i64) {
4218 let l = [];
4219 for i in 0..n {
4220 l.push(i);
4221 }
4222 l
4223 }
4224 "#
4225 .to_vec(),
4226 )?;
4227 let compiled = vm.get_fn("vm_loop_pushed_list::make", &[Type::I64])?;
4228 let make: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4229 let result = unsafe { &*make(3) };
4230 assert!(matches!(result, Dynamic::VecI64(v) if v == &vec![0, 1, 2]), "expected flat VecI64, got: {:?}", result);
4231 Ok(())
4232 }
4233
4234 #[test]
4235 fn inferred_empty_list_uses_typed_dynamic_vector() -> anyhow::Result<()> {
4236 let vm = Vm::with_all()?;
4237 vm.import_code(
4238 "vm_inferred_typed_list",
4239 br#"
4240 pub fn make() {
4241 let l = [];
4242 l.push(1i64);
4243 l
4244 }
4245 "#
4246 .to_vec(),
4247 )?;
4248
4249 let compiled = vm.get_fn("vm_inferred_typed_list::make", &[])?;
4250 assert_eq!(compiled.ret_ty(), &Type::Any);
4251 let make: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4252 let result = unsafe { &*make() };
4253 assert!(matches!(result, Dynamic::VecI64(values) if values == &vec![1]), "result: {:?}", result);
4254 Ok(())
4255 }
4256
4257 #[test]
4258 fn for_in_iterates_list_filled_in_same_function() -> anyhow::Result<()> {
4259 let vm = Vm::with_all()?;
4260 vm.import_code(
4261 "vm_for_in_local_pushed_list",
4262 br#"
4263 pub fn sum_i32_items() {
4264 let items = [];
4265 items.push(6000i32);
4266 items.push(4000i32);
4267 let total = 0i32;
4268 for item in items {
4269 total += item;
4270 }
4271 total
4272 }
4273
4274 pub fn sum_split_bps() {
4275 let splits = [];
4276 splits.push({ bps: "6000" });
4277 splits.push({ bps: 4000 });
4278 let total = 0i32;
4279 let count = 0i32;
4280 for split in splits {
4281 total += split.bps as i32;
4282 count += 1i32;
4283 }
4284 total + count
4285 }
4286 "#
4287 .to_vec(),
4288 )?;
4289
4290 let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_i32_items", &[])?;
4291 assert_eq!(compiled.ret_ty(), &Type::I32);
4292 let sum_i32_items: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4293 assert_eq!(sum_i32_items(), 10000);
4294
4295 let compiled = vm.get_fn("vm_for_in_local_pushed_list::sum_split_bps", &[])?;
4296 assert_eq!(compiled.ret_ty(), &Type::I32);
4297 let sum_split_bps: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4298 assert_eq!(sum_split_bps(), 10002);
4299 Ok(())
4300 }
4301
4302 #[test]
4303 fn inferred_list_shortcuts_cover_scalar_types() -> anyhow::Result<()> {
4304 let vm = Vm::with_all()?;
4305 vm.import_code(
4306 "vm_inferred_list_shortcuts",
4307 br#"
4308 pub fn second_bool() {
4309 let l = [];
4310 l.push(true);
4311 l.push(false);
4312 l[1]
4313 }
4314
4315 pub fn first_u8() {
4316 let l = [];
4317 l.push(7u8);
4318 l[0]
4319 }
4320
4321 pub fn sum_i32(n: i64) {
4322 let l = [];
4323 for i in 0..n {
4324 l.push(i as i32);
4325 }
4326 let sum = 0i32;
4327 for j in 0..n {
4328 sum = sum + l[j];
4329 }
4330 sum
4331 }
4332
4333 pub fn sum_f32(n: i64) {
4334 let l = [];
4335 for i in 0..n {
4336 l.push(i as f32);
4337 }
4338 let sum = 0f32;
4339 for j in 0..n {
4340 sum = sum + l[j];
4341 }
4342 sum
4343 }
4344
4345 pub fn second_str() {
4346 let l = [];
4347 l.push("first");
4348 l.push("second");
4349 l[1]
4350 }
4351 "#
4352 .to_vec(),
4353 )?;
4354
4355 let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_bool", &[])?;
4356 let second_bool_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_bool")?;
4357 let hints = vm.jit.write().compiler.inferred_local_type_hints(second_bool_id, &[], &[]);
4358 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Bool)), "bool local type hints: {:?}", hints);
4359 assert_eq!(compiled.ret_ty(), &Type::Bool);
4360 let second_bool: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4361 assert!(!second_bool());
4362
4363 let compiled = vm.get_fn("vm_inferred_list_shortcuts::first_u8", &[])?;
4364 let first_u8_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::first_u8")?;
4365 let hints = vm.jit.write().compiler.inferred_local_type_hints(first_u8_id, &[], &[]);
4366 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::U8)), "u8 local type hints: {:?}", hints);
4367 assert_eq!(compiled.ret_ty(), &Type::U8);
4368 let first_u8: extern "C" fn() -> u8 = unsafe { std::mem::transmute(compiled.ptr()) };
4369 assert_eq!(first_u8(), 7);
4370
4371 let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_i32", &[Type::I64])?;
4372 let sum_i32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_i32")?;
4373 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_i32_id, &[], &[Type::I64]);
4374 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::I32)), "i32 local type hints: {:?}", hints);
4375 assert_eq!(compiled.ret_ty(), &Type::I32);
4376 let sum_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4377 assert_eq!(sum_i32(100), 4950);
4378
4379 let compiled = vm.get_fn("vm_inferred_list_shortcuts::sum_f32", &[Type::I64])?;
4380 let sum_f32_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::sum_f32")?;
4381 let hints = vm.jit.write().compiler.inferred_local_type_hints(sum_f32_id, &[], &[Type::I64]);
4382 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::F32)), "f32 local type hints: {:?}", hints);
4383 assert_eq!(compiled.ret_ty(), &Type::F32);
4384 let sum_f32: extern "C" fn(i64) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
4385 assert_eq!(sum_f32(10), 45.0);
4386
4387 let compiled = vm.get_fn("vm_inferred_list_shortcuts::second_str", &[])?;
4388 let second_str_id = vm.jit.write().compiler.symbols.get_id("vm_inferred_list_shortcuts::second_str")?;
4389 let hints = vm.jit.write().compiler.inferred_local_type_hints(second_str_id, &[], &[]);
4390 assert!(hints.iter().any(|ty| matches!(ty, Some(Type::List(elem)) if elem.as_ref() == &Type::Str)), "str local type hints: {:?}", hints);
4391 assert_eq!(compiled.ret_ty(), &Type::Str);
4392 let second_str: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4393 let result = unsafe { &*second_str() };
4394 assert_eq!(result.as_str(), "second");
4395 Ok(())
4396 }
4397
4398 #[test]
4399 fn inferred_list_supports_bracket_set_idx() -> anyhow::Result<()> {
4400 let vm = Vm::with_all()?;
4401 vm.import_code(
4402 "vm_inferred_list_set_idx",
4403 br#"
4404 pub fn swap_first_two() {
4405 let items = [];
4406 items.push(1i64);
4407 items.push(2i64);
4408 let j = 0i64;
4409 let a = items[j];
4410 let b = items[j + 1];
4411 items[j] = b;
4412 items[j + 1] = a;
4413 items[0] * 10i64 + items[1]
4414 }
4415
4416 pub fn replace_string() {
4417 let items = [];
4418 items.push("old");
4419 items[0] = "new";
4420 items[0]
4421 }
4422 "#
4423 .to_vec(),
4424 )?;
4425
4426 let compiled = vm.get_fn("vm_inferred_list_set_idx::swap_first_two", &[])?;
4427 assert_eq!(compiled.ret_ty(), &Type::I64);
4428 let swap_first_two: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4429 assert_eq!(swap_first_two(), 21);
4430
4431 let compiled = vm.get_fn("vm_inferred_list_set_idx::replace_string", &[])?;
4432 assert_eq!(compiled.ret_ty(), &Type::Str);
4433 let replace_string: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4434 let result = unsafe { &*replace_string() };
4435 assert_eq!(result.as_str(), "new");
4436 Ok(())
4437 }
4438
4439 #[test]
4440 fn root_get_returns_null_for_missing_key_which_compares_correctly() -> anyhow::Result<()> {
4441 let vm = Vm::with_all()?;
4442 vm.import_code(
4443 "vm_root_get_missing",
4444 br#"
4445 pub fn check_missing() {
4446 let existing = root::get("local/vm_root_get_missing_test");
4447 if existing.is_map() {
4448 return false;
4449 }
4450 true
4451 }
4452 "#
4453 .to_vec(),
4454 )?;
4455
4456 let compiled = vm.get_fn("vm_root_get_missing::check_missing", &[])?;
4457 assert_eq!(compiled.ret_ty(), &Type::Bool);
4458 let check_missing: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4459 assert!(check_missing());
4460 Ok(())
4461 }
4462
4463 #[test]
4464 fn map_get_key_on_null_map_returns_null() -> anyhow::Result<()> {
4465 let vm = Vm::with_all()?;
4466 vm.import_code(
4467 "vm_get_key_null_map",
4468 br#"
4469 pub fn get_key_null(data) {
4470 data.get_key("missing")
4471 }
4472 "#
4473 .to_vec(),
4474 )?;
4475
4476 let compiled = vm.get_fn("vm_get_key_null_map::get_key_null", &[Type::Any])?;
4477 assert_eq!(compiled.ret_ty(), &Type::Any);
4478 let get_key_null: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4479
4480 let data_map = dynamic::map!("exists"=> 1i64);
4481 let missing = unsafe { &*get_key_null(&data_map) };
4482 assert!(missing.is_null());
4483
4484 let null = Dynamic::Null;
4485 let result = unsafe { &*get_key_null(&null) };
4486 assert!(result.is_null());
4487 Ok(())
4488 }
4489
4490 #[test]
4491 fn keys_on_empty_map_returns_empty_list() -> anyhow::Result<()> {
4492 let vm = Vm::with_all()?;
4493 vm.import_code(
4494 "vm_keys_empty_map",
4495 br#"
4496 pub fn empty_map_keys() {
4497 let data = {};
4498 data.keys().len()
4499 }
4500 "#
4501 .to_vec(),
4502 )?;
4503
4504 let compiled = vm.get_fn("vm_keys_empty_map::empty_map_keys", &[])?;
4505 assert_eq!(compiled.ret_ty(), &Type::I32);
4506 let empty_map_keys: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4507 assert_eq!(empty_map_keys(), 0);
4508 Ok(())
4509 }
4510
4511 #[test]
4512 fn cast_between_all_integer_widths() -> anyhow::Result<()> {
4513 let vm = Vm::with_all()?;
4514 vm.import_code(
4515 "vm_cast_integer_widths",
4516 br#"
4517 pub fn i64_to_i32(value: i64) {
4518 value as i32
4519 }
4520
4521 pub fn i32_to_i64(value: i32) {
4522 value as i64
4523 }
4524
4525 pub fn u32_to_i64(value: u32) {
4526 value as i64
4527 }
4528 "#
4529 .to_vec(),
4530 )?;
4531
4532 let compiled = vm.get_fn("vm_cast_integer_widths::i64_to_i32", &[Type::I64])?;
4533 assert_eq!(compiled.ret_ty(), &Type::I32);
4534 let i64_to_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
4535 assert_eq!(i64_to_i32(42), 42);
4536
4537 let compiled = vm.get_fn("vm_cast_integer_widths::i32_to_i64", &[Type::I32])?;
4538 assert_eq!(compiled.ret_ty(), &Type::I64);
4539 let i32_to_i64: extern "C" fn(i32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4540 assert_eq!(i32_to_i64(-1), -1);
4541
4542 let compiled = vm.get_fn("vm_cast_integer_widths::u32_to_i64", &[Type::U32])?;
4543 assert_eq!(compiled.ret_ty(), &Type::I64);
4544 let u32_to_i64: extern "C" fn(u32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4545 assert_eq!(u32_to_i64(42), 42);
4546 Ok(())
4547 }
4548
4549 #[test]
4550 fn boolean_literals_in_complex_expression_trees() -> anyhow::Result<()> {
4551 let vm = Vm::with_all()?;
4552 vm.import_code(
4553 "vm_complex_boolean",
4554 br#"
4555 pub fn exclusive_or(a: bool, b: bool) {
4556 (a && !b) || (!a && b)
4557 }
4558
4559 pub fn implies(a: bool, b: bool) {
4560 !a || b
4561 }
4562 "#
4563 .to_vec(),
4564 )?;
4565
4566 let compiled = vm.get_fn("vm_complex_boolean::exclusive_or", &[Type::Bool, Type::Bool])?;
4567 assert_eq!(compiled.ret_ty(), &Type::Bool);
4568 let exclusive_or: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4569 assert!(exclusive_or(true, false));
4570 assert!(exclusive_or(false, true));
4571 assert!(!exclusive_or(true, true));
4572 assert!(!exclusive_or(false, false));
4573
4574 let compiled = vm.get_fn("vm_complex_boolean::implies", &[Type::Bool, Type::Bool])?;
4575 assert_eq!(compiled.ret_ty(), &Type::Bool);
4576 let implies: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4577 assert!(implies(false, true));
4578 assert!(implies(false, false));
4579 assert!(implies(true, true));
4580 assert!(!implies(true, false));
4581 Ok(())
4582 }
4583
4584 #[test]
4585 fn concrete_struct_method_returning_self_type() -> anyhow::Result<()> {
4586 let vm = Vm::with_all()?;
4587 vm.import_code(
4588 "vm_struct_method_self",
4589 br#"
4590 pub struct Vec3 {
4591 x: f64,
4592 y: f64,
4593 z: f64,
4594 }
4595
4596 impl Vec3 {
4597 pub fn add(self: Vec3, other: Vec3) {
4598 Vec3{x: self.x + other.x, y: self.y + other.y, z: self.z + other.z}
4599 }
4600 }
4601
4602 pub fn run() {
4603 let v1 = Vec3{x: 1.0f64, y: 2.0f64, z: 3.0f64};
4604 let v2 = Vec3{x: 4.0f64, y: 5.0f64, z: 6.0f64};
4605 let sum = v1.add(v2);
4606 sum.x + sum.y + sum.z
4607 }
4608 "#
4609 .to_vec(),
4610 )?;
4611
4612 let compiled = vm.get_fn("vm_struct_method_self::run", &[])?;
4613 assert_eq!(compiled.ret_ty(), &Type::F64);
4614 let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
4615 assert_eq!(run(), 21.0);
4616 Ok(())
4617 }
4618
4619 #[test]
4620 fn deep_nested_struct_access_with_multiple_field_levels() -> anyhow::Result<()> {
4621 let vm = Vm::with_all()?;
4622 vm.import_code(
4623 "vm_deep_nested_struct",
4624 br#"
4625 pub struct A {
4626 value: i64,
4627 }
4628
4629 pub struct B {
4630 a: A,
4631 }
4632
4633 pub struct C {
4634 b: B,
4635 }
4636
4637 pub fn direct_access() {
4638 let c = C{b: B{a: A{value: 99}}};
4639 c.b.a.value
4640 }
4641
4642 pub fn via_variable() {
4643 let c = C{b: B{a: A{value: 77}}};
4644 let b = c.b;
4645 let a = b.a;
4646 a.value
4647 }
4648 "#
4649 .to_vec(),
4650 )?;
4651
4652 let compiled = vm.get_fn("vm_deep_nested_struct::direct_access", &[])?;
4653 assert_eq!(compiled.ret_ty(), &Type::I64);
4654 let direct_access: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4655 assert_eq!(direct_access(), 99);
4656
4657 let compiled = vm.get_fn("vm_deep_nested_struct::via_variable", &[])?;
4658 assert_eq!(compiled.ret_ty(), &Type::I64);
4659 let via_variable: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4660 assert_eq!(via_variable(), 77);
4661 Ok(())
4662 }
4663
4664 #[test]
4665 fn array_index_with_dynamic_value_via_method() -> anyhow::Result<()> {
4666 let vm = Vm::with_all()?;
4667 vm.import_code(
4668 "vm_array_idx_dynamic",
4669 br#"
4670 pub fn get_by_idx(list, idx) {
4671 list.get_idx(idx)
4672 }
4673 "#
4674 .to_vec(),
4675 )?;
4676
4677 let compiled = vm.get_fn("vm_array_idx_dynamic::get_by_idx", &[Type::Any, Type::I64])?;
4678 assert_eq!(compiled.ret_ty(), &Type::Any);
4679 let get_by_idx: extern "C" fn(*const Dynamic, i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4680
4681 let list = Dynamic::list(vec!["a".into(), "b".into()]);
4682 let first = unsafe { &*get_by_idx(&list, 0) };
4683 assert_eq!(first.as_str(), "a");
4684
4685 let out = unsafe { &*get_by_idx(&list, 10) };
4686 assert!(out.is_null());
4687 Ok(())
4688 }
4689
4690 #[test]
4691 fn dynamic_field_access_with_optional_or_fallback() -> anyhow::Result<()> {
4692 let vm = Vm::with_all()?;
4693 vm.import_code(
4694 "vm_dynamic_or_fallback",
4695 br#"
4696 pub fn with_fallback(data) {
4697 if data.contains("name") { data.name } else { "unknown" }
4698 }
4699
4700 pub fn with_fallback_missing(data) {
4701 if data.contains("nickname") { data.nickname } else { "unnamed" }
4702 }
4703 "#
4704 .to_vec(),
4705 )?;
4706
4707 let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback", &[Type::Any])?;
4708 assert_eq!(compiled.ret_ty(), &Type::Any);
4709 let with_fallback: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4710 let data = dynamic::map!("name"=> "Alice");
4711 let result = unsafe { &*with_fallback(&data) };
4712 assert_eq!(result.as_str(), "Alice");
4713
4714 let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback_missing", &[Type::Any])?;
4715 let with_fallback_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
4716 let result = unsafe { &*with_fallback_missing(&data) };
4717 assert_eq!(result.as_str(), "unnamed");
4718 Ok(())
4719 }
4720
4721 #[test]
4722 fn for_in_loop_iterates_over_list_and_map_directly() -> anyhow::Result<()> {
4723 let vm = Vm::with_all()?;
4724 vm.import_code(
4725 "vm_for_in_collection",
4726 br#"
4727 pub fn sum_list(items) {
4728 let total = 0i64;
4729 for item in items {
4730 total = total + 1;
4731 }
4732 total
4733 }
4734
4735 pub fn count_map_keys(data) {
4736 let count = 0i64;
4737 for key in data.keys() {
4738 count = count + 1;
4739 }
4740 count
4741 }
4742
4743 pub fn for_in_list_works(items) {
4744 let exists = false;
4745 for item in items {
4746 exists = true;
4747 }
4748 exists
4749 }
4750
4751 pub fn for_in_map_values_works(data) {
4752 let exists = false;
4753 for value in data {
4754 exists = true;
4755 }
4756 exists
4757 }
4758 "#
4759 .to_vec(),
4760 )?;
4761
4762 let compiled = vm.get_fn("vm_for_in_collection::sum_list", &[Type::Any])?;
4763 assert_eq!(compiled.ret_ty(), &Type::I64);
4764 let sum_list: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4765 let items = Dynamic::list(vec![Dynamic::from(1i64), Dynamic::from(2i64), Dynamic::from(3i64)]);
4766 assert_eq!(sum_list(&items), 3);
4767
4768 let data = dynamic::map!("x"=> 1i64, "y"=> 2i64);
4769 let compiled = vm.get_fn("vm_for_in_collection::count_map_keys", &[Type::Any])?;
4770 assert_eq!(compiled.ret_ty(), &Type::I64);
4771 let count_map_keys: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
4772 assert_eq!(count_map_keys(&data), 2);
4773
4774 let compiled = vm.get_fn("vm_for_in_collection::for_in_list_works", &[Type::Any])?;
4775 assert_eq!(compiled.ret_ty(), &Type::Bool);
4776 let for_in_list_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4777 let empty = Dynamic::list(Vec::new());
4778 assert!(!for_in_list_works(&empty));
4779 assert!(for_in_list_works(&items));
4780
4781 let compiled = vm.get_fn("vm_for_in_collection::for_in_map_values_works", &[Type::Any])?;
4782 assert_eq!(compiled.ret_ty(), &Type::Bool);
4783 let for_in_map_values_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
4784 let empty_map = dynamic::map!();
4785 assert!(!for_in_map_values_works(&empty_map));
4786 assert!(for_in_map_values_works(&data));
4787
4788 Ok(())
4789 }
4790
4791 #[test]
4792 fn concurrent_100_threads_no_memory_leak() -> anyhow::Result<()> {
4793 let vm = Vm::with_all()?;
4794 vm.import_code(
4795 "vm_stress",
4796 br#"
4797 pub fn heavy_alloc(idx: i64) {
4798 let items = [];
4799 let i = 0;
4800 while i < 50 {
4801 items.push({
4802 id: i + idx,
4803 name: "item-" + i,
4804 tags: ["tag-a", "tag-b", "tag-c"],
4805 meta: {
4806 created: 1234567890i64,
4807 score: (i * 3.14f64) as i64,
4808 extra: "prefix/" + i + "/" + idx
4809 }
4810 });
4811 i = i + 1;
4812 }
4813 items
4814 }
4815
4816 pub fn string_concat_stress() {
4817 let i = 0;
4818 let result = "";
4819 while i < 200 {
4820 result = result + "data-" + i + ",";
4821 i = i + 1;
4822 }
4823 result
4824 }
4825 "#
4826 .to_vec(),
4827 )?;
4828
4829 let (heavy_ptr, _) = vm.get_fn_ptr("vm_stress::heavy_alloc", &[Type::I64])?;
4830 let (concat_ptr, _) = vm.get_fn_ptr("vm_stress::string_concat_stress", &[])?;
4831
4832 let threads: usize = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4).max(100);
4833 let iters_per_thread = 200;
4834 let total_calls = threads * iters_per_thread * 2;
4835
4836 let before = current_rss_kb();
4837 eprintln!("threads={threads} iters_per_thread={iters_per_thread} total_calls={total_calls} rss_before={before}KB");
4838
4839 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4841 let r1 = current_rss_kb();
4842 eprintln!("rss_after_round1={r1}KB");
4843
4844 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4846 let r2 = current_rss_kb();
4847 eprintln!("rss_after_round2={r2}KB");
4848
4849 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4851 let r3 = current_rss_kb();
4852 eprintln!("rss_after_round3={r3}KB");
4853
4854 run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
4856 let r4 = current_rss_kb();
4857 eprintln!("rss_after_round4={r4}KB");
4858
4859 let d12 = r2.saturating_sub(r1);
4861 let d23 = r3.saturating_sub(r2);
4862 let d34 = r4.saturating_sub(r3);
4863 eprintln!("delta_r1→r2={d12}KB delta_r2→r3={d23}KB delta_r3→r4={d34}KB");
4864
4865 let max_growth_kb = 20 * 1024;
4867 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)");
4868
4869 Ok(())
4870 }
4871
4872 fn run_stress_round(threads: usize, iters: usize, heavy_ptr: usize, concat_ptr: usize) {
4873 std::thread::scope(|scope| {
4874 let mut handles = Vec::with_capacity(threads);
4875 for t in 0..threads {
4876 let heavy_ptr = heavy_ptr;
4877 let concat_ptr = concat_ptr;
4878 handles.push(scope.spawn(move || {
4879 let heavy_fn: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(heavy_ptr as *const u8) };
4880 let concat_fn: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(concat_ptr as *const u8) };
4881 for i in 0..iters {
4882 let r_ptr = heavy_fn((t * iters + i) as i64);
4884 assert!(!r_ptr.is_null());
4885 unsafe {
4886 let r = &*r_ptr;
4887 assert!(r.len() > 0, "heavy_alloc returned empty list");
4888 drop(Box::from_raw(r_ptr as *mut Dynamic));
4889 }
4890
4891 let s_ptr = concat_fn();
4893 assert!(!s_ptr.is_null());
4894 unsafe {
4895 let s = &*s_ptr;
4896 assert!(s.len() > 0, "string_concat_stress returned empty");
4897 drop(Box::from_raw(s_ptr as *mut Dynamic));
4898 }
4899 }
4900 }));
4901 }
4902 for h in handles {
4903 h.join().unwrap();
4904 }
4905 });
4906 }
4907
4908 fn current_rss_kb() -> u64 {
4909 let pid = std::process::id();
4911 if let Ok(output) = std::process::Command::new("ps").args(["-p", &pid.to_string(), "-o", "rss="]).output() {
4912 if let Ok(s) = String::from_utf8(output.stdout) {
4913 if let Some(kb) = s.trim().parse::<u64>().ok() {
4914 return kb;
4915 }
4916 }
4917 }
4918 if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
4920 let parts: Vec<&str> = statm.split_whitespace().collect();
4921 if let Some(rss_pages) = parts.get(1).and_then(|s| s.parse::<u64>().ok()) {
4922 return rss_pages * 4; }
4924 }
4925 0
4926 }
4927}