1pub mod analysis;
4mod argument_defaults;
5pub mod bigint_pool;
6pub mod bounds;
7pub mod box_types;
8mod call_arguments;
9pub mod cast_check;
10mod cast_diagnostics;
11pub mod classes;
12mod closure_coercions;
13pub(crate) use closure_coercions::ADAPTER_ORIGINAL_FIELD;
14pub mod closures;
15pub mod dependency_usage;
16pub mod dwarf;
17mod field_guards;
18mod field_lookup;
19pub mod field_name_strings;
20pub mod field_names;
21pub mod function_adapters;
22pub mod function_emitter;
23pub mod imported_classes;
24pub mod init_guard;
25pub mod intrinsics;
26#[cfg(test)]
27mod invariant_tests;
28mod nullish;
29pub mod recursive_validators;
30mod runtime_descriptors;
31mod runtime_values;
32pub mod string_pool;
33pub mod symbol_table;
34mod this_binding;
35pub mod throw;
36pub mod user_subtypes;
37mod vtable_walk;
38
39use crate::compiler_error::{CompilerFailure, CompilerStage};
40use crate::tree_height;
41use std::borrow::Cow;
42use std::collections::{BTreeMap, BTreeSet};
43
44use wasm_encoder::{
45 AbstractHeapType, CodeSection, ConstExpr, CustomSection, DataCountSection, DataSection,
46 EntityType, ExportKind as WasmExportKind, ExportSection, FunctionSection, GlobalSection,
47 GlobalType, HeapType, Ieee64, ImportSection, Instruction, Module, RefType, StartSection,
48 TagKind, TagType, TypeSection, ValType,
49};
50
51use crate::{
52 LineIndex, MangledName, PackageDeclaration, Param, StmtId, Type, TypeKind, TypedAst, ValueKind,
53 ValueSymbol,
54};
55use analysis::CodegenAnalysis;
56use bigint_pool::BigIntPool;
57use function_emitter::FunctionEmitter;
58use function_emitter::stmt::emit_statement;
59use string_pool::StringPool;
60
61pub(crate) fn internal_failure(message: impl Into<String>) -> CompilerFailure {
62 CompilerFailure::Internal {
63 stage: CompilerStage::Codegen,
64 span: None,
65 message: message.into(),
66 }
67}
68
69pub(crate) fn is_host_package(name: &str) -> bool {
72 name.starts_with("submilli:")
73}
74
75fn is_resultless(owner: &str, ret: &Type) -> bool {
79 ret.is_void() && is_host_package(owner)
80}
81
82pub(crate) fn next_index(counter: &mut u32) -> Result<u32, CompilerFailure> {
85 let index = *counter;
86 *counter = index.checked_add(1).ok_or_else(index_space_exhausted)?;
87 Ok(index)
88}
89
90pub(crate) fn wasm_u32(value: usize) -> Result<u32, CompilerFailure> {
92 u32::try_from(value).map_err(|_| index_space_exhausted())
93}
94
95pub(crate) fn parameter_local(position: usize) -> Result<u32, CompilerFailure> {
98 wasm_u32(position)?
99 .checked_add(1)
100 .ok_or_else(index_space_exhausted)
101}
102
103#[derive(Clone, Copy, Debug, Default)]
106pub struct GuardedBodies {
107 pub equals: u32,
108 pub to_json: u32,
109 pub hash: u32,
110}
111
112fn index_space_exhausted() -> CompilerFailure {
113 CompilerFailure::Limit {
114 stage: CompilerStage::Codegen,
115 span: None,
116 message: "the module exceeds a WebAssembly index or size limit".into(),
117 help: vec!["split the program into smaller modules".into()],
118 }
119}
120
121fn static_value_tag(name: &str) -> String {
125 if name.starts_with(|c: char| c.is_ascii_uppercase()) {
126 format!("function {name}() {{ [native code] }}")
127 } else {
128 format!("[object {name}]")
129 }
130}
131
132fn static_interface_of<'a>(
137 defs: &'a PackageDeclaration,
138 ty: &Type,
139) -> Option<&'a crate::TypeSymbol> {
140 fn find<'a>(
141 types: &'a std::collections::BTreeMap<String, crate::TypeSymbol>,
142 namespaces: &'a std::collections::BTreeMap<String, crate::NamespaceSymbol>,
143 mangled: &crate::MangledName,
144 ) -> Option<&'a crate::TypeSymbol> {
145 types
146 .values()
147 .find(|t| t.mangled_name == *mangled)
148 .or_else(|| {
149 namespaces
150 .values()
151 .find_map(|ns| find(&ns.types, &ns.namespaces, mangled))
152 })
153 }
154 let Type::InterfaceRef { mangled, .. } = ty.peel() else {
155 return None;
156 };
157 find(&defs.types, &defs.namespaces, mangled).filter(|ts| {
158 matches!(
159 &ts.kind,
160 TypeKind::Interface {
161 dispatch: crate::Dispatch::Static,
162 ..
163 }
164 )
165 })
166}
167
168#[allow(clippy::too_many_arguments)]
169fn import_value_symbol(
170 name: &str,
171 value: &ValueSymbol,
172 defs: &PackageDeclaration,
173 intrinsics: &intrinsics::IntrinsicTypeIndices,
174 next_type_idx: &mut u32,
175 next_func_idx: &mut u32,
176 next_global_idx: &mut u32,
177 types: &mut wasm_encoder::TypeSection,
178 import_section: &mut wasm_encoder::ImportSection,
179 symbols: &mut SymbolTable,
180) -> Result<(), crate::compiler_error::CompilerFailure> {
181 use wasm_encoder::{EntityType, GlobalType, ValType};
182 match &value.kind {
183 ValueKind::Function { params, ret, .. } => {
184 let sig_idx = *next_type_idx;
185 crate::codegen::next_index(next_type_idx)?;
186 let raw_string_abi = defs.package_name.as_str() == crate::runtime::JSON_MODULE_NAME;
191 let (mut param_types, result_types): (Vec<ValType>, Vec<ValType>) = if raw_string_abi {
192 json_host_signature(name, params, symbols, intrinsics)?
193 } else {
194 (
195 params
196 .iter()
197 .map(|p| symbols.value_type(&p.ty))
198 .collect::<Result<_, _>>()?,
199 if is_resultless(&defs.package_name, ret) {
200 vec![]
201 } else {
202 symbols.wasm_result(ret)?
203 },
204 )
205 };
206 if defs.runtime_generics.contains(&value.mangled_name) {
207 symbols
208 .runtime_generic_functions
209 .insert(value.mangled_name.clone());
210 param_types.push(runtime_descriptors::environment_type(symbols)?);
211 }
212 if is_host_package(&defs.package_name) {
213 symbols.record_host_parameter_defaults(
214 *next_func_idx,
215 params.iter().map(|param| param.default.clone()).collect(),
216 );
217 }
218 if result_types.is_empty() {
219 symbols.resultless_functions.insert(*next_func_idx);
220 }
221 types.ty().function(param_types, result_types);
222 import_section.import(
223 defs.package_name.as_str(),
224 value.mangled_name.as_str(),
225 EntityType::Function(sig_idx),
226 );
227 if let Some(metadata) = call_arguments::metadata(params.iter().map(|param| {
228 (
229 param
230 .default
231 .as_ref()
232 .or(param.optional.then_some(&crate::DefaultValue::Undefined)),
233 param.rest,
234 )
235 })) {
236 symbols
237 .function_argument_metadata
238 .insert(value.mangled_name.clone(), metadata);
239 }
240 symbols.record_imported_fn(
241 value.mangled_name.clone(),
242 *next_func_idx,
243 params.iter().map(|p| p.ty.clone()).collect(),
244 ret.clone(),
245 raw_string_abi,
246 );
247 crate::codegen::next_index(next_func_idx)?;
248 }
249 ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => {
250 if let Some(ts) = static_interface_of(defs, ty) {
254 symbols.record_iface_dispatch(ts.mangled_name.clone(), crate::Dispatch::Static);
255 symbols.record_static_value_tag(
256 ts.mangled_name.clone(),
257 static_value_tag(&value.name),
258 );
259 return Ok(());
260 }
261 let global_ty = GlobalType {
266 val_type: global_val_type(ty, symbols)?,
267 mutable: true,
268 shared: false,
269 };
270 import_section.import(
271 defs.package_name.as_str(),
272 value.mangled_name.as_str(),
273 EntityType::Global(global_ty),
274 );
275 symbols.record_typed_global(value.mangled_name.clone(), *next_global_idx, ty.clone());
276 crate::codegen::next_index(next_global_idx)?;
277 }
278 };
279 Ok(())
280}
281
282fn declared_wrapper_abi(
303 symbols: &SymbolTable,
304 mangled: &crate::MangledName,
305 sig: &crate::MethodSig,
306 has_receiver: bool,
307) -> Result<symbol_table::MethodSlotAbi, crate::compiler_error::CompilerFailure> {
308 let receiver_offset = usize::from(has_receiver);
309 if let Some(imported) = symbols.top_level_fn(mangled)
310 && sig.params.len().checked_add(receiver_offset) == Some(imported.params.len())
311 {
312 return Ok(symbol_table::MethodSlotAbi {
313 params: imported
314 .params
315 .iter()
316 .skip(receiver_offset)
317 .map(|ty| symbols.value_type(ty))
318 .collect::<Result<_, _>>()?,
319 ret: if symbols.resultless_functions.contains(&imported.wasm_idx) {
320 None
321 } else {
322 symbols.wasm_result(&imported.ret)?.first().copied()
323 },
324 });
325 }
326 Ok(symbol_table::MethodSlotAbi {
327 params: sig
328 .params
329 .iter()
330 .map(|p| symbols.value_type(&p.ty))
331 .collect::<Result<_, _>>()?,
332 ret: if is_resultless(mangled.as_str(), &sig.ret) {
333 None
334 } else {
335 symbols.wasm_result(&sig.ret)?.first().copied()
336 },
337 })
338}
339
340#[allow(clippy::too_many_arguments)]
341fn import_json_host_function(
342 name: &str,
343 intrinsics: &intrinsics::IntrinsicTypeIndices,
344 next_type_idx: &mut u32,
345 next_func_idx: &mut u32,
346 types: &mut wasm_encoder::TypeSection,
347 import_section: &mut wasm_encoder::ImportSection,
348 symbols: &mut SymbolTable,
349) -> Result<(), crate::compiler_error::CompilerFailure> {
350 let mangled = crate::mangle::host(crate::runtime::JSON_MODULE_NAME, name);
351 if symbols.func_idx(&mangled).is_some() {
352 return Ok(());
353 }
354
355 let sig_idx = *next_type_idx;
356 crate::codegen::next_index(next_type_idx)?;
357 let (param_types, result_types) = json_host_signature(name, &[], symbols, intrinsics)?;
358 types.ty().function(param_types, result_types);
359 import_section.import(
360 crate::runtime::JSON_MODULE_NAME,
361 mangled.as_str(),
362 EntityType::Function(sig_idx),
363 );
364 symbols.record_func(mangled, *next_func_idx);
365 crate::codegen::next_index(next_func_idx)?;
366
367 Ok(())
368}
369
370pub use symbol_table::SymbolTable;
371
372#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct CodegenError {
374 message: &'static str,
375 span: crate::Span,
376}
377
378impl std::fmt::Display for CodegenError {
379 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 write!(out, "internal code generation error: {}", self.message)
381 }
382}
383
384impl std::error::Error for CodegenError {}
385
386impl From<CodegenError> for CompilerFailure {
387 fn from(error: CodegenError) -> Self {
388 Self::Internal {
389 stage: CompilerStage::Codegen,
390 span: Some(error.span),
391 message: error.message.to_owned(),
392 }
393 }
394}
395
396impl CodegenError {
397 pub fn diagnostic(self) -> crate::Diagnostic {
398 crate::Diagnostic {
399 severity: crate::Severity::Error,
400 span: self.span,
401 message: self.to_string(),
402 help: vec!["report this compiler error with the source program".into()],
403 notes: Vec::new(),
404 }
405 }
406}
407
408pub struct CodegenCtx<'a> {
409 pub ta: &'a TypedAst,
410 pub strings: &'a StringPool,
411 pub bigints: &'a BigIntPool,
412 pub symbols: &'a SymbolTable,
413 pub source: &'a str,
414 pub line_index: &'a LineIndex,
415 pub file: crate::FileId,
418 pub validator_bodies: &'a recursive_validators::ValidatorBodies,
420 pub type_info: &'a crate::TypeInfoTable,
421 pub package_string_global_idx: Option<u32>,
422 failure: std::cell::Cell<Option<CompilerFailure>>,
423 validator_steps_left: std::cell::Cell<u64>,
426 validator_root: std::cell::Cell<Option<Type>>,
429 check_is_standalone: std::cell::Cell<bool>,
431}
432
433impl CodegenCtx<'_> {
434 fn require<T>(&self, value: Option<T>, message: &'static str) -> Option<T> {
437 if value.is_none() {
438 self.fail(message);
439 }
440 value
441 }
442
443 fn latch<T>(&self, result: Result<T, CompilerFailure>) -> Option<T> {
447 match result {
448 Ok(value) => Some(value),
449 Err(failure) => {
450 self.record_failure(failure);
451 None
452 }
453 }
454 }
455
456 fn check_failure(&self) -> Result<(), CompilerFailure> {
457 match self.failure.take() {
458 Some(error) => Err(error),
459 None => Ok(()),
460 }
461 }
462
463 fn fail(&self, message: &'static str) {
464 self.record_failure(
465 CodegenError {
466 message,
467 span: crate::Span::at(self.file),
468 }
469 .into(),
470 );
471 }
472
473 fn charge_validator_step(&self, emitter: &mut FunctionEmitter) -> Result<(), CompilerFailure> {
479 self.charge_validator_steps(emitter, 1)
480 }
481
482 fn charge_validator_steps(
485 &self,
486 emitter: &mut FunctionEmitter,
487 steps: u64,
488 ) -> Result<(), CompilerFailure> {
489 use crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS;
490 self.check_function_limits(emitter)?;
491 let Some(left) = self.validator_steps_left.get().checked_sub(steps) else {
492 return Err(self.validator_limit(
493 emitter.last_source_span(),
494 format!("exceeds the compiler limit of {MAX_INLINE_VALIDATOR_STEPS} steps"),
495 "check against a smaller type, or restructure the interfaces so members do not repeat the same nested interface",
496 ));
497 };
498 self.validator_steps_left.set(left);
499 Ok(())
500 }
501
502 fn check_function_limits(&self, emitter: &mut FunctionEmitter) -> Result<(), CompilerFailure> {
512 let Some(limit) = FunctionLimit::exceeded(emitter.body_size(), emitter.local_count())
513 else {
514 return Ok(());
515 };
516 let check_has_emitted_code =
519 self.validator_steps_left.get() < crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS;
520 if !check_has_emitted_code {
521 let span = match limit {
522 FunctionLimit::BodyBytes => emitter.body_limit_span(),
523 FunctionLimit::Locals => emitter.locals_limit_span(),
524 };
525 return Err(limit.failure(span));
526 }
527 let fix = if self.check_is_standalone.get() {
528 "check against a smaller type"
529 } else {
530 "check against a smaller type, or move some of the function's checks into another function"
531 };
532 Err(self.validator_limit(emitter.last_source_span(), limit.check_exceeds(), fix))
533 }
534
535 fn check_finished_function(&self, function: FinishedFunction) {
539 let Some(limit) = FunctionLimit::exceeded(function.body_bytes, function.locals) else {
540 return;
541 };
542 let span = match limit {
543 FunctionLimit::BodyBytes => function.body_limit_span,
544 FunctionLimit::Locals => function.locals_limit_span,
545 };
546 self.record_failure(limit.failure(span));
547 }
548
549 fn validator_limit(
551 &self,
552 span: Option<crate::Span>,
553 exceeded: String,
554 fix: &str,
555 ) -> CompilerFailure {
556 let root = self.validator_root.take();
557 let subject = match &root {
558 Some(ty) => format!("the runtime check of `{}`", abbreviated(ty)),
559 None => "a runtime type check".to_string(),
560 };
561 self.validator_root.set(root);
562 CompilerFailure::Limit {
563 stage: crate::compiler_error::CompilerStage::Codegen,
564 span,
565 message: format!("{subject} {exceeded}"),
566 help: vec![
567 "a runtime check of a value against an interface includes the checks of every member, so interfaces whose members reference other interfaces several times multiply its size".into(),
568 fix.into(),
569 ],
570 }
571 }
572
573 fn checking<T>(&self, ty: &Type, emit: impl FnOnce() -> T) -> T {
577 if let Some(outer) = self.validator_root.take() {
578 self.validator_root.set(Some(outer));
579 return emit();
580 }
581 self.validator_steps_left
582 .set(crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS);
583 self.validator_root.set(Some(ty.clone()));
584 let result = emit();
585 self.validator_root.set(None);
586 result
587 }
588
589 fn checking_standalone<T>(&self, ty: &Type, emit: impl FnOnce() -> T) -> T {
592 self.check_is_standalone.set(true);
593 let result = self.checking(ty, emit);
594 self.check_is_standalone.set(false);
595 result
596 }
597
598 fn record_failure(&self, failure: CompilerFailure) {
600 let first = self.failure.take().unwrap_or(failure);
601 self.failure.set(Some(first));
602 }
603}
604
605#[derive(Clone, Copy)]
607enum FunctionLimit {
608 BodyBytes,
610 Locals,
613}
614
615impl FunctionLimit {
616 fn exceeded(body_bytes: usize, locals: u32) -> Option<Self> {
620 if locals > crate::compiler_limits::MAX_FUNCTION_LOCALS {
621 Some(Self::Locals)
622 } else if !Self::body_fits(body_bytes) {
623 Some(Self::BodyBytes)
624 } else {
625 None
626 }
627 }
628
629 fn body_fits(bytes: usize) -> bool {
631 bytes <= crate::compiler_limits::MAX_FUNCTION_BODY_BYTES
632 }
633
634 fn failure(self, span: Option<crate::Span>) -> CompilerFailure {
637 CompilerFailure::Limit {
638 stage: crate::compiler_error::CompilerStage::Codegen,
639 span,
640 message: format!("the function this code compiles into {}", self.function_exceeds()),
641 help: vec![
642 "split the code into smaller functions; top-level statements compile into one function, and runtime type checks of large types are emitted inline in the function that contains them".into(),
643 ],
644 }
645 }
646
647 fn function_exceeds(self) -> String {
649 use crate::compiler_limits::{MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_LOCALS};
650 match self {
651 Self::BodyBytes => {
652 format!("is larger than the {MAX_FUNCTION_BODY_BYTES} bytes a function may have")
653 }
654 Self::Locals => {
655 format!("needs more than the {MAX_FUNCTION_LOCALS} locals a function may have")
656 }
657 }
658 }
659
660 fn check_exceeds(self) -> String {
663 use crate::compiler_limits::{MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_LOCALS};
664 match self {
665 Self::BodyBytes => format!(
666 "makes its function larger than the {MAX_FUNCTION_BODY_BYTES} bytes a function may have"
667 ),
668 Self::Locals => {
669 format!("needs more than the {MAX_FUNCTION_LOCALS} locals a function may have")
670 }
671 }
672 }
673}
674
675struct FinishedFunction {
677 body_bytes: usize,
678 body_limit_span: Option<crate::Span>,
679 locals: u32,
680 locals_limit_span: Option<crate::Span>,
681}
682
683fn abbreviated(ty: &Type) -> String {
686 const MAX_CHARS: usize = 120;
687 let text = ty.to_string();
688 if text.chars().count() <= MAX_CHARS {
689 return text;
690 }
691 let mut short: String = text.chars().take(MAX_CHARS).collect();
692 short.push('…');
693 short
694}
695
696pub fn codegen(
697 source: &str,
698 filename: &str,
699 file: crate::FileId,
700 ta: &TypedAst,
701 dependencies: &[&PackageDeclaration],
702) -> Result<Vec<u8>, CompilerFailure> {
703 Ok(codegen_with_type_info(source, filename, file, ta, dependencies)?.wasm)
704}
705
706pub fn codegen_owned_by(
714 owning_package: &str,
715 source: &str,
716 filename: &str,
717 file: crate::FileId,
718 ta: &TypedAst,
719 dependencies: &[&PackageDeclaration],
720) -> Result<GeneratedModule, CompilerFailure> {
721 codegen_inner(
722 source,
723 filename,
724 file,
725 ta,
726 dependencies,
727 owning_package,
728 None,
729 )
730}
731
732#[derive(Clone, Debug, PartialEq)]
733pub struct GeneratedModule {
734 pub wasm: Vec<u8>,
735 pub type_info: crate::TypeInfoTable,
736 pub runtime_functions: BTreeMap<crate::MangledName, crate::RuntimeFunction>,
737 pub runtime_globals: BTreeMap<crate::MangledName, Type>,
738 pub closure_caches: BTreeSet<crate::MangledName>,
739}
740
741pub fn codegen_with_type_info(
742 source: &str,
743 filename: &str,
744 file: crate::FileId,
745 ta: &TypedAst,
746 dependencies: &[&PackageDeclaration],
747) -> Result<GeneratedModule, CompilerFailure> {
748 codegen_inner(
749 source,
750 filename,
751 file,
752 ta,
753 dependencies,
754 &ta.package_name,
755 None,
756 )
757}
758
759pub fn codegen_package_with_type_info(
760 sources: &crate::Sources,
761 file: crate::FileId,
762 ta: &TypedAst,
763 dependencies: &[&PackageDeclaration],
764) -> Result<GeneratedModule, CompilerFailure> {
765 let source = sources.get(file).ok_or_else(|| {
766 crate::source::SourceError::UnknownFile { file }
767 .into_compiler_failure(CompilerStage::Codegen)
768 })?;
769 codegen_inner(
770 source.text(),
771 source.path.as_str(),
772 file,
773 ta,
774 dependencies,
775 &ta.package_name,
776 Some(sources),
777 )
778}
779
780fn codegen_inner(
781 source: &str,
782 filename: &str,
783 file: crate::FileId,
784 ta: &TypedAst,
785 dependencies: &[&PackageDeclaration],
786 owning_package: &str,
787 package_sources: Option<&crate::Sources>,
788) -> Result<GeneratedModule, CompilerFailure> {
789 tree_height::check_typed(ta, CompilerStage::Codegen)?;
790 u32::try_from(source.len()).map_err(|_| CompilerFailure::Limit {
791 stage: CompilerStage::Codegen,
792 span: None,
793 message: "source exceeds the 32-bit source-offset limit".into(),
794 help: vec!["split the source into smaller modules".into()],
795 })?;
796 let line_index = LineIndex::new(source)
797 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
798 let debug_sources = dwarf::DebugSources {
799 file,
800 filename,
801 index: &line_index,
802 package: package_sources.map(|sources| dwarf::PackageSources {
803 sources,
804 name: owning_package,
805 }),
806 };
807 for id in ta
808 .expr_ids()
809 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?
810 {
811 let expr = ta
812 .try_expr(id)
813 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
814 debug_sources.location(expr.span)?;
815 }
816 for id in ta
817 .stmt_ids()
818 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?
819 {
820 let stmt = ta
821 .try_stmt(id)
822 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
823 debug_sources.location(stmt.span)?;
824 }
825 let source_main_return_ty = ta
826 .functions
827 .iter()
828 .find(|function| function.name.name == "main")
829 .map(|function| function.return_type.clone());
830 let lowered = runtime_values::lower(ta, dependencies)?;
831 let ta = &lowered;
832 let lowered_dependencies: Vec<_> = dependencies
833 .iter()
834 .map(|defs| runtime_values::lower_declaration(defs))
835 .collect::<Result<_, _>>()?;
836 let dependencies: Vec<_> = lowered_dependencies.iter().collect();
837 let dependencies = dependencies.as_slice();
838 let analysis = CodegenAnalysis::collect(ta, dependencies)?;
839 let pool = analysis.string_pool;
840 let bigint_pool = analysis.bigint_pool;
841 let dependency_usage = analysis.dependency_usage.finish(dependencies);
842
843 let mut module = Module::new();
844 let mut types = TypeSection::new();
845 let mut import_section = ImportSection::new();
846 let mut symbols = SymbolTable::default();
847 let mut next_type_idx: u32 = 0;
848 let mut next_func_idx: u32 = 0;
849
850 let intrinsics = intrinsics::declare_intrinsic_types(&mut types);
854 symbols.set_intrinsic_type_indices(intrinsics);
855 next_type_idx = next_type_idx
856 .checked_add(intrinsics::INTRINSIC_TYPE_COUNT)
857 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
858 field_names::declare_optional_name_type(
859 &mut types,
860 &mut symbols,
861 &mut next_type_idx,
862 intrinsics,
863 )?;
864
865 let error_tag_func_type_idx = next_type_idx;
874 types.ty().function(
875 [ValType::Ref(RefType {
876 nullable: false,
877 heap_type: HeapType::Concrete(intrinsics.error),
878 })],
879 Vec::<ValType>::new(),
880 );
881 crate::codegen::next_index(&mut next_type_idx)?;
882 import_section.import(
883 crate::runtime::prelude::MODULE_NAME,
884 crate::mangle::prelude("__error_tag").as_str(),
885 EntityType::Tag(TagType {
886 kind: TagKind::Exception,
887 func_type_idx: error_tag_func_type_idx,
888 }),
889 );
890 symbols.set_error_tag_idx(0);
891
892 let dependency_types = dependency_usage.dependency_types(dependencies);
896 let validator_bodies =
897 recursive_validators::ValidatorBodies::collect(ta, dependency_types.iter());
898 let recursive_validators =
899 recursive_validators::discover(ta, dependency_types.iter(), &validator_bodies)?;
900 let mut all_shapes = ta.shapes.clone();
901 all_shapes.extend(recursive_validators.extra_shapes.iter().cloned());
902
903 let dependency_shapes = dependency_usage.dependency_shapes(dependencies);
904 let user_emitted_types: Vec<Type> =
905 user_subtypes::collect_object_shapes(dependency_shapes.iter().copied(), &all_shapes);
906 let (type_info, type_info_index) = crate::TypeInfoTable::collect_indexed(
907 ta.package_name.clone(),
908 &all_shapes,
909 &user_emitted_types,
910 );
911 let needs_host_object_to_json =
912 user_subtypes::needs_host_object_to_json_adapter(&user_emitted_types);
913
914 let closure_metas = analysis.closure_metas;
917 let adapter_metas = analysis.adapter_metas;
918 let mut mentioned_closure_sigs = analysis.mentioned_closure_sigs;
919 if !recursive_validators.descriptor_types.is_empty() {
922 mentioned_closure_sigs.push(field_guards::signature());
923 }
924 let dependency_values = dependency_usage.dependency_values(dependencies);
925 let hof_dependency_sigs = closures::collect_from_dependencies(
926 dependency_values.iter().map(|value| value.symbol),
927 dependency_shapes.iter().copied(),
928 dependency_types.iter(),
929 &dependency_usage,
930 )?;
931 let class_member_sigs = closures::class_member_sigs(ta, dependencies)?;
932 closures::emit_func_and_struct_types(
935 closure_metas
936 .iter()
937 .map(|m| closures::classify(&m.signature))
938 .chain(
939 adapter_metas
940 .iter()
941 .map(|a| closures::classify(&a.signature)),
942 )
943 .collect::<Result<Vec<_>, _>>()?
944 .into_iter()
945 .chain(hof_dependency_sigs.iter().copied())
946 .chain(class_member_sigs.iter().copied())
947 .chain(mentioned_closure_sigs.iter().copied()),
948 &mut types,
949 &mut symbols,
950 &mut next_type_idx,
951 )?;
952
953 let boxed_value_types = box_types::collect(ta, &symbols)?;
955 box_types::emit(
956 &boxed_value_types,
957 &mut types,
958 &mut symbols,
959 &mut next_type_idx,
960 );
961
962 closures::emit_env_types(&closure_metas, &mut types, &mut symbols, &mut next_type_idx)?;
964
965 let mut next_global_idx: u32 = 0;
967
968 symbols.record_rewritable_members(classes::RewritableMembers::of(ta)?);
972 let imported_class_layouts = imported_classes::reconstruct(
973 dependencies,
974 ta,
975 &dependency_usage,
976 intrinsics,
977 file,
978 &mut types,
979 &mut next_type_idx,
980 &mut import_section,
981 &mut next_func_idx,
982 &mut next_global_idx,
983 &mut symbols,
984 )?;
985
986 let mut class_plan = classes::ClassPlan::collect(ta, &imported_class_layouts)?;
989 class_plan.reserve_and_emit_types(&mut types, &mut next_type_idx, &mut symbols, intrinsics)?;
990 for value in &dependency_values {
991 let defs = value.package;
992 if defs.mcp_server.is_some() {
997 continue;
998 }
999 import_value_symbol(
1000 value.export_name.as_ref(),
1001 value.symbol,
1002 defs,
1003 &intrinsics,
1004 &mut next_type_idx,
1005 &mut next_func_idx,
1006 &mut next_global_idx,
1007 &mut types,
1008 &mut import_section,
1009 &mut symbols,
1010 )?;
1011 }
1012 function_adapters::import_shared_closure_caches(
1013 &adapter_metas,
1014 dependencies,
1015 &mut import_section,
1016 &mut symbols,
1017 &mut next_global_idx,
1018 )?;
1019 let exported_closure_caches = function_adapters::exported_closure_caches(ta);
1020
1021 let vtable_ref = ValType::Ref(RefType {
1023 nullable: false,
1024 heap_type: HeapType::Concrete(intrinsics.vtable),
1025 });
1026 for (module, name) in [
1030 (crate::runtime::prelude::MODULE_NAME, "string_vtable"),
1031 (crate::runtime::prelude::MODULE_NAME, "boxed_number_vtable"),
1032 (crate::runtime::prelude::MODULE_NAME, "boxed_boolean_vtable"),
1033 (crate::runtime::prelude::MODULE_NAME, "array_vtable"),
1034 (crate::runtime::prelude::MODULE_NAME, "closure_vtable"),
1035 (crate::runtime::prelude::MODULE_NAME, "object_vtable"),
1036 ] {
1037 let mangled = crate::mangle::prelude(name);
1038 import_section.import(
1039 module,
1040 mangled.as_str(),
1041 EntityType::Global(GlobalType {
1042 val_type: vtable_ref,
1043 mutable: false,
1044 shared: false,
1045 }),
1046 );
1047 symbols.record_global(crate::mangle::prelude(name), next_global_idx);
1048 crate::codegen::next_index(&mut next_global_idx)?;
1049 }
1050 let undefined_value = crate::mangle::prelude(crate::runtime::prelude::undefined::GLOBAL_NAME);
1052 import_section.import(
1053 crate::runtime::prelude::MODULE_NAME,
1054 undefined_value.as_str(),
1055 EntityType::Global(GlobalType {
1056 val_type: ValType::Ref(RefType {
1057 nullable: false,
1058 heap_type: HeapType::Concrete(intrinsics.undefined),
1059 }),
1060 mutable: false,
1061 shared: false,
1062 }),
1063 );
1064 symbols.record_global(undefined_value, next_global_idx);
1065 crate::codegen::next_index(&mut next_global_idx)?;
1066 if dependency_usage.uses_bigint() {
1067 let mangled = crate::mangle::prelude("bigint_vtable");
1068 import_section.import(
1069 crate::runtime::prelude::MODULE_NAME,
1070 mangled.as_str(),
1071 EntityType::Global(GlobalType {
1072 val_type: vtable_ref,
1073 mutable: false,
1074 shared: false,
1075 }),
1076 );
1077 symbols.record_global(crate::mangle::prelude("bigint_vtable"), next_global_idx);
1078 crate::codegen::next_index(&mut next_global_idx)?;
1079 }
1080
1081 let raw_bigint_val_type = ValType::Ref(RefType {
1084 nullable: false,
1085 heap_type: HeapType::Concrete(intrinsics.raw_bigint),
1086 });
1087 let mut bigint_binop_sig_idx = None;
1088 let mut import_bigint_host = |name: &str, sig_idx: u32| -> Result<(), CompilerFailure> {
1089 let mangled = crate::mangle::host(crate::runtime::BIGINT_MODULE_NAME, name);
1090 import_section.import(
1091 crate::runtime::BIGINT_MODULE_NAME,
1092 mangled.as_str(),
1093 EntityType::Function(sig_idx),
1094 );
1095 symbols.record_func(mangled, next_func_idx);
1096 crate::codegen::next_index(&mut next_func_idx)?;
1097 Ok(())
1098 };
1099 for name in ["add", "sub", "mul", "div", "mod", "pow"] {
1100 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, name) {
1101 let sig_idx = if let Some(sig_idx) = bigint_binop_sig_idx {
1102 sig_idx
1103 } else {
1104 let sig_idx = next_type_idx;
1105 crate::codegen::next_index(&mut next_type_idx)?;
1106 types.ty().function(
1107 [
1108 ValType::I32,
1109 raw_bigint_val_type,
1110 ValType::I32,
1111 raw_bigint_val_type,
1112 ],
1113 [ValType::I32, raw_bigint_val_type],
1114 );
1115 bigint_binop_sig_idx = Some(sig_idx);
1116 sig_idx
1117 };
1118 import_bigint_host(name, sig_idx)?;
1119 }
1120 }
1121 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "cmp") {
1122 let sig_idx = {
1123 let sig_idx = next_type_idx;
1124 crate::codegen::next_index(&mut next_type_idx)?;
1125 types.ty().function(
1126 [
1127 ValType::I32,
1128 raw_bigint_val_type,
1129 ValType::I32,
1130 raw_bigint_val_type,
1131 ],
1132 [ValType::I32],
1133 );
1134 sig_idx
1135 };
1136 import_bigint_host("cmp", sig_idx)?;
1137 }
1138 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "neg") {
1139 let sig_idx = {
1140 let sig_idx = next_type_idx;
1141 crate::codegen::next_index(&mut next_type_idx)?;
1142 types.ty().function(
1143 [ValType::I32, raw_bigint_val_type],
1144 [ValType::I32, raw_bigint_val_type],
1145 );
1146 sig_idx
1147 };
1148 import_bigint_host("neg", sig_idx)?;
1149 }
1150 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "fromNumber") {
1151 let sig_idx = {
1152 let sig_idx = next_type_idx;
1153 crate::codegen::next_index(&mut next_type_idx)?;
1154 types
1155 .ty()
1156 .function([ValType::F64], [ValType::I32, raw_bigint_val_type]);
1157 sig_idx
1158 };
1159 import_bigint_host("fromNumber", sig_idx)?;
1160 }
1161 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "fromString") {
1162 let raw_string_val_type = ValType::Ref(RefType {
1163 nullable: false,
1164 heap_type: HeapType::Concrete(intrinsics.raw_string),
1165 });
1166 let sig_idx = {
1167 let sig_idx = next_type_idx;
1168 crate::codegen::next_index(&mut next_type_idx)?;
1169 types
1170 .ty()
1171 .function([raw_string_val_type], [ValType::I32, raw_bigint_val_type]);
1172 sig_idx
1173 };
1174 import_bigint_host("fromString", sig_idx)?;
1175 }
1176 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "toString") {
1177 let raw_string_val_type = ValType::Ref(RefType {
1178 nullable: false,
1179 heap_type: HeapType::Concrete(intrinsics.raw_string),
1180 });
1181 let sig_idx = {
1182 let sig_idx = next_type_idx;
1183 crate::codegen::next_index(&mut next_type_idx)?;
1184 types
1185 .ty()
1186 .function([ValType::I32, raw_bigint_val_type], [raw_string_val_type]);
1187 sig_idx
1188 };
1189 import_bigint_host("toString", sig_idx)?;
1190 }
1191 if dependency_usage.is_host_value_used(crate::runtime::NUMBER_MODULE_NAME, "fromBigInt") {
1192 let sig_idx = {
1193 let sig_idx = next_type_idx;
1194 crate::codegen::next_index(&mut next_type_idx)?;
1195 types
1196 .ty()
1197 .function([ValType::I32, raw_bigint_val_type], [ValType::F64]);
1198 sig_idx
1199 };
1200 let from_bigint = crate::mangle::host(crate::runtime::NUMBER_MODULE_NAME, "fromBigInt");
1201 import_section.import(
1202 crate::runtime::NUMBER_MODULE_NAME,
1203 from_bigint.as_str(),
1204 EntityType::Function(sig_idx),
1205 );
1206 symbols.record_func(from_bigint, next_func_idx);
1207 crate::codegen::next_index(&mut next_func_idx)?;
1208 }
1209
1210 let intrinsic_string_ref = ValType::Ref(RefType {
1211 nullable: false,
1212 heap_type: HeapType::Concrete(intrinsics.string),
1213 });
1214 let intrinsic_object_ref = ValType::Ref(RefType {
1215 nullable: false,
1216 heap_type: HeapType::Concrete(intrinsics.object),
1217 });
1218 for dependency_type in &dependency_types {
1219 let defs = dependency_type.package;
1220 let iface_name = dependency_type.name;
1221 let ty_sym = dependency_type.symbol;
1222 let TypeKind::Interface {
1223 dispatch,
1224 methods,
1225 properties,
1226 ..
1227 } = &ty_sym.kind
1228 else {
1229 continue;
1230 };
1231 if *dispatch == crate::Dispatch::VTable {
1232 continue;
1233 }
1234 symbols.record_iface_dispatch(ty_sym.mangled_name.clone(), *dispatch);
1236 let intrinsic_bigint_ref = ValType::Ref(RefType {
1237 nullable: false,
1238 heap_type: HeapType::Concrete(intrinsics.bigint),
1239 });
1240 let receiver_wasm: Option<ValType> = match dispatch {
1241 crate::Dispatch::Static => None,
1242 crate::Dispatch::Direct => Some(match iface_name {
1243 "Number" => ValType::F64,
1244 "Boolean" => ValType::I32,
1245 "String" => intrinsic_string_ref,
1246 "BigInt" => intrinsic_bigint_ref,
1247 _ => intrinsic_object_ref,
1248 }),
1249 crate::Dispatch::VTable => {
1250 return Err(crate::codegen::internal_failure(
1251 "invalid compiler emission dispatch",
1252 ));
1253 }
1254 };
1255 for (method_name, sig) in methods {
1256 if !dependency_usage.is_interface_member_used(ty_sym, method_name) {
1257 continue;
1258 }
1259 if defs.package_name.as_str() == crate::stdlib::http::MODULE_NAME
1265 && iface_name == "Response"
1266 && method_name == "json"
1267 {
1268 continue;
1269 }
1270 let mangled = crate::mangle::extend(&ty_sym.mangled_name, method_name);
1271 let abi = declared_wrapper_abi(&symbols, &mangled, sig, receiver_wasm.is_some())?;
1275 symbols.record_iface_method_abi(mangled.clone(), abi.clone());
1276 let host_defaults = is_host_package(&defs.package_name).then(|| {
1284 std::iter::repeat_n(None, usize::from(receiver_wasm.is_some()))
1285 .chain(sig.params.iter().map(|param| param.default.clone()))
1286 .collect::<Vec<_>>()
1287 });
1288 if let Some(func_idx) = symbols.func_idx(&mangled) {
1289 if let Some(defaults) = host_defaults {
1290 symbols.record_host_parameter_defaults(func_idx, defaults);
1291 }
1292 continue;
1293 }
1294 let mut params: Vec<ValType> = Vec::new();
1295 if let Some(recv) = receiver_wasm {
1296 params.push(recv);
1297 }
1298 params.extend(abi.params.iter().copied());
1299 if let Some(defaults) = host_defaults {
1300 symbols.record_host_parameter_defaults(next_func_idx, defaults);
1301 }
1302 let results: Vec<ValType> = abi.ret.into_iter().collect();
1303 let sig_idx = next_type_idx;
1304 crate::codegen::next_index(&mut next_type_idx)?;
1305 types.ty().function(params, results);
1306 import_section.import(
1307 defs.package_name.as_str(),
1308 mangled.as_str(),
1309 EntityType::Function(sig_idx),
1310 );
1311 symbols.record_func(mangled, next_func_idx);
1312 crate::codegen::next_index(&mut next_func_idx)?;
1313 }
1314 for (prop_name, sig) in properties {
1315 if sig.intrinsic {
1319 symbols.record_intrinsic_member(crate::mangle::extend(
1320 &ty_sym.mangled_name,
1321 prop_name,
1322 ));
1323 continue;
1324 }
1325 if !dependency_usage.is_interface_member_used(ty_sym, prop_name) {
1326 continue;
1327 }
1328 let mangled = crate::mangle::extend(&ty_sym.mangled_name, prop_name);
1329 if symbols.func_idx(&mangled).is_some() || symbols.global_idx(&mangled).is_some() {
1333 continue;
1334 }
1335 let Some(recv) = receiver_wasm else {
1336 import_section.import(
1341 defs.package_name.as_str(),
1342 mangled.as_str(),
1343 EntityType::Global(wasm_encoder::GlobalType {
1344 val_type: symbols.value_type(&sig.ty)?,
1345 mutable: true,
1346 shared: false,
1347 }),
1348 );
1349 symbols.record_typed_global(mangled, next_global_idx, sig.ty.clone());
1350 crate::codegen::next_index(&mut next_global_idx)?;
1351 continue;
1352 };
1353 let params = vec![recv];
1354 let results = symbols.wasm_result(&sig.ty)?;
1355 let sig_idx = next_type_idx;
1356 crate::codegen::next_index(&mut next_type_idx)?;
1357 types.ty().function(params, results);
1358 import_section.import(
1359 defs.package_name.as_str(),
1360 mangled.as_str(),
1361 EntityType::Function(sig_idx),
1362 );
1363 symbols.record_func(mangled, next_func_idx);
1364 crate::codegen::next_index(&mut next_func_idx)?;
1365 }
1366 }
1367 if needs_host_object_to_json {
1368 import_json_host_function(
1369 "stringifyTypedObject",
1370 &intrinsics,
1371 &mut next_type_idx,
1372 &mut next_func_idx,
1373 &mut types,
1374 &mut import_section,
1375 &mut symbols,
1376 )?;
1377 }
1378 import_json_host_function(
1379 "parse",
1380 &intrinsics,
1381 &mut next_type_idx,
1382 &mut next_func_idx,
1383 &mut types,
1384 &mut import_section,
1385 &mut symbols,
1386 )?;
1387
1388 let start_type_idx = next_type_idx;
1390 crate::codegen::next_index(&mut next_type_idx)?;
1391 types
1392 .ty()
1393 .function(Vec::<ValType>::new(), Vec::<ValType>::new());
1394 let start_func_idx = next_func_idx;
1395 crate::codegen::next_index(&mut next_func_idx)?;
1396
1397 let mut user_funcs: Vec<UserFunc> = Vec::new();
1399 let mut user_func_type_idx: BTreeMap<MangledName, u32> = BTreeMap::new();
1400 for f in &ta.functions {
1401 let sig_idx = next_type_idx;
1402 crate::codegen::next_index(&mut next_type_idx)?;
1403 let mut params: Vec<_> = f
1404 .params
1405 .iter()
1406 .map(|p| symbols.value_type(&p.ty))
1407 .collect::<Result<_, _>>()?;
1408 if !f.generics.is_empty() {
1409 symbols
1410 .runtime_generic_functions
1411 .insert(f.mangled_name.clone());
1412 params.push(runtime_descriptors::environment_type(&symbols)?);
1413 }
1414 types
1415 .ty()
1416 .function(params, symbols.wasm_result(&f.return_type)?);
1417 let func_idx = next_func_idx;
1418 crate::codegen::next_index(&mut next_func_idx)?;
1419 let param_types: Vec<Type> = f.params.iter().map(|p| p.ty.clone()).collect();
1420 if let Some(metadata) = call_arguments::typed_metadata(&f.params) {
1421 symbols
1422 .function_argument_metadata
1423 .insert(f.mangled_name.clone(), metadata);
1424 }
1425 symbols.record_local_fn(
1426 f.mangled_name.clone(),
1427 func_idx,
1428 param_types,
1429 f.return_type.clone(),
1430 )?;
1431 user_func_type_idx.insert(f.mangled_name.clone(), sig_idx);
1432 user_funcs.push(UserFunc {
1433 name: f.name.name.clone(),
1434 decl_span: f.name.span,
1435 type_idx: sig_idx,
1436 func_idx,
1437 body: f.body,
1438 return_type: f.return_type.clone(),
1439 params: f.params.clone(),
1440 generics: f.generics.clone(),
1441 });
1442 }
1443
1444 let needs_closure_coercions = !mentioned_closure_sigs.is_empty();
1446 let closure_methods = if closure_metas.is_empty()
1447 && adapter_metas.is_empty()
1448 && class_member_sigs.is_empty()
1449 && !needs_closure_coercions
1450 {
1451 None
1452 } else {
1453 Some(closures::allocate_methods(&mut next_func_idx)?)
1454 };
1455
1456 for meta in &closure_metas {
1457 let func_idx = next_func_idx;
1458 crate::codegen::next_index(&mut next_func_idx)?;
1459 symbols.record_closure_func_idx(meta.expr_id, func_idx);
1460 }
1461
1462 for meta in &adapter_metas {
1463 let func_idx = next_func_idx;
1464 crate::codegen::next_index(&mut next_func_idx)?;
1465 symbols.record_adapter_func_idx(meta.mangled.clone(), func_idx);
1466 }
1467
1468 let closure_coercion_targets = if closure_methods.is_some() {
1469 closure_coercions::allocate(&mut symbols, &mut next_func_idx)?
1470 } else {
1471 Vec::new()
1472 };
1473
1474 let mut user_subtypes_alloc: Vec<user_subtypes::UserSubtype> = Vec::new();
1476 for ty in &user_emitted_types {
1477 if let Type::Object { .. } = ty {
1478 user_subtypes_alloc.push(user_subtypes::allocate_methods(ty, &mut next_func_idx)?);
1479 }
1480 }
1481
1482 class_plan.allocate_funcs(&mut next_func_idx, &mut symbols)?;
1484 let instance_field_guards = field_guards::allocate(ta, &mut symbols, &mut next_func_idx)?;
1485 let type_descriptors = runtime_descriptors::allocate(
1486 ta,
1487 &recursive_validators.descriptor_types,
1488 &mut symbols,
1489 &mut next_func_idx,
1490 )?;
1491
1492 let field_lookup_signature = field_lookup::allocate(
1493 &mut types,
1494 &mut symbols,
1495 &mut next_type_idx,
1496 &mut next_func_idx,
1497 )?;
1498
1499 let object_ref_null = ValType::Ref(RefType {
1502 nullable: true,
1503 heap_type: HeapType::Concrete(intrinsics.object),
1504 });
1505 let raw_array_ref = ValType::Ref(RefType {
1506 nullable: false,
1507 heap_type: HeapType::Concrete(intrinsics.raw_array),
1508 });
1509 let raw_index_array_ref = ValType::Ref(RefType {
1510 nullable: false,
1511 heap_type: HeapType::Concrete(intrinsics.raw_index_array),
1512 });
1513 let mut runtime_validator_sigs: Vec<u32> = Vec::with_capacity(recursive_validators.plans.len());
1514 for plan in &recursive_validators.plans {
1515 let sig_idx = next_type_idx;
1516 crate::codegen::next_index(&mut next_type_idx)?;
1517 types.ty().function(
1518 [
1519 object_ref_null,
1520 raw_array_ref,
1521 raw_index_array_ref,
1522 ValType::I32,
1523 runtime_descriptors::environment_type(&symbols)?,
1524 ],
1525 [ValType::I32],
1526 );
1527 let func_idx = next_func_idx;
1528 crate::codegen::next_index(&mut next_func_idx)?;
1529 symbols.record_runtime_validator(plan.key.clone(), func_idx);
1530 runtime_validator_sigs.push(sig_idx);
1531 }
1532
1533 let main_func = ta.functions.iter().find(|f| f.name.name == "main");
1541 let main_return_ty = main_func.map(|f| f.return_type.clone());
1542 let main_output_shim = match main_return_ty.as_ref().map(Type::peel) {
1543 None | Some(Type::Never | Type::Error) => None,
1544 Some(_) => {
1545 let sig_idx = next_type_idx;
1546 types.ty().function(
1547 Vec::<ValType>::new(),
1548 [ValType::Ref(RefType {
1549 nullable: true,
1550 heap_type: HeapType::Concrete(intrinsics.string),
1551 })],
1552 );
1553 let func_idx = next_func_idx;
1555 Some((sig_idx, func_idx))
1556 }
1557 };
1558
1559 let pkg_string_global_idx = if needs_host_object_to_json {
1560 let idx = next_global_idx;
1561 crate::codegen::next_index(&mut next_global_idx)?;
1562 Some(idx)
1563 } else {
1564 None
1565 };
1566
1567 let main_mangled = main_func.map(|f| f.mangled_name.clone());
1568 let main_func_idx = main_mangled
1569 .as_ref()
1570 .map(|mangled| {
1571 symbols.func_idx(mangled).ok_or_else(|| {
1572 crate::codegen::internal_failure("`main` recorded during user-function pre-pass")
1573 })
1574 })
1575 .transpose()?;
1576
1577 module.section(&types);
1578 module.section(&import_section);
1579
1580 let mut functions = FunctionSection::new();
1581 functions.function(start_type_idx);
1582 for f in &user_funcs {
1583 functions.function(f.type_idx);
1584 }
1585 if closure_methods.is_some() {
1586 closures::emit_method_function_entries(&mut functions, intrinsics);
1587 }
1588 for meta in &closure_metas {
1589 let sig_idx = symbols
1590 .closure_func_type_idx(closures::classify(&meta.signature)?)
1591 .ok_or_else(|| {
1592 crate::codegen::internal_failure(
1593 "closures::emit_func_and_struct_types registered the funcref type",
1594 )
1595 })?;
1596 functions.function(sig_idx);
1597 }
1598 for meta in &adapter_metas {
1599 let sig_idx = symbols
1600 .closure_func_type_idx(closures::classify(&meta.signature)?)
1601 .ok_or_else(|| {
1602 crate::codegen::internal_failure(
1603 "closures::emit_func_and_struct_types registered the adapter signature",
1604 )
1605 })?;
1606 functions.function(sig_idx);
1607 }
1608 closure_coercions::emit_entries(&closure_coercion_targets, &mut functions, &symbols)?;
1609 user_subtypes::emit_method_function_entries(&mut functions, &user_subtypes_alloc, intrinsics);
1610 class_plan.emit_function_entries(&mut functions, &symbols, intrinsics)?;
1611 for _ in instance_field_guards
1612 .iter()
1613 .map(|_| ())
1614 .chain(type_descriptors.iter().map(|_| ()))
1615 {
1616 functions.function(
1617 symbols
1618 .closure_func_type_idx(field_guards::signature())
1619 .ok_or_else(|| crate::codegen::internal_failure("guard signature registered"))?,
1620 );
1621 }
1622 functions.function(field_lookup_signature);
1623 for &sig_idx in &runtime_validator_sigs {
1624 functions.function(sig_idx);
1625 }
1626 if let Some((sig_idx, _)) = main_output_shim {
1627 functions.function(sig_idx);
1628 }
1629 module.section(&functions);
1630
1631 let mut globals = GlobalSection::new();
1633 let mut globals_count: u32 = 0;
1634 if pkg_string_global_idx.is_some() {
1635 globals.global(
1636 GlobalType {
1637 val_type: ValType::Ref(RefType {
1638 nullable: true,
1639 heap_type: HeapType::Concrete(intrinsics.string),
1640 }),
1641 mutable: true,
1642 shared: false,
1643 },
1644 &ConstExpr::ref_null(HeapType::Concrete(intrinsics.string)),
1645 );
1646 globals_count = globals_count
1647 .checked_add(1)
1648 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1649 }
1650 for g in &ta.globals {
1651 let val_type = global_val_type(&g.ty, &symbols)?;
1652 globals.global(
1653 GlobalType {
1654 val_type,
1655 mutable: true,
1656 shared: false,
1657 },
1658 &default_const_expr(val_type)?,
1659 );
1660 symbols.record_typed_global(g.mangled_name.clone(), next_global_idx, g.ty.clone());
1661 crate::codegen::next_index(&mut next_global_idx)?;
1662 globals_count = globals_count
1663 .checked_add(1)
1664 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1665 }
1666 let init_flags_count =
1667 init_guard::allocate_flags(ta, &mut globals, &mut symbols, &mut next_global_idx)?;
1668 globals_count = globals_count
1669 .checked_add(init_flags_count)
1670 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1671 let vtable_globals_count = wasm_u32(user_subtypes_alloc.len())?;
1672 user_subtypes::emit_vtable_globals(
1673 &mut globals,
1674 &mut user_subtypes_alloc,
1675 &mut symbols,
1676 &mut next_global_idx,
1677 intrinsics,
1678 )?;
1679 let mut field_names_shapes = field_names::collect(&user_emitted_types);
1680 for class_fields in class_plan.field_name_lists() {
1683 if !field_names_shapes.contains(&class_fields) {
1684 field_names_shapes.push(class_fields);
1685 }
1686 }
1687 let string_vtable_global_idx =
1688 symbols.prelude_global_idx("string_vtable").ok_or_else(|| {
1689 crate::codegen::internal_failure(
1690 "string_vtable imported from prelude in the hard-coded import bootstrap",
1691 )
1692 })?;
1693 let field_names_globals_count = wasm_u32(field_names_shapes.len())?;
1694 field_names::emit(
1695 &field_names_shapes,
1696 &mut globals,
1697 &mut symbols,
1698 &mut next_global_idx,
1699 intrinsics,
1700 string_vtable_global_idx,
1701 )?;
1702 let mut extra_field_names = analysis.extra_field_names;
1707 for dependency_type in &dependency_types {
1708 let ty_sym = dependency_type.symbol;
1709 if let TypeKind::Interface {
1710 methods,
1711 properties,
1712 dispatch: crate::Dispatch::VTable,
1713 ..
1714 } = &ty_sym.kind
1715 {
1716 for method_name in methods.keys() {
1717 if dependency_usage.is_interface_member_used(ty_sym, method_name) {
1718 extra_field_names.push(method_name.clone());
1719 }
1720 }
1721 for property_name in properties.keys() {
1722 if dependency_usage.is_interface_member_used(ty_sym, property_name) {
1723 extra_field_names.push(property_name.clone());
1724 }
1725 }
1726 } else if let TypeKind::Class { accessors, .. } = &ty_sym.kind {
1727 for acc in accessors {
1731 extra_field_names.push(acc.name().to_string());
1732 match acc {
1733 crate::AccessorSig::Getter { name, .. } => {
1734 extra_field_names.push(crate::codegen::classes::accessor_getter_name(name));
1735 }
1736 crate::AccessorSig::Setter { name, .. } => {
1737 extra_field_names.push(crate::codegen::classes::accessor_setter_name(name));
1738 }
1739 }
1740 }
1741 }
1742 }
1743 for class_fields in class_plan.field_name_lists() {
1746 extra_field_names.extend(class_fields.into_iter().map(|field| field.name));
1747 }
1748 extra_field_names.extend(class_plan.accessor_property_names());
1751 let field_name_strings = field_name_strings::collect(&user_emitted_types, &extra_field_names);
1752 let field_name_strings_count = wasm_u32(field_name_strings.len())?;
1753 field_name_strings::emit(
1754 &field_name_strings,
1755 &mut globals,
1756 &mut symbols,
1757 &mut next_global_idx,
1758 intrinsics,
1759 string_vtable_global_idx,
1760 )?;
1761 class_plan.emit_globals(&mut globals, &mut symbols, &mut next_global_idx, intrinsics)?;
1763 let mut closure_methods_for_emit = closure_methods;
1764 let closure_vtable_count = if let Some(methods) = closure_methods_for_emit.as_mut() {
1765 closures::emit_vtable_global(
1766 &mut globals,
1767 methods,
1768 &mut symbols,
1769 &mut next_global_idx,
1770 intrinsics,
1771 )?;
1772 1
1773 } else {
1774 0
1775 };
1776 let adapter_closure_globals_count = function_adapters::allocate_closure_globals(
1777 &adapter_metas,
1778 &exported_closure_caches,
1779 &mut globals,
1780 &mut symbols,
1781 &mut next_global_idx,
1782 )?;
1783 let descriptor_globals_count =
1784 runtime_descriptors::allocate_globals(&mut globals, &mut symbols, &mut next_global_idx)?;
1785 if [
1786 adapter_closure_globals_count,
1787 descriptor_globals_count,
1788 globals_count,
1789 vtable_globals_count,
1790 field_names_globals_count,
1791 field_name_strings_count,
1792 closure_vtable_count,
1793 ]
1794 .iter()
1795 .any(|&count| count > 0)
1796 {
1797 module.section(&globals);
1798 }
1799
1800 let mut exports = ExportSection::new();
1801 if let Some(main_func_idx) = main_func_idx {
1802 exports.export("main", WasmExportKind::Func, main_func_idx);
1803 }
1804 if let Some((_, func_idx)) = main_output_shim {
1805 exports.export("__main_output", WasmExportKind::Func, func_idx);
1806 }
1807 for entry in &ta.exports {
1808 match entry.kind {
1809 crate::ExportKind::Function => {
1810 let func_idx = symbols
1811 .top_level_fn(&entry.target)
1812 .map(|f| f.wasm_idx)
1813 .ok_or_else(|| {
1814 crate::codegen::internal_failure(
1815 "checked package export function target exists",
1816 )
1817 })?;
1818 exports.export(entry.public_name.as_str(), WasmExportKind::Func, func_idx);
1819 }
1820 crate::ExportKind::Global => {
1821 let global_idx = symbols.global_idx(&entry.target).ok_or_else(|| {
1822 crate::codegen::internal_failure("checked package export global target exists")
1823 })?;
1824 exports.export(
1825 entry.public_name.as_str(),
1826 WasmExportKind::Global,
1827 global_idx,
1828 );
1829 }
1830 crate::ExportKind::Type => {}
1831 }
1832 }
1833 let exported_class_mangles: BTreeSet<&MangledName> = ta
1840 .types
1841 .iter()
1842 .filter_map(|decl| match decl {
1843 crate::TypedTypeDecl::Class(class) => Some(&class.mangled_name),
1844 _ => None,
1845 })
1846 .collect();
1847 for (name, func_idx) in
1848 class_plan.exported_funcs(&symbols, |m| exported_class_mangles.contains(m))?
1849 {
1850 exports.export(name.as_str(), WasmExportKind::Func, func_idx);
1851 }
1852 for (name, global_idx) in
1853 class_plan.exported_vtable_globals(&symbols, |m| exported_class_mangles.contains(m))?
1854 {
1855 exports.export(name.as_str(), WasmExportKind::Global, global_idx);
1856 }
1857 for (public_name, function) in &exported_closure_caches {
1858 let global_idx = symbols
1859 .adapter_closure_global_idx(function)
1860 .ok_or_else(|| crate::codegen::internal_failure("exported closure cache allocated"))?;
1861 exports.export(
1862 crate::mangle::closure_cache(public_name).as_str(),
1863 WasmExportKind::Global,
1864 global_idx,
1865 );
1866 }
1867 module.section(&exports);
1868
1869 module.section(&StartSection {
1870 function_index: start_func_idx,
1871 });
1872
1873 let mut declared = user_subtypes::declared_method_funcs(&user_subtypes_alloc);
1875 if let Some(methods) = closure_methods_for_emit {
1876 declared.extend(closures::declared_funcs(methods));
1877 }
1878 for meta in &closure_metas {
1879 let idx = symbols
1880 .closure_func_idx(meta.expr_id)
1881 .ok_or_else(|| crate::codegen::internal_failure("closure func index allocated"))?;
1882 declared.push(idx);
1883 }
1884 for meta in &adapter_metas {
1885 let idx = symbols
1886 .adapter_func_idx(&meta.mangled)
1887 .ok_or_else(|| crate::codegen::internal_failure("adapter func index allocated"))?;
1888 declared.push(idx);
1889 }
1890 declared.extend(
1891 closure_coercion_targets
1892 .iter()
1893 .map(|&sig| {
1894 symbols
1895 .closure_coercion(sig)
1896 .ok_or_else(|| crate::codegen::internal_failure("coercion allocated"))
1897 })
1898 .collect::<Result<Vec<_>, _>>()?,
1899 );
1900 declared.extend(class_plan.declared_funcs(&symbols));
1901 declared.extend(instance_field_guards.iter().map(|guard| guard.function));
1902 declared.extend(type_descriptors.iter().map(|(_, function)| *function));
1903 if !declared.is_empty() {
1904 let mut elements = wasm_encoder::ElementSection::new();
1905 elements.declared(wasm_encoder::Elements::Functions(Cow::Owned(declared)));
1906 module.section(&elements);
1907 }
1908
1909 let total_data_segments = pool
1911 .strings
1912 .len()
1913 .checked_add(bigint_pool.literals.len())
1914 .ok_or_else(|| internal_failure("data segment count overflow"))?;
1915 if total_data_segments > 0 {
1916 module.section(&DataCountSection {
1917 count: wasm_u32(total_data_segments)?,
1918 });
1919 }
1920
1921 let ctx = CodegenCtx {
1922 ta,
1923 strings: &pool,
1924 bigints: &bigint_pool,
1925 symbols: &symbols,
1926 source,
1927 line_index: &line_index,
1928 file,
1929 validator_bodies: &validator_bodies,
1930 type_info: &type_info,
1931 package_string_global_idx: pkg_string_global_idx,
1932 failure: std::cell::Cell::new(None),
1933 validator_steps_left: std::cell::Cell::new(
1934 crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS,
1935 ),
1936 validator_root: std::cell::Cell::new(None),
1937 check_is_standalone: std::cell::Cell::new(false),
1938 };
1939
1940 let mut code = CodeSection::new();
1941 let mut start_emitter = FunctionEmitter::new(&ctx, &[])?;
1942 if let Some(pkg_string_global_idx) = pkg_string_global_idx {
1943 emit_package_string_init(
1944 &mut start_emitter,
1945 ta.package_name.as_str(),
1946 pkg_string_global_idx,
1947 intrinsics.raw_string,
1948 intrinsics.string,
1949 string_vtable_global_idx,
1950 )?;
1951 }
1952 for (index, &stmt_id) in ctx.ta.top_level_statements.iter().enumerate() {
1953 init_guard::emit_mark_classes_declared(&mut start_emitter, &ctx, index);
1954 emit_statement(&mut start_emitter, &ctx, stmt_id)?;
1955 ctx.check_failure()?;
1956 }
1957 init_guard::emit_mark_classes_declared(
1958 &mut start_emitter,
1959 &ctx,
1960 ctx.ta.top_level_statements.len(),
1961 );
1962 let mut debug_functions = dwarf::DebugFunctions::default();
1963 debug_functions.write(
1964 &mut code,
1965 start_emitter.build_with_lines()?,
1966 TOP_LEVEL_FRAME_NAME.to_string(),
1967 crate::Span::at(ctx.file),
1968 )?;
1969
1970 for func in &user_funcs {
1971 let built = function_emitter::emit_function(
1972 &func.generics,
1973 &ctx,
1974 &func.params,
1975 func.body,
1976 &func.return_type,
1977 )?;
1978 ctx.check_failure()?;
1979 debug_functions.write(&mut code, built, func.name.clone(), func.decl_span)?;
1980 }
1981
1982 if let Some(methods) = closure_methods_for_emit {
1983 closures::emit_method_bodies(&mut code, methods, &symbols)?;
1984 }
1985
1986 for meta in &closure_metas {
1987 let built = function_emitter::emit_closure_function(&ctx, meta)?;
1988 ctx.check_failure()?;
1989 let (name, decl_span) = closure_frame(ta, meta)?;
1990 debug_functions.write(&mut code, built, name, decl_span)?;
1991 }
1992
1993 function_adapters::emit_bodies(&adapter_metas, &mut code, &ctx)?;
1994 closure_coercions::emit_bodies(&closure_coercion_targets, &mut code, &ctx)?;
1995
1996 user_subtypes::emit_method_bodies(
1997 &mut code,
1998 &user_subtypes_alloc,
1999 &symbols,
2000 &type_info,
2001 &type_info_index,
2002 pkg_string_global_idx,
2003 )?;
2004
2005 class_plan.emit_bodies(&mut code, &mut debug_functions, &ctx)?;
2006 ctx.check_failure()?;
2009 for guard in &instance_field_guards {
2010 code.function(&field_guards::body(&ctx, guard)?);
2011 }
2012 for (ty, _) in &type_descriptors {
2013 code.function(&runtime_descriptors::body(&ctx, ty)?);
2014 }
2015
2016 code.function(&field_lookup::body(&ctx)?);
2017 for (validator_id, plan) in recursive_validators.plans.iter().enumerate() {
2018 let validator = ctx.checking_standalone(&plan.key, || {
2019 cast_check::emit_runtime_validator_body(
2020 &ctx,
2021 &plan.key,
2022 &plan.body,
2023 plan.rejects_polymorphic_edge,
2024 i32::try_from(validator_id)
2025 .map_err(|_| internal_failure("validator id exceeds i32"))?,
2026 )
2027 })?;
2028 code.function(&validator);
2029 }
2030
2031 if let (Some(_), Some(main_func_idx), Some(main_return_ty)) =
2032 (main_output_shim, main_func_idx, main_return_ty.as_ref())
2033 {
2034 code.function(&function_emitter::json::emit_main_output_shim(
2035 &ctx,
2036 main_func_idx,
2037 main_return_ty,
2038 source_main_return_ty.as_ref().unwrap_or(main_return_ty),
2039 )?);
2040 }
2041
2042 ctx.check_failure()?;
2043 module.section(&code);
2044
2045 let vec_count_size = leb128_u32_size(code.len()) as u64;
2047 let code_content_size = vec_count_size
2048 .checked_add(u64::try_from(code.byte_len()).map_err(|_| index_space_exhausted())?)
2049 .ok_or_else(index_space_exhausted)?;
2050
2051 let funcs = debug_functions.into_debug_info(vec_count_size)?;
2052 for (sect_name, bytes) in
2053 dwarf::build_dwarf(&funcs, code_content_size, filename, &debug_sources)?
2054 {
2055 module.section(&CustomSection {
2056 name: Cow::Borrowed(sect_name),
2057 data: Cow::Owned(bytes),
2058 });
2059 }
2060
2061 {
2065 let mut names = wasm_encoder::NameSection::new();
2066 names.module(owning_package);
2067 module.section(&names);
2068 }
2069
2070 if total_data_segments > 0 {
2071 let mut data = DataSection::new();
2072 for i in 0..pool.strings.len() {
2073 data.passive(pool.utf16_le_bytes(i)?);
2074 }
2075 for i in 0..bigint_pool.literals.len() {
2077 data.passive(bigint_pool.le_bytes(i)?);
2078 }
2079 module.section(&data);
2080 }
2081
2082 Ok(GeneratedModule {
2083 wasm: module.finish(),
2084 type_info,
2085 runtime_functions: runtime_values::signatures(ta),
2086 runtime_globals: runtime_values::global_types(ta),
2087 closure_caches: exported_closure_caches.into_keys().collect(),
2088 })
2089}
2090
2091const TOP_LEVEL_FRAME_NAME: &str = "<top level>";
2095const ANONYMOUS_FRAME_NAME: &str = "<anonymous>";
2096
2097fn closure_frame(
2101 ta: &TypedAst,
2102 meta: &closures::ClosureMeta,
2103) -> Result<(String, crate::Span), CompilerFailure> {
2104 let name = meta
2105 .self_name
2106 .as_ref()
2107 .or_else(|| ta.nested_function_names.get(&meta.expr_id));
2108 if let Some(name) = name {
2109 return Ok((name.name.clone(), name.span));
2110 }
2111 let span = ta.try_expr(meta.expr_id).map_err(arena_failure)?.span;
2112 Ok((ANONYMOUS_FRAME_NAME.to_string(), span))
2113}
2114
2115struct UserFunc {
2116 name: String,
2117 decl_span: crate::Span,
2118 type_idx: u32,
2119 #[allow(dead_code)]
2120 func_idx: u32,
2121 body: StmtId,
2122 return_type: Type,
2123 params: Vec<crate::TypedParam>,
2124 generics: Vec<String>,
2125}
2126
2127fn leb128_u32_size(mut v: u32) -> usize {
2131 let mut size = 0;
2132 loop {
2133 size += 1;
2134 v >>= 7;
2135 if v == 0 {
2136 return size;
2137 }
2138 }
2139}
2140
2141fn json_host_signature(
2142 name: &str,
2143 params: &[Param],
2144 symbols: &SymbolTable,
2145 intrinsics: &intrinsics::IntrinsicTypeIndices,
2146) -> Result<
2147 (Vec<wasm_encoder::ValType>, Vec<wasm_encoder::ValType>),
2148 crate::compiler_error::CompilerFailure,
2149> {
2150 use wasm_encoder::{HeapType, RefType, ValType};
2151
2152 let raw_string_ref = ValType::Ref(RefType {
2153 nullable: false,
2154 heap_type: HeapType::Concrete(intrinsics.raw_string),
2155 });
2156 Ok(match name {
2157 "parse" => (
2159 vec![raw_string_ref],
2160 vec![ValType::Ref(RefType {
2161 nullable: true,
2162 heap_type: HeapType::Concrete(intrinsics.object),
2163 })],
2164 ),
2165 "stringify" => (vec![raw_string_ref], vec![raw_string_ref]),
2166 "stringifyTypedObject" => (
2167 vec![
2168 raw_string_ref,
2169 ValType::I32,
2170 ValType::Ref(RefType {
2171 nullable: false,
2172 heap_type: HeapType::Abstract {
2173 shared: false,
2174 ty: AbstractHeapType::Struct,
2175 },
2176 }),
2177 ],
2178 vec![raw_string_ref],
2179 ),
2180 "stringifyPrettyNumber" => (vec![raw_string_ref, ValType::F64], vec![raw_string_ref]),
2181 "stringifyPrettyString" => (vec![raw_string_ref, raw_string_ref], vec![raw_string_ref]),
2182 _ => (
2183 params
2184 .iter()
2185 .map(|p| symbols.host_value_type(&p.ty))
2186 .collect::<Result<_, _>>()?,
2187 vec![],
2188 ),
2189 })
2190}
2191
2192fn global_val_type(
2199 ty: &Type,
2200 im: &SymbolTable,
2201) -> Result<ValType, crate::compiler_error::CompilerFailure> {
2202 Ok(match im.value_type(ty)? {
2203 ValType::Ref(r) => ValType::Ref(RefType {
2204 nullable: true,
2205 ..r
2206 }),
2207 other => other,
2208 })
2209}
2210
2211fn default_const_expr(val_type: ValType) -> Result<ConstExpr, CompilerFailure> {
2213 Ok(match val_type {
2214 ValType::F64 => ConstExpr::f64_const(Ieee64::from(0.0_f64)),
2215 ValType::I32 => ConstExpr::i32_const(0),
2216 ValType::Ref(RefType { heap_type, .. }) => ConstExpr::ref_null(heap_type),
2217 other => {
2218 return Err(crate::codegen::internal_failure(format!(
2219 "unsupported default constant type {other:?}"
2220 )));
2221 }
2222 })
2223}
2224
2225fn emit_package_string_init(
2226 emitter: &mut FunctionEmitter<'_>,
2227 package_name: &str,
2228 pkg_string_global_idx: u32,
2229 raw_string_type_idx: u32,
2230 string_type_idx: u32,
2231 string_vtable_global_idx: u32,
2232) -> Result<(), CompilerFailure> {
2233 emitter.instruction(Instruction::GlobalGet(string_vtable_global_idx));
2234 for code_unit in package_name.encode_utf16() {
2235 emitter.instruction(Instruction::I32Const(code_unit as i32));
2236 }
2237 emitter.instruction(Instruction::ArrayNewFixed {
2238 array_type_index: raw_string_type_idx,
2239 array_size: wasm_u32(package_name.encode_utf16().count())?,
2240 });
2241 emitter.instruction(Instruction::I64Const(0));
2242 emitter.instruction(Instruction::StructNew(string_type_idx));
2243 emitter.instruction(Instruction::GlobalSet(pkg_string_global_idx));
2244 Ok(())
2245}
2246
2247fn arena_failure(error: crate::arena::ArenaError) -> CompilerFailure {
2248 error.into_compiler_failure(CompilerStage::Codegen)
2249}
2250
2251#[cfg(test)]
2252pub(crate) mod tests {
2253 use super::{SymbolTable, ValueSymbol, codegen, codegen_with_type_info};
2254 use crate::runtime::prelude;
2255 use crate::{
2256 Asi, ModulePath, NamespaceSymbol, ObjectField, PackageDeclaration, Sources, Token,
2257 TokenKind, Type, TypedAst, capture, check, desugar, infer, infer_package, lower_patterns,
2258 parse,
2259 };
2260 use wasmparser::{Parser, Payload};
2261
2262 #[test]
2263 fn missing_record_import_returns_an_internal_error_without_wasm() {
2264 let source = "function main(): number | undefined { const d: Record<string, number> = {}; const key: string = 'x'; return d[key]; }";
2265 let ta = type_check(source);
2266 let (prelude_defs, host_defs, internal_defs) =
2267 prelude::cached_runtime_package_declarations();
2268 let mut dependencies: Vec<_> = prelude_defs
2269 .iter()
2270 .chain(host_defs.iter())
2271 .chain(internal_defs.iter())
2272 .cloned()
2273 .collect();
2274 for declaration in &mut dependencies {
2275 declaration.values.retain(|_, value| {
2276 !value
2277 .mangled_name
2278 .as_str()
2279 .ends_with("ObjectConstructor##getField")
2280 });
2281 }
2282 let refs: Vec<_> = dependencies.iter().collect();
2283 let error = codegen_with_type_info(source, "record.ts", crate::FileId(0), &ta, &refs)
2284 .expect_err("missing internal import must not return a module");
2285 assert!(
2286 error.to_string().contains("dynamic read imported"),
2287 "{error}"
2288 );
2289 super::tests::compile("function main(): number { return 42; }");
2290 }
2291
2292 fn type_check(source: &str) -> TypedAst {
2293 type_check_with_packages(source, &[])
2294 }
2295
2296 fn type_check_with_packages(source: &str, packages: &[&PackageDeclaration]) -> TypedAst {
2297 let mut asi = Asi::new(source, crate::FileId(0));
2298 let mut tokens: Vec<Token> = Vec::new();
2299 loop {
2300 let tok = asi.next_token();
2301 let is_eof = matches!(tok.kind, TokenKind::Eof);
2302 tokens.push(tok);
2303 if is_eof {
2304 break;
2305 }
2306 }
2307 let lex_diags = asi.into_diagnostics();
2308 assert!(
2309 lex_diags.is_empty(),
2310 "unexpected lexer diags: {lex_diags:?}"
2311 );
2312 let (ast, parse_diags) = parse(source, tokens, crate::FileId(0));
2313 assert!(
2314 parse_diags.is_empty(),
2315 "unexpected parser diags: {parse_diags:?}"
2316 );
2317 let packages = runtime_packages(packages);
2318 let (mut ta, mut diags) = infer(source, "main", &ast, &packages);
2319 diags.extend(check(&ta).unwrap());
2320 ta = capture(ta).unwrap();
2321 ta = desugar(ta, crate::FileId(0)).unwrap();
2322 assert!(diags.is_empty(), "unexpected typecheck diags: {diags:?}");
2323 ta
2324 }
2325
2326 fn runtime_packages<'a>(packages: &[&'a PackageDeclaration]) -> Vec<&'a PackageDeclaration> {
2327 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
2328 let mut out = Vec::with_capacity(prelude_defs.len() + host_defs.len() + packages.len());
2329 out.extend(prelude_defs.iter());
2330 out.extend(host_defs.iter());
2331 out.extend(packages.iter().copied());
2332 out
2333 }
2334
2335 fn infer_with_runtime_packages(source: &str, ast: &crate::Ast) -> Vec<crate::Diagnostic> {
2336 let packages = runtime_packages(&[]);
2337 let (_, diags) = infer(source, "main", ast, &packages);
2338 diags
2339 }
2340
2341 fn parse_module(
2342 sources: &mut Sources,
2343 module: &str,
2344 source: &str,
2345 ) -> (ModulePath, crate::FileId, crate::Ast) {
2346 let file = sources.add(module.to_string(), source).unwrap();
2347 let mut asi = Asi::new(source, file);
2348 let mut tokens: Vec<Token> = Vec::new();
2349 loop {
2350 let tok = asi.next_token();
2351 let is_eof = matches!(tok.kind, TokenKind::Eof);
2352 tokens.push(tok);
2353 if is_eof {
2354 break;
2355 }
2356 }
2357 let lex_diags = asi.into_diagnostics();
2358 assert!(
2359 lex_diags.is_empty(),
2360 "unexpected lexer diags: {lex_diags:?}"
2361 );
2362 let (mut ast, parse_diags) = parse(source, tokens, file);
2363 assert!(
2364 parse_diags.is_empty(),
2365 "unexpected parser diags: {parse_diags:?}"
2366 );
2367 ast = lower_patterns(ast).unwrap();
2368 (ModulePath::from(module), file, ast)
2369 }
2370
2371 pub(crate) fn compile_package_modules(
2372 package_name: &str,
2373 modules: &[(&str, &str)],
2374 packages: &[&PackageDeclaration],
2375 ) -> (Vec<u8>, PackageDeclaration, crate::TypeInfoTable) {
2376 let mut sources = Sources::new();
2377 let owned_modules: Vec<_> = modules
2378 .iter()
2379 .map(|(module, source)| parse_module(&mut sources, module, source))
2380 .collect();
2381 let module_refs: Vec<_> = owned_modules
2382 .iter()
2383 .map(|(module, file, ast)| (module.clone(), *file, ast))
2384 .collect();
2385 let stdlib_defs = crate::runtime::stdlib_package_declarations();
2386 let mut external_packages: std::collections::BTreeMap<String, PackageDeclaration> =
2387 stdlib_defs
2388 .into_iter()
2389 .map(|defs| (defs.package_name.clone(), defs))
2390 .collect();
2391 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
2392 for defs in prelude_defs {
2393 external_packages.insert(defs.package_name.clone(), defs.clone());
2394 }
2395 for defs in host_defs {
2396 external_packages.insert(defs.package_name.clone(), defs.clone());
2397 }
2398 for defs in packages {
2399 external_packages.insert(defs.package_name.clone(), (*defs).clone());
2400 }
2401 let (mut ta, mut package, diags) = infer_package(
2402 package_name,
2403 ModulePath::from("lib"),
2404 module_refs,
2405 &sources,
2406 external_packages,
2407 std::collections::BTreeMap::new(),
2408 );
2409 assert!(diags.is_empty(), "unexpected package diags: {diags:?}");
2410 ta = capture(ta).unwrap();
2411 let root_file = owned_modules
2412 .iter()
2413 .find(|(module, _, _)| module.as_str() == "lib")
2414 .map(|(_, file, _)| *file)
2415 .expect("test package has lib module");
2416 ta = desugar(ta, root_file).unwrap();
2417
2418 let (prelude_defs, host_defs, internal_defs) =
2419 prelude::cached_runtime_package_declarations();
2420 let stdlib_defs = crate::runtime::stdlib_package_declarations();
2421 let mut dependencies: Vec<&PackageDeclaration> = prelude_defs.iter().collect();
2422 dependencies.extend(host_defs.iter());
2423 dependencies.extend(internal_defs.iter());
2424 dependencies.extend(stdlib_defs.iter());
2425 dependencies.extend_from_slice(packages);
2426 let generated =
2427 super::codegen_package_with_type_info(&sources, root_file, &ta, &dependencies)
2428 .expect("code generation");
2429 package.runtime_functions = generated.runtime_functions;
2430 package.runtime_globals = generated.runtime_globals;
2431 package.closure_caches = generated.closure_caches;
2432 (generated.wasm, package, generated.type_info)
2433 }
2434
2435 pub(crate) fn compile(source: &str) -> Vec<u8> {
2436 crate::compile::compile_script(source, "script.subm", crate::FileId(0), &[], &[])
2437 .expect("test-only compile expects no diagnostics")
2438 .wasm
2439 }
2440
2441 #[test]
2442 fn compiled_type_info_includes_stringify_object_shapes() {
2443 let source = r#"
2444function main(): string {
2445 const original = "{\"id\":1,\"name\":\"alice\"}";
2446 const u = JSON.parse(original) as { id: number, name: string };
2447 return JSON.stringify(u);
2448}
2449"#;
2450 let compiled =
2451 crate::compile::compile_script(source, "script.subm", crate::FileId(0), &[], &[])
2452 .expect("script should compile");
2453 let ty = Type::Object {
2454 index: None,
2455 fields: std::collections::BTreeMap::from([
2456 ("id".to_string(), ObjectField::required(Type::Number)),
2457 ("name".to_string(), ObjectField::required(Type::String)),
2458 ]),
2459 };
2460
2461 let id = compiled
2462 .type_info
2463 .object_type_id(&ty)
2464 .expect("compiled TypeInfo should contain the object stringify target");
2465 let info = compiled.type_info.get(id).expect("TypeInfo id is valid");
2466 assert!(matches!(
2467 &info.kind,
2468 crate::TypeInfoKind::Object { fields } if fields.len() == 2
2469 ));
2470
2471 let ta = type_check(source);
2472 let emitted = super::user_subtypes::collect_object_shapes(std::iter::empty(), &ta.shapes);
2473 assert!(
2474 emitted
2475 .iter()
2476 .all(|ty| compiled.type_info.object_type_id(ty).is_some()),
2477 "every emitted object subtype should have TypeInfo"
2478 );
2479 }
2480
2481 fn build_symbol_table(source: &str) -> SymbolTable {
2482 let _ta = type_check(source);
2483 let prelude_defs = prelude::prelude_package_declaration();
2484 let dependencies: [&crate::PackageDeclaration; 1] = [&prelude_defs];
2485 let mut map = SymbolTable::default();
2486 let mut next_type_idx: u32 = 0;
2487 let mut next_func_idx: u32 = 0;
2488 let mut next_global_idx: u32 = 0;
2489 map.set_intrinsic_type_indices(super::intrinsics::IntrinsicTypeIndices {
2490 raw_string: 0,
2491 vtable: 1,
2492 object: 2,
2493 undefined: 51,
2494 string: 3,
2495 boxed_number: 4,
2496 boxed_boolean: 5,
2497 field_names: 6,
2498 object_fields: 7,
2499 object_shape: 8,
2500 to_string_fn: 9,
2501 to_json_fn: 10,
2502 equals_fn: 11,
2503 hash_fn: 12,
2504 field_getter: 13,
2505 field_setter: 14,
2506 raw_array: 15,
2507 array: 16,
2508 raw_uint8_array: 17,
2509 uint8_array: 18,
2510 closure: 19,
2511 class_vtable: 20,
2512 error_vtable: 21,
2513 error: 22,
2514 raw_bigint: 23,
2515 bigint: 24,
2516 regex_capture_array: 25,
2517 regex_match: 26,
2518 regex: 27,
2519 regex_match_box: 28,
2520 temporal_instant: 29,
2521 temporal_duration: 30,
2522 temporal_zdt: 31,
2523 raw_index_array: 32,
2524 map: 33,
2525 set: 34,
2526 url: 35,
2527 fs_stat: 41,
2528 fs_peek: 42,
2529 fs_dir_entry: 43,
2530 fs_info: 44,
2531 fs_file_writer: 45,
2532 http_response: 46,
2533 http_download_result: 47,
2534 session_entry: 48,
2535 session_page: 49,
2536 fs_mount_info: 50,
2537
2538 temporal_plain_date: 36,
2539 temporal_plain_time: 37,
2540 temporal_plain_date_time: 38,
2541 temporal_plain_year_month: 39,
2542 temporal_plain_month_day: 40,
2543 });
2544 next_type_idx += super::intrinsics::INTRINSIC_TYPE_COUNT;
2545 let _ = next_type_idx;
2546 for defs in &dependencies {
2547 for value in defs.values.values() {
2548 record(&mut map, value, &mut next_func_idx, &mut next_global_idx);
2549 }
2550 walk_test_namespaces(
2551 &defs.namespaces,
2552 &mut map,
2553 &mut next_func_idx,
2554 &mut next_global_idx,
2555 );
2556 }
2557 map
2558 }
2559
2560 fn record(
2561 map: &mut SymbolTable,
2562 value: &ValueSymbol,
2563 next_func_idx: &mut u32,
2564 next_global_idx: &mut u32,
2565 ) {
2566 match &value.kind {
2567 crate::ValueKind::Function { .. } => {
2568 map.record_func(value.mangled_name.clone(), *next_func_idx);
2569 *next_func_idx += 1;
2570 }
2571 crate::ValueKind::Let { .. } | crate::ValueKind::Const { .. } => {
2572 map.record_global(value.mangled_name.clone(), *next_global_idx);
2573 *next_global_idx += 1;
2574 }
2575 }
2576 }
2577
2578 fn walk_test_namespaces(
2579 map_in: &std::collections::BTreeMap<String, NamespaceSymbol>,
2580 map: &mut SymbolTable,
2581 next_func_idx: &mut u32,
2582 next_global_idx: &mut u32,
2583 ) {
2584 for ns in map_in.values() {
2585 for value in ns.values.values() {
2586 record(map, value, next_func_idx, next_global_idx);
2587 }
2588 walk_test_namespaces(&ns.namespaces, map, next_func_idx, next_global_idx);
2589 }
2590 }
2591
2592 fn function_count(bytes: &[u8]) -> usize {
2593 for payload in Parser::new(0).parse_all(bytes) {
2594 if let Payload::FunctionSection(reader) = payload.expect("payload") {
2595 return reader.count() as usize;
2596 }
2597 }
2598 0
2599 }
2600
2601 fn imports(bytes: &[u8]) -> Vec<(String, String)> {
2602 let mut out = Vec::new();
2603 for payload in Parser::new(0).parse_all(bytes) {
2604 if let Payload::ImportSection(reader) = payload.expect("payload") {
2605 for entry in reader {
2606 match entry.expect("import") {
2607 wasmparser::Imports::Single(_, imp) => {
2608 out.push((imp.module.to_string(), imp.name.to_string()));
2609 }
2610 other => panic!("unexpected import form {other:?}"),
2611 }
2612 }
2613 }
2614 }
2615 out
2616 }
2617
2618 fn global_count(bytes: &[u8]) -> usize {
2619 for payload in Parser::new(0).parse_all(bytes) {
2620 if let Payload::GlobalSection(reader) = payload.expect("payload") {
2621 return reader.count() as usize;
2622 }
2623 }
2624 0
2625 }
2626
2627 fn global_decls(bytes: &[u8]) -> Vec<(bool, &'static str)> {
2628 let mut out = Vec::new();
2629 for payload in Parser::new(0).parse_all(bytes) {
2630 if let Payload::GlobalSection(reader) = payload.expect("payload") {
2631 for g in reader {
2632 let g = g.expect("global");
2633 let kind = match g.ty.content_type {
2634 wasmparser::ValType::F64 => "f64",
2635 wasmparser::ValType::I32 => "i32",
2636 wasmparser::ValType::Ref(_) => "ref",
2637 _ => "other",
2638 };
2639 out.push((g.ty.mutable, kind));
2640 }
2641 }
2642 }
2643 out
2644 }
2645
2646 fn start_function_idx(bytes: &[u8]) -> Option<u32> {
2647 for payload in Parser::new(0).parse_all(bytes) {
2648 if let Payload::StartSection { func, .. } = payload.expect("payload") {
2649 return Some(func);
2650 }
2651 }
2652 None
2653 }
2654
2655 fn data_count(bytes: &[u8]) -> Option<u32> {
2656 for payload in Parser::new(0).parse_all(bytes) {
2657 if let Payload::DataCountSection { count, .. } = payload.expect("payload") {
2658 return Some(count);
2659 }
2660 }
2661 None
2662 }
2663
2664 fn data_segments(bytes: &[u8]) -> Vec<Vec<u8>> {
2665 let mut out = Vec::new();
2666 for payload in Parser::new(0).parse_all(bytes) {
2667 if let Payload::DataSection(reader) = payload.expect("payload") {
2668 for d in reader {
2669 let d = d.expect("data segment");
2670 out.push(d.data.to_vec());
2671 }
2672 }
2673 }
2674 out
2675 }
2676
2677 fn module_name(bytes: &[u8]) -> Option<String> {
2681 let engine = crate::runtime::RuntimeConfig::default()
2682 .engine()
2683 .expect("engine");
2684 let module = wasmtime::Module::new(&engine, bytes).expect("module validates");
2685 module.name().map(str::to_string)
2686 }
2687
2688 #[test]
2689 fn package_module_is_named_for_its_package() {
2690 let (bytes, _decl, _ti) = compile_package_modules(
2691 "test:pkg",
2692 &[(
2693 "lib",
2694 "/** Identity.\n * @param x Value to return.\n * @returns `x`. */\nexport function noop(x: number): number { return x; }",
2695 )],
2696 &[],
2697 );
2698 assert_eq!(module_name(&bytes).as_deref(), Some("test:pkg"));
2699 }
2700
2701 #[test]
2702 fn script_module_is_named_main() {
2703 let bytes = compile("function main(): number { return 1; }");
2704 assert_eq!(module_name(&bytes).as_deref(), Some("main"));
2705 }
2706
2707 #[test]
2712 fn test_file_module_is_named_for_its_owning_package() {
2713 let source = "function main(): number { return 1; }";
2714 let compiled = crate::compile::compile_script_owned_by(
2715 "test:pkg",
2716 source,
2717 "tests/thing.test.ts",
2718 crate::FileId(0),
2719 &[],
2720 &[],
2721 )
2722 .expect("compiles");
2723 assert_eq!(module_name(&compiled.wasm).as_deref(), Some("test:pkg"));
2724
2725 assert!(
2726 main_export_func_idx(&compiled.wasm).is_some(),
2727 "entry point must stay exported as `main` — the module name is identity, not mangling"
2728 );
2729 }
2730
2731 struct RecordingAllowAll {
2735 seen: std::sync::Mutex<Vec<(String, String)>>,
2736 }
2737
2738 impl crate::runtime::SecurityCheck for RecordingAllowAll {
2739 fn check(
2740 &self,
2741 caller: &str,
2742 capability: &str,
2743 _context: &serde_json::Value,
2744 ) -> crate::runtime::CheckOutcome {
2745 self.seen
2746 .lock()
2747 .expect("mutex")
2748 .push((caller.to_string(), capability.to_string()));
2749 crate::runtime::CheckOutcome::Allow { rule: None }
2750 }
2751 }
2752
2753 struct FixedSecret;
2754
2755 impl crate::runtime::SecretProvider for FixedSecret {
2756 fn get<'a>(
2757 &'a self,
2758 _name: &'a str,
2759 ) -> std::pin::Pin<
2760 Box<dyn std::future::Future<Output = Result<Option<String>, String>> + Send + 'a>,
2761 > {
2762 Box::pin(async { Ok(Some("SECRET-VALUE".to_string())) })
2763 }
2764 }
2765
2766 #[tokio::test]
2775 async fn main_authored_code_keeps_main_identity_inside_a_package_call() {
2776 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
2777 "test:vuln",
2778 &[(
2779 "lib",
2780 r#"
2781 /**
2782 * Pass-through JSON encoder.
2783 * @param value Value to encode.
2784 * @returns `value` as JSON.
2785 */
2786 export function passthrough(value: unknown): string | undefined {
2787 return JSON.stringify(value);
2788 }
2789 "#,
2790 )],
2791 &[],
2792 );
2793
2794 let recording = std::sync::Arc::new(RecordingAllowAll {
2795 seen: std::sync::Mutex::new(Vec::new()),
2796 });
2797 let cfg = crate::runtime::RuntimeConfig::default();
2798 let engine = cfg.engine().expect("engine");
2799 let mut data =
2800 crate::runtime::StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
2801 data.install_type_info(lib_type_info);
2802 data.security_check = recording.clone();
2803 data.secret_provider = std::sync::Arc::new(FixedSecret);
2804 let mut store = cfg.store(&engine, data).expect("store");
2805 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
2806 crate::runtime::install_runtime_async(&mut linker, &mut store)
2807 .await
2808 .expect("install runtime");
2809 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
2810 let lib_inst = linker
2811 .instantiate_async(&mut store, &lib_module)
2812 .await
2813 .expect("instantiate library");
2814 linker
2815 .instance(&mut store, "test:vuln", lib_inst)
2816 .expect("register library instance");
2817 let public_name = crate::mangle::package_symbol("test:vuln", "passthrough");
2818 let func = lib_inst
2819 .get_func(&mut store, public_name.as_str())
2820 .expect("library public export");
2821 linker
2822 .define(&mut store, "test:vuln", "passthrough", func)
2823 .expect("plain package import alias");
2824
2825 let direct = crate::compile::compile_script(
2827 r#"
2828 import { get } from "submilli:secrets";
2829 function main(): void { const _ = get("TOKEN"); }
2830 "#,
2831 "direct.subm",
2832 crate::FileId(0),
2833 &[&lib_decl],
2834 &[],
2835 )
2836 .expect("direct consumer compiles");
2837 store.data_mut().install_type_info(direct.type_info.clone());
2838 let direct_module = wasmtime::Module::new(&engine, &direct.wasm).expect("direct module");
2839 let direct_inst = linker
2840 .instantiate_async(&mut store, &direct_module)
2841 .await
2842 .expect("instantiate direct consumer");
2843 let err = crate::runtime::dispatch_main_async(&mut store, &direct_inst)
2844 .await
2845 .expect_err("direct read is denied");
2846 assert!(
2847 err.to_string().contains("permission denied"),
2848 "expected denial, got: {err}",
2849 );
2850
2851 let escalated = crate::compile::compile_script(
2853 r#"
2854 import { passthrough } from "test:vuln";
2855 import { get } from "submilli:secrets";
2856
2857 class Exfil {
2858 stolen: string;
2859 constructor() { this.stolen = "none"; }
2860 toJson(): string {
2861 const token = get("TOKEN");
2862 if (token !== undefined) { this.stolen = token; }
2863 return "\"ok\"";
2864 }
2865 }
2866
2867 function main(): string {
2868 const payload = new Exfil();
2869 const _ = passthrough(payload);
2870 return payload.stolen;
2871 }
2872 "#,
2873 "escalated.subm",
2874 crate::FileId(0),
2875 &[&lib_decl],
2876 &[],
2877 )
2878 .expect("escalating consumer compiles");
2879 store
2880 .data_mut()
2881 .install_type_info(escalated.type_info.clone());
2882 let escalated_module =
2883 wasmtime::Module::new(&engine, &escalated.wasm).expect("escalated module");
2884 let escalated_inst = linker
2885 .instantiate_async(&mut store, &escalated_module)
2886 .await
2887 .expect("instantiate escalating consumer");
2888 let result = crate::runtime::dispatch_main_async(&mut store, &escalated_inst).await;
2889 let seen = recording.seen.lock().expect("mutex").clone();
2890
2891 assert!(
2894 !seen.iter().any(|(caller, _)| caller == "test:vuln"),
2895 "main-authored code was attributed to the package it was handed to: {seen:?}",
2896 );
2897 assert!(
2898 result.is_err(),
2899 "main must not obtain a secret it is unconditionally denied; got {result:?}",
2900 );
2901 }
2902
2903 async fn attributions_for(
2908 package: &str,
2909 lib_bytes: &[u8],
2910 lib_decl: &PackageDeclaration,
2911 lib_type_info: crate::TypeInfoTable,
2912 consumer_src: &str,
2913 ) -> (Vec<(String, String)>, bool) {
2914 let recording = std::sync::Arc::new(RecordingAllowAll {
2915 seen: std::sync::Mutex::new(Vec::new()),
2916 });
2917 let cfg = crate::runtime::RuntimeConfig::default();
2918 let engine = cfg.engine().expect("engine");
2919 let mut data =
2920 crate::runtime::StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
2921 data.install_type_info(lib_type_info);
2922 data.security_check = recording.clone();
2923 data.secret_provider = std::sync::Arc::new(FixedSecret);
2924 let mut store = cfg.store(&engine, data).expect("store");
2925 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
2926 crate::runtime::install_runtime_async(&mut linker, &mut store)
2927 .await
2928 .expect("install runtime");
2929 let lib_module = wasmtime::Module::new(&engine, lib_bytes).expect("library module");
2930 let lib_inst = linker
2931 .instantiate_async(&mut store, &lib_module)
2932 .await
2933 .expect("instantiate library");
2934 linker
2935 .instance(&mut store, package, lib_inst)
2936 .expect("register library instance");
2937
2938 let consumer = crate::compile::compile_script(
2939 consumer_src,
2940 "consumer.subm",
2941 crate::FileId(0),
2942 &[lib_decl],
2943 &[],
2944 )
2945 .expect("consumer compiles");
2946 store
2947 .data_mut()
2948 .install_type_info(consumer.type_info.clone());
2949 let consumer_module = wasmtime::Module::new(&engine, &consumer.wasm).expect("module");
2950 let inst = linker
2951 .instantiate_async(&mut store, &consumer_module)
2952 .await
2953 .expect("instantiate consumer");
2954 let ran_ok = crate::runtime::dispatch_main_async(&mut store, &inst)
2955 .await
2956 .is_ok();
2957 (recording.seen.lock().expect("mutex").clone(), ran_ok)
2958 }
2959
2960 #[tokio::test]
2964 async fn main_authored_tojson_inside_an_array_keeps_main_identity() {
2965 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
2966 "test:arr",
2967 &[(
2968 "lib",
2969 r#"
2970 /**
2971 * Pass-through JSON encoder for a collection.
2972 * @param values Values to encode.
2973 * @returns `values` as JSON.
2974 */
2975 export function passthroughAll(values: unknown[]): string {
2976 return JSON.stringify(values);
2977 }
2978 "#,
2979 )],
2980 &[],
2981 );
2982 let (seen, ran_ok) = attributions_for(
2983 "test:arr",
2984 &lib_bytes,
2985 &lib_decl,
2986 lib_type_info,
2987 r#"
2988 import { passthroughAll } from "test:arr";
2989 import { get } from "submilli:secrets";
2990
2991 class Exfil {
2992 stolen: string;
2993 constructor() { this.stolen = "none"; }
2994 toJson(): string {
2995 const t = get("TOKEN");
2996 if (t !== undefined) { this.stolen = t; }
2997 return "\"ok\"";
2998 }
2999 }
3000
3001 function main(): string {
3002 const payload = new Exfil();
3003 const _ = passthroughAll([payload]);
3004 return payload.stolen;
3005 }
3006 "#,
3007 )
3008 .await;
3009 assert!(
3010 !seen.iter().any(|(caller, _)| caller == "test:arr"),
3011 "an element's toJson is main's code wherever the package serializes it: {seen:?}",
3012 );
3013 assert!(
3014 !ran_ok,
3015 "identified as main, the read must be refused rather than completing",
3016 );
3017 }
3018
3019 #[tokio::test]
3025 async fn a_packages_exported_class_method_is_attributed_to_the_package() {
3026 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3027 "test:cls",
3028 &[(
3029 "lib",
3030 r#"
3031 import { get } from "submilli:secrets";
3032
3033 /** Resolves a credential from inside a class method. */
3034 export class Client {
3035 token: string;
3036 constructor() { this.token = "none"; }
3037
3038 /** Reads the token.
3039 * @returns The credential, or "none" when it is absent.
3040 */
3041 load(): string {
3042 const t = get("TOKEN");
3043 return t === undefined ? "none" : t;
3044 }
3045 }
3046 "#,
3047 )],
3048 &[],
3049 );
3050 let (seen, ran_ok) = attributions_for(
3051 "test:cls",
3052 &lib_bytes,
3053 &lib_decl,
3054 lib_type_info,
3055 r#"
3056 import { Client } from "test:cls";
3057 function main(): string {
3058 const c = new Client();
3059 return c.load();
3060 }
3061 "#,
3062 )
3063 .await;
3064 assert!(ran_ok, "the package is permitted, so main must complete");
3065 assert_eq!(
3066 seen,
3067 vec![("test:cls".to_string(), "secrets.get".to_string())],
3068 "a class method's gated call belongs to the package that defines it",
3069 );
3070 }
3071
3072 #[tokio::test]
3077 async fn a_packages_initializer_is_attributed_to_the_package() {
3078 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3079 "test:init",
3080 &[(
3081 "lib",
3082 r#"
3083 import { get } from "submilli:secrets";
3084
3085 const TOKEN: string | undefined = get("TOKEN");
3086
3087 /**
3088 * Reports whether the module-level read succeeded.
3089 * @returns Whether the read succeeded.
3090 */
3091 export function loaded(): boolean {
3092 return TOKEN !== undefined;
3093 }
3094 "#,
3095 )],
3096 &[],
3097 );
3098 let (seen, ran_ok) = attributions_for(
3099 "test:init",
3100 &lib_bytes,
3101 &lib_decl,
3102 lib_type_info,
3103 r#"
3104 import { loaded } from "test:init";
3105 function main(): boolean { return loaded(); }
3106 "#,
3107 )
3108 .await;
3109 assert!(ran_ok, "the package is permitted, so main must complete");
3110 assert_eq!(
3111 seen,
3112 vec![("test:init".to_string(), "secrets.get".to_string())],
3113 "a package initializer's gated call belongs to the package",
3114 );
3115 }
3116
3117 fn custom_section(bytes: &[u8], name: &str) -> Option<Vec<u8>> {
3118 for payload in Parser::new(0).parse_all(bytes) {
3119 if let Payload::CustomSection(reader) = payload.expect("payload")
3120 && reader.name() == name
3121 {
3122 return Some(reader.data().to_vec());
3123 }
3124 }
3125 None
3126 }
3127
3128 fn export_names(bytes: &[u8]) -> Vec<String> {
3129 let mut names = Vec::new();
3130 for payload in Parser::new(0).parse_all(bytes) {
3131 if let Payload::ExportSection(reader) = payload.expect("payload") {
3132 for export in reader {
3133 names.push(export.expect("export").name.to_string());
3134 }
3135 }
3136 }
3137 names
3138 }
3139
3140 fn export_entries(bytes: &[u8]) -> Vec<(String, wasmparser::ExternalKind, u32)> {
3141 let mut exports = Vec::new();
3142 for payload in Parser::new(0).parse_all(bytes) {
3143 if let Payload::ExportSection(reader) = payload.expect("payload") {
3144 for export in reader {
3145 let export = export.expect("export");
3146 exports.push((export.name.to_string(), export.kind, export.index));
3147 }
3148 }
3149 }
3150 exports
3151 }
3152
3153 fn imported_func_count(bytes: &[u8]) -> u32 {
3154 let mut count = 0;
3155 for payload in Parser::new(0).parse_all(bytes) {
3156 if let Payload::ImportSection(reader) = payload.expect("payload") {
3157 for entry in reader {
3158 if let wasmparser::Imports::Single(_, imp) = entry.expect("import")
3159 && matches!(imp.ty, wasmparser::TypeRef::Func(_))
3160 {
3161 count += 1;
3162 }
3163 }
3164 }
3165 }
3166 count
3167 }
3168
3169 fn imported_func_idx(bytes: &[u8], field: &str) -> u32 {
3171 let mut count = 0;
3172 for payload in Parser::new(0).parse_all(bytes) {
3173 if let Payload::ImportSection(reader) = payload.expect("payload") {
3174 for entry in reader {
3175 if let wasmparser::Imports::Single(_, imp) = entry.expect("import")
3176 && matches!(imp.ty, wasmparser::TypeRef::Func(_))
3177 {
3178 if imp.name == field {
3179 return count;
3180 }
3181 count += 1;
3182 }
3183 }
3184 }
3185 }
3186 panic!("no func import named {field}");
3187 }
3188
3189 fn body_call_targets(bytes: &[u8], body_idx: usize) -> Vec<u32> {
3190 let mut bodies_seen = 0;
3191 let mut targets = Vec::new();
3192 for payload in Parser::new(0).parse_all(bytes) {
3193 if let Payload::CodeSectionEntry(body) = payload.expect("payload") {
3194 if bodies_seen == body_idx {
3195 let mut reader = body
3196 .get_operators_reader()
3197 .expect("body operators readable");
3198 while !reader.eof() {
3199 if let wasmparser::Operator::Call { function_index } =
3200 reader.read().expect("operator")
3201 {
3202 targets.push(function_index);
3203 }
3204 }
3205 return targets;
3206 }
3207 bodies_seen += 1;
3208 }
3209 }
3210 targets
3211 }
3212
3213 fn main_body_call_targets(bytes: &[u8]) -> Vec<u32> {
3214 let main_idx = main_export_func_idx(bytes).expect("main exported");
3215 let body_idx = (main_idx - imported_func_count(bytes)) as usize;
3216 body_call_targets(bytes, body_idx)
3217 }
3218
3219 fn main_export_func_idx(bytes: &[u8]) -> Option<u32> {
3220 for payload in Parser::new(0).parse_all(bytes) {
3221 if let Payload::ExportSection(reader) = payload.expect("payload") {
3222 for export in reader {
3223 let export = export.expect("export");
3224 if export.name == "main"
3225 && matches!(export.kind, wasmparser::ExternalKind::Func)
3226 {
3227 return Some(export.index);
3228 }
3229 }
3230 }
3231 }
3232 None
3233 }
3234
3235 fn instantiate_against_prelude(consumer_bytes: &[u8]) {
3236 let _ = link_consumer(consumer_bytes);
3237 }
3238
3239 fn link_consumer(
3240 consumer_bytes: &[u8],
3241 ) -> (
3242 wasmtime::Store<crate::runtime::StoreData>,
3243 wasmtime::Instance,
3244 ) {
3245 let cfg = crate::runtime::RuntimeConfig::default();
3246 link_consumer_with(&cfg, consumer_bytes)
3247 }
3248
3249 fn link_consumer_with(
3250 cfg: &crate::runtime::RuntimeConfig,
3251 consumer_bytes: &[u8],
3252 ) -> (
3253 wasmtime::Store<crate::runtime::StoreData>,
3254 wasmtime::Instance,
3255 ) {
3256 let engine = cfg.engine().expect("engine builds with default config");
3257 let mut store = cfg
3258 .store(
3259 &engine,
3260 crate::runtime::StoreData::with_tempdir()
3261 .expect("tempdir allocates for codegen test VFS"),
3262 )
3263 .expect("store builds with default config");
3264 let consumer_module =
3265 wasmtime::Module::new(&engine, consumer_bytes).expect("consumer module");
3266 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
3267 let consumer_inst = pollster::block_on(async {
3271 crate::runtime::install_runtime_async(&mut linker, &mut store)
3272 .await
3273 .expect("runtime installs the prelude + host functions");
3274 linker
3275 .instantiate_async(&mut store, &consumer_module)
3276 .await
3277 .expect("consumer instantiates against the runtime")
3278 });
3279 (store, consumer_inst)
3280 }
3281
3282 fn run_main_f64(source: &str) -> f64 {
3283 run_main_number(&compile(source))
3284 }
3285
3286 #[test]
3287 fn undefined_backend_distinguishes_nullish_values_and_absence() {
3288 assert_eq!(
3289 run_main_f64(
3290 r#"
3291 function read(value: { a?: number } | null | undefined): number | undefined {
3292 return value?.a;
3293 }
3294 function main(): number {
3295 const absent: { a?: number } = {};
3296 const present: { a?: number } = { a: undefined };
3297 assert(absent.a === undefined, "missing read");
3298 assert(!("a" in absent) && "a" in present, "presence");
3299 assert(read(null) === undefined && read(undefined) === undefined, "chain");
3300 assert((read(undefined) ?? 7) === 7, "coalesce");
3301 const value: number | undefined = undefined;
3302 assert(!value && typeof value === "undefined", "truthiness and tag");
3303 assert(typeof null === "object" && typeof 1n === "bigint", "other tags");
3304 return 1;
3305 }
3306 "#
3307 ),
3308 1.0
3309 );
3310 }
3311
3312 #[test]
3313 fn undefined_backend_preserves_void_callback_completion() {
3314 assert_eq!(
3315 run_main_f64(
3316 r#"
3317 function empty(): void {}
3318 function invoke(callback: () => void): unknown { return callback(); }
3319 function main(): number {
3320 const callback: () => void = () => 42;
3321 const result: unknown = invoke(callback);
3322 assert(result === 42, "void callback preserves value");
3323 const completion: unknown = empty();
3324 assert(completion === undefined, "fallthrough completion");
3325 assert((void 42) === undefined, "void expression");
3326 return 1;
3327 }
3328 "#
3329 ),
3330 1.0
3331 );
3332 }
3333
3334 #[test]
3335 fn undefined_backend_optional_tuple_casts_preserve_length() {
3336 assert_eq!(
3337 run_main_f64(
3338 r#"
3339 function check(value: unknown): [number, string?] {
3340 return value as [number, string?];
3341 }
3342 function main(): number {
3343 const short = check([1]);
3344 const entries: unknown[] = [1, undefined];
3345 const explicit = check(entries);
3346 assert(short.length === 1 && short[1] === undefined, "omitted tuple slot");
3347 assert(explicit.length === 2 && explicit[1] === undefined, "present tuple slot");
3348 assert(JSON.stringify(short) === "[1]", "short tuple JSON");
3349 return 1;
3350 }
3351 "#
3352 ),
3353 1.0
3354 );
3355 }
3356
3357 #[test]
3358 fn undefined_backend_native_defaults_and_constructor_properties() {
3359 assert_eq!(
3360 run_main_f64(
3361 r#"
3362 class Value {
3363 constructor(public value: number = 17) {}
3364 }
3365 function main(): number {
3366 assert(new Value(undefined).value === 17, "parameter property default");
3367 assert("abc".slice(1, undefined) === "bc", "native numeric default");
3368 assert("a".padStart(2, undefined) === " a", "native string default");
3369 return 1;
3370 }
3371 "#
3372 ),
3373 1.0
3374 );
3375 }
3376
3377 fn run_main_number(bytes: &[u8]) -> f64 {
3378 let (mut store, inst) = link_consumer(bytes);
3379 let main = inst.get_func(&mut store, "main").expect("main export");
3380 let mut results = [wasmtime::Val::F64(0)];
3381 pollster::block_on(main.call_async(&mut store, &[], &mut results))
3382 .expect("main does not trap");
3383 match &results[0] {
3384 wasmtime::Val::F64(bits) => f64::from_bits(*bits),
3385 wasmtime::Val::AnyRef(Some(value)) => {
3386 let boxed = value
3387 .as_struct(&mut store)
3388 .expect("read result")
3389 .expect("boxed number");
3390 boxed
3391 .field(&mut store, 1)
3392 .expect("number payload")
3393 .f64()
3394 .expect("f64 payload")
3395 }
3396 other => panic!("expected numeric result, got {other:?}"),
3397 }
3398 }
3399
3400 fn run_main_i32(source: &str) -> i32 {
3401 let bytes = compile(source);
3402 let (mut store, inst) = link_consumer(&bytes);
3403 let main = inst
3404 .get_typed_func::<(), i32>(&mut store, "main")
3405 .expect("main is exported as `() -> i32`");
3406 pollster::block_on(main.call_async(&mut store, ())).expect("main does not trap")
3407 }
3408
3409 fn run_main_expecting_error(source: &str) -> String {
3410 let bytes = compile(source);
3411 let (mut store, inst) = link_consumer(&bytes);
3412 let main = inst.get_func(&mut store, "main").expect("main export");
3413 let result = pollster::block_on(main.call_async(
3414 &mut store,
3415 &[],
3416 &mut [wasmtime::Val::AnyRef(None)],
3417 ));
3418 let err = result.expect_err("expected main() to trap or throw");
3421 let err = crate::runtime::exec::uncaught_error(&mut store, err);
3422 let (sources, file) = crate::Sources::single("script.subm", source).unwrap();
3423 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full)
3424 .expect("backtrace empty — was wasm_backtrace_details enabled?")
3425 }
3426
3427 fn dump_dwarf_dies(bytes: &[u8]) -> String {
3428 use gimli::LittleEndian;
3429 use gimli::read::{AttributeValue, DebugAbbrev, DebugInfo, DebugStr};
3430 use std::fmt::Write;
3431
3432 let info_bytes = custom_section(bytes, ".debug_info").expect(".debug_info");
3433 let abbrev_bytes = custom_section(bytes, ".debug_abbrev").expect(".debug_abbrev");
3434 let str_bytes = custom_section(bytes, ".debug_str").expect(".debug_str");
3435 let debug_info = DebugInfo::new(&info_bytes, LittleEndian);
3436 let debug_abbrev = DebugAbbrev::new(&abbrev_bytes, LittleEndian);
3437 let debug_str = DebugStr::new(&str_bytes, LittleEndian);
3438
3439 let mut out = String::new();
3440 let mut units = debug_info.units();
3441 while let Some(header) = units.next().expect("unit") {
3442 let abbreviations = header.abbreviations(&debug_abbrev).expect("abbreviations");
3443 let mut entries = header.entries(&abbreviations);
3444 while let Some(entry) = entries.next_dfs().expect("entry") {
3445 let tag = entry.tag().static_string().unwrap_or("DW_TAG_<unknown>");
3446 writeln!(out, "<{:#x}> {}", entry.offset().0, tag).unwrap();
3447 for attr in entry.attrs() {
3448 let name = attr.name().static_string().unwrap_or("DW_AT_<unknown>");
3449 let value_repr = match attr.value() {
3450 AttributeValue::Addr(a) => format!("Addr({a:#x})"),
3451 AttributeValue::Udata(u) => format!("Udata({u})"),
3452 AttributeValue::DebugStrRef(off) => {
3453 let s = debug_str.get_str(off).expect("str");
3454 format!("Str({:?})", std::str::from_utf8(s.slice()).unwrap_or("?"))
3455 }
3456 AttributeValue::Language(lang) => format!(
3457 "Language({})",
3458 lang.static_string().unwrap_or("DW_LANG_<unknown>")
3459 ),
3460 other => format!("{other:?}"),
3461 };
3462 writeln!(out, " {name} = {value_repr}").unwrap();
3463 }
3464 }
3465 }
3466 out
3467 }
3468
3469 fn dump_dwarf_lines(bytes: &[u8]) -> String {
3470 use gimli::LittleEndian;
3471 use gimli::read::{DebugAbbrev, DebugInfo, DebugLine};
3472 use std::fmt::Write;
3473
3474 let info_bytes = custom_section(bytes, ".debug_info").expect(".debug_info");
3475 let abbrev_bytes = custom_section(bytes, ".debug_abbrev").expect(".debug_abbrev");
3476 let line_bytes = custom_section(bytes, ".debug_line").expect(".debug_line");
3477 let debug_info = DebugInfo::new(&info_bytes, LittleEndian);
3478 let debug_abbrev = DebugAbbrev::new(&abbrev_bytes, LittleEndian);
3479 let debug_line = DebugLine::new(&line_bytes, LittleEndian);
3480
3481 let mut out = String::new();
3482 let header = debug_info
3483 .units()
3484 .next()
3485 .expect("units")
3486 .expect("at least one unit");
3487 let _ = header.abbreviations(&debug_abbrev).expect("abbrevs");
3488 let program = debug_line
3489 .program(gimli::DebugLineOffset(0), header.address_size(), None, None)
3490 .expect("line program");
3491 let (program, sequences) = program.sequences().expect("sequences");
3492 for seq in sequences {
3493 writeln!(out, "sequence start={:#x} end={:#x}", seq.start, seq.end,).unwrap();
3494 let cloned = program.clone();
3495 let mut state = cloned.resume_from(&seq);
3496 while let Some((header, row)) = state.next_row().expect("next_row") {
3497 let _ = header;
3498 let line = row.line().map_or(0, std::num::NonZero::get);
3499 writeln!(
3500 out,
3501 " addr={:#x} file={} line={} col={:?} stmt={}{}",
3502 row.address(),
3503 row.file_index(),
3504 line,
3505 row.column(),
3506 row.is_stmt(),
3507 if row.end_sequence() {
3508 " end_sequence"
3509 } else {
3510 ""
3511 },
3512 )
3513 .unwrap();
3514 }
3515 }
3516 out
3517 }
3518
3519 #[test]
3520 fn empty_main_links_with_prelude() {
3521 instantiate_against_prelude(&compile("function main(): void { }"));
3522 }
3523
3524 #[test]
3525 fn main_with_number_return_links_with_prelude() {
3526 instantiate_against_prelude(&compile("function main(): number { return 0; }"));
3527 }
3528
3529 #[test]
3530 fn main_with_boolean_return_links_with_prelude() {
3531 instantiate_against_prelude(&compile("function main(): boolean { return true; }"));
3532 }
3533
3534 #[test]
3535 fn string_runtime_imported_from_prelude() {
3536 let bytes = compile("function main(): void { }");
3537 assert_eq!(function_count(&bytes), 4);
3539 let stable_imports: Vec<_> = imports(&bytes)
3540 .into_iter()
3541 .filter(|(_, name)| !is_sub421_temporal_getter_import(name))
3542 .collect();
3543 assert!(
3544 stable_imports.contains(&(
3545 crate::runtime::prelude::MODULE_NAME.to_string(),
3546 crate::mangle::prelude("__error_tag").to_string(),
3547 )),
3548 "host tag import should use canonical name"
3549 );
3550 assert!(
3551 stable_imports.contains(&(
3552 crate::runtime::prelude::MODULE_NAME.to_string(),
3553 crate::mangle::prelude("string_vtable").to_string(),
3554 )),
3555 "host-owned string_vtable import should use canonical name"
3556 );
3557 assert!(
3558 !stable_imports
3559 .iter()
3560 .any(|(module, _)| module == "submilli:crypto"),
3561 "unused stdlib imports should be tree-shaken"
3562 );
3563 assert!(
3564 !stable_imports
3565 .iter()
3566 .any(|(module, name)| module == "submilli:prelude" && name == "string_vtable"),
3567 "prelude imports must not use short names"
3568 );
3569 assert_eq!(data_count(&bytes), None);
3570 assert!(data_segments(&bytes).is_empty());
3571 }
3572
3573 fn is_sub421_temporal_getter_import(name: &str) -> bool {
3574 matches!(
3575 name,
3576 "Temporal#PlainDate#monthCode"
3577 | "Temporal#PlainDate#dayOfYear"
3578 | "Temporal#PlainDate#weekOfYear"
3579 | "Temporal#PlainDate#yearOfWeek"
3580 | "Temporal#PlainDate#daysInWeek"
3581 | "Temporal#PlainDate#daysInMonth"
3582 | "Temporal#PlainDate#daysInYear"
3583 | "Temporal#PlainDate#monthsInYear"
3584 | "Temporal#PlainDate#inLeapYear"
3585 | "Temporal#PlainTime#millisecond"
3586 | "Temporal#PlainTime#microsecond"
3587 | "Temporal#PlainDateTime#dayOfWeek"
3588 | "Temporal#PlainDateTime#monthCode"
3589 | "Temporal#PlainDateTime#dayOfYear"
3590 | "Temporal#PlainDateTime#weekOfYear"
3591 | "Temporal#PlainDateTime#yearOfWeek"
3592 | "Temporal#PlainDateTime#daysInWeek"
3593 | "Temporal#PlainDateTime#daysInMonth"
3594 | "Temporal#PlainDateTime#daysInYear"
3595 | "Temporal#PlainDateTime#monthsInYear"
3596 | "Temporal#PlainDateTime#inLeapYear"
3597 | "Temporal#PlainDateTime#millisecond"
3598 | "Temporal#PlainDateTime#microsecond"
3599 | "Temporal#PlainYearMonth#monthCode"
3600 | "Temporal#PlainYearMonth#daysInMonth"
3601 | "Temporal#PlainYearMonth#daysInYear"
3602 | "Temporal#PlainYearMonth#monthsInYear"
3603 | "Temporal#PlainYearMonth#inLeapYear"
3604 | "Temporal#PlainMonthDay#monthCode"
3605 | "Temporal#ZonedDateTime#monthCode"
3606 | "Temporal#ZonedDateTime#dayOfYear"
3607 | "Temporal#ZonedDateTime#weekOfYear"
3608 | "Temporal#ZonedDateTime#yearOfWeek"
3609 | "Temporal#ZonedDateTime#daysInWeek"
3610 | "Temporal#ZonedDateTime#daysInMonth"
3611 | "Temporal#ZonedDateTime#daysInYear"
3612 | "Temporal#ZonedDateTime#monthsInYear"
3613 | "Temporal#ZonedDateTime#inLeapYear"
3614 | "Temporal#ZonedDateTime#millisecond"
3615 | "Temporal#ZonedDateTime#microsecond"
3616 | "Temporal#ZonedDateTime#nanosecond"
3617 )
3618 }
3619
3620 #[test]
3621 fn main_function_index_follows_imports() {
3622 let bytes = compile("function main(): void { }");
3623 assert_eq!(
3624 main_export_func_idx(&bytes),
3625 Some(imported_func_count(&bytes) + 1),
3626 );
3627 }
3628
3629 #[test]
3630 fn main_still_exported_uniquely() {
3631 let bytes = compile("function main(): void { }");
3632 assert_eq!(
3633 export_names(&bytes),
3634 vec!["main".to_string(), "__main_output".to_string()]
3635 );
3636 }
3637
3638 #[test]
3639 fn symbol_table_resolves_prelude_function_indices() {
3640 let map = build_symbol_table("function main(): void { }");
3641 assert_eq!(map.prelude_func_idx("isFinite"), Some(0));
3643 assert_eq!(map.prelude_func_idx("isNaN"), Some(1));
3644 assert_eq!(map.prelude_func_idx("string_cmp"), Some(2));
3645 assert_eq!(map.prelude_func_idx("string_concat"), Some(3));
3646 assert_eq!(map.prelude_func_idx("string_eq"), Some(4));
3647 assert_eq!(map.prelude_func_idx("string_length"), Some(5));
3648 assert_eq!(map.prelude_func_idx("missing"), None);
3649 }
3650
3651 #[test]
3652 fn symbol_table_resolves_prelude_string_type_index() {
3653 let map = build_symbol_table("function main(): void { }");
3654 assert_eq!(map.string_type_idx(), Some(3));
3655 assert_eq!(map.raw_string_type_idx(), Some(0));
3656 }
3657
3658 #[test]
3659 fn module_with_one_string_adds_one_data_segment() {
3660 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
3661 instantiate_against_prelude(&bytes);
3662 assert_eq!(data_count(&bytes), Some(1));
3663 let segments = data_segments(&bytes);
3664 assert_eq!(segments.len(), 1);
3665 assert_eq!(segments[0], b"h\0e\0l\0l\0o\0".to_vec());
3666 }
3667
3668 #[test]
3669 fn module_with_two_distinct_strings_has_two_segments() {
3670 let bytes = compile(
3671 r#"let x: string = "hello"; let y: string = "world"; function main(): void { }"#,
3672 );
3673 instantiate_against_prelude(&bytes);
3674 assert_eq!(data_count(&bytes), Some(2));
3675 let segments = data_segments(&bytes);
3676 assert_eq!(segments.len(), 2);
3677 assert_eq!(segments[0], b"h\0e\0l\0l\0o\0".to_vec());
3678 assert_eq!(segments[1], b"w\0o\0r\0l\0d\0".to_vec());
3679 }
3680
3681 #[test]
3682 fn module_with_dedup_strings_has_one_segment() {
3683 let bytes = compile(
3684 r#"let x: string = "hello"; let y: string = "hello"; function main(): void { }"#,
3685 );
3686 instantiate_against_prelude(&bytes);
3687 assert_eq!(data_count(&bytes), Some(1));
3688 assert_eq!(data_segments(&bytes).len(), 1);
3689 }
3690
3691 #[test]
3692 fn module_with_strings_keeps_runtime_imports() {
3693 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
3694 assert_eq!(function_count(&bytes), 4);
3696 let imp = imports(&bytes);
3697 assert!(imp.contains(&(
3698 crate::runtime::prelude::MODULE_NAME.to_string(),
3699 crate::mangle::prelude("string_vtable").to_string(),
3700 )));
3701 assert!(
3702 !imp.iter()
3703 .any(|(module, _)| module == crate::runtime::BIGINT_MODULE_NAME),
3704 "unused bigint imports should be tree-shaken",
3705 );
3706 }
3707
3708 #[test]
3709 fn cross_module_string_type_canonicalization_proves_out() {
3710 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
3711 instantiate_against_prelude(&bytes);
3712 }
3713
3714 #[test]
3715 fn helper_emits_function_body_but_is_not_exported() {
3716 let bytes = compile("function main(): void { } function helper(): void { }");
3717 instantiate_against_prelude(&bytes);
3718 assert_eq!(function_count(&bytes), 5);
3720 assert_eq!(
3721 export_names(&bytes),
3722 vec!["main".to_string(), "__main_output".to_string()]
3723 );
3724 }
3725
3726 #[test]
3727 fn typed_exports_emit_function_and_global_but_skip_types() {
3728 let bytes = compile(
3729 r#"
3730 export interface Public { value: number; }
3731 export const answer: number = 42;
3732 export function api(): number { return answer; }
3733 function main(): void { }
3734 "#,
3735 );
3736 instantiate_against_prelude(&bytes);
3737 let exports = export_entries(&bytes);
3738 assert!(
3739 exports.iter().any(|(name, kind, _)| {
3740 name == "main#api" && matches!(kind, wasmparser::ExternalKind::Func)
3741 }),
3742 "expected exported function entry, got {exports:?}",
3743 );
3744 assert!(
3745 exports.iter().any(|(name, kind, _)| {
3746 name == "main#answer" && matches!(kind, wasmparser::ExternalKind::Global)
3747 }),
3748 "expected exported global entry, got {exports:?}",
3749 );
3750 assert!(
3751 !exports.iter().any(|(name, _, _)| name == "main#Public"),
3752 "type-only exports must not become Wasm exports: {exports:?}",
3753 );
3754 }
3755
3756 #[test]
3757 fn package_reexport_aliases_share_one_wrapper_index() {
3758 let (bytes, package, _) = compile_package_modules(
3759 "test:lib",
3760 &[
3761 (
3762 "lib",
3763 r#"export { inner as first, inner as second } from "./util";"#,
3764 ),
3765 (
3766 "util",
3767 "/** Inner function.\n * @returns One. */\nexport function inner(): number { return 1; }",
3768 ),
3769 ],
3770 &[],
3771 );
3772 assert_eq!(
3773 package.values.keys().collect::<Vec<_>>(),
3774 vec![&"first".to_string(), &"second".to_string()],
3775 );
3776 let exports = export_entries(&bytes);
3777 let first = exports
3778 .iter()
3779 .find(|(name, kind, _)| {
3780 name == "test:lib#first" && matches!(kind, wasmparser::ExternalKind::Func)
3781 })
3782 .expect("first function export")
3783 .2;
3784 let second = exports
3785 .iter()
3786 .find(|(name, kind, _)| {
3787 name == "test:lib#second" && matches!(kind, wasmparser::ExternalKind::Func)
3788 })
3789 .expect("second function export")
3790 .2;
3791 assert_eq!(first, second, "aliases for one target share one wrapper");
3792 assert!(
3793 main_export_func_idx(&bytes).is_none(),
3794 "libraries have no main"
3795 );
3796 }
3797
3798 struct RecordingSecurity {
3799 seen: std::sync::Mutex<Vec<(String, String)>>,
3800 }
3801
3802 impl crate::runtime::SecurityCheck for RecordingSecurity {
3803 fn check(
3804 &self,
3805 caller: &str,
3806 capability: &str,
3807 _context: &serde_json::Value,
3808 ) -> crate::runtime::CheckOutcome {
3809 self.seen
3810 .lock()
3811 .expect("recording security mutex")
3812 .push((caller.to_string(), capability.to_string()));
3813 if capability == "test.denied" {
3814 crate::runtime::CheckOutcome::Deny {
3815 rule: None,
3816 reason: "blocked by test".to_string(),
3817 }
3818 } else {
3819 crate::runtime::CheckOutcome::Allow { rule: None }
3820 }
3821 }
3822 }
3823
3824 #[tokio::test]
3825 async fn a_package_api_attributes_to_the_package_on_both_outcomes() {
3826 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3827 "test:lib",
3828 &[(
3829 "lib",
3830 r#"
3831 import security from "submilli:security";
3832 /**
3833 * Allowed operation.
3834 * @capability test.allowed {}
3835 */
3836 export function allowed(): void {
3837 security.check("test.allowed", {});
3838 }
3839 /**
3840 * Denied operation.
3841 * @capability test.denied {}
3842 */
3843 export function denied(): void {
3844 security.check("test.denied", {});
3845 }
3846 "#,
3847 )],
3848 &[],
3849 );
3850 let recording = std::sync::Arc::new(RecordingSecurity {
3851 seen: std::sync::Mutex::new(Vec::new()),
3852 });
3853 let cfg = crate::runtime::RuntimeConfig::default();
3854 let engine = cfg.engine().expect("engine");
3855 let mut data =
3856 crate::runtime::StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
3857 data.install_type_info(lib_type_info);
3858 data.security_check = recording.clone();
3859 let mut store = cfg.store(&engine, data).expect("store");
3860 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
3861 crate::runtime::install_runtime_async(&mut linker, &mut store)
3862 .await
3863 .expect("install runtime");
3864 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
3865 let lib_inst = linker
3866 .instantiate_async(&mut store, &lib_module)
3867 .await
3868 .expect("instantiate library");
3869 linker
3870 .instance(&mut store, "test:lib", lib_inst)
3871 .expect("register library instance");
3872 for name in ["allowed", "denied"] {
3873 let public_name = crate::mangle::package_symbol("test:lib", name);
3874 let func = lib_inst
3875 .get_func(&mut store, public_name.as_str())
3876 .expect("library public export");
3877 linker
3878 .define(&mut store, "test:lib", name, func)
3879 .expect("plain package import alias");
3880 }
3881
3882 let allowed = crate::compile::compile_script(
3883 r#"import { allowed } from "test:lib"; function main(): void { allowed(); }"#,
3884 "consumer.subm",
3885 crate::FileId(0),
3886 &[&lib_decl],
3887 &[],
3888 )
3889 .expect("allowed consumer compiles");
3890 store
3891 .data_mut()
3892 .install_type_info(allowed.type_info.clone());
3893 let allowed_module = wasmtime::Module::new(&engine, &allowed.wasm).expect("allowed module");
3894 let allowed_inst = linker
3895 .instantiate_async(&mut store, &allowed_module)
3896 .await
3897 .expect("instantiate allowed consumer");
3898 crate::runtime::dispatch_main_async(&mut store, &allowed_inst)
3899 .await
3900 .expect("allowed consumer runs");
3901
3902 assert_eq!(
3903 recording
3904 .seen
3905 .lock()
3906 .expect("recording security mutex")
3907 .as_slice(),
3908 &[("main".to_string(), "test.allowed".to_string())],
3909 );
3910
3911 let denied = crate::compile::compile_script(
3912 r#"import { denied } from "test:lib"; function main(): void { denied(); }"#,
3913 "consumer.subm",
3914 crate::FileId(0),
3915 &[&lib_decl],
3916 &[],
3917 )
3918 .expect("denied consumer compiles");
3919 store.data_mut().install_type_info(denied.type_info.clone());
3920 let denied_module = wasmtime::Module::new(&engine, &denied.wasm).expect("denied module");
3921 let denied_inst = linker
3922 .instantiate_async(&mut store, &denied_module)
3923 .await
3924 .expect("instantiate denied consumer");
3925 let err = crate::runtime::dispatch_main_async(&mut store, &denied_inst)
3926 .await
3927 .expect_err("denied security check throws");
3928 assert!(
3929 err.to_string().contains("permission denied"),
3930 "unexpected error: {err}",
3931 );
3932 assert!(
3933 err.to_string().contains("caller=main"),
3934 "unexpected error: {err}",
3935 );
3936 }
3937
3938 #[tokio::test]
3943 async fn a_caught_throw_from_a_package_export_leaves_main_as_the_caller() {
3944 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3945 "test:lib",
3946 &[(
3947 "lib",
3948 r#"
3949 /** Always throws, so the caller can catch and continue. */
3950 export function boom(): void {
3951 throw new Error("boom");
3952 }
3953 "#,
3954 )],
3955 &[],
3956 );
3957 let cfg = crate::runtime::RuntimeConfig::default();
3958 let engine = cfg.engine().expect("engine");
3959 let mut data =
3960 crate::runtime::StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
3961 data.install_type_info(lib_type_info);
3962 let mut store = cfg.store_async(&engine, data).expect("store");
3963 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
3964 crate::runtime::install_runtime_async(&mut linker, &mut store)
3965 .await
3966 .expect("install runtime");
3967 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
3968 let lib_inst = linker
3969 .instantiate_async(&mut store, &lib_module)
3970 .await
3971 .expect("instantiate library");
3972 linker
3973 .instance(&mut store, "test:lib", lib_inst)
3974 .expect("register library instance");
3975 let public_name = crate::mangle::package_symbol("test:lib", "boom");
3976 let func = lib_inst
3977 .get_func(&mut store, public_name.as_str())
3978 .expect("library public export");
3979 linker
3980 .define(&mut store, "test:lib", "boom", func)
3981 .expect("plain package import alias");
3982
3983 let consumer = crate::compile::compile_script(
3984 r#"
3985 import { boom } from "test:lib";
3986 import { get } from "submilli:secrets";
3987
3988 function main(): void {
3989 try {
3990 boom();
3991 } catch (e: Error) {
3992 // The export's frame must come off even though it threw.
3993 }
3994 const _ = get("TOKEN");
3995 }
3996 "#,
3997 "consumer.subm",
3998 crate::FileId(0),
3999 &[&lib_decl],
4000 &[],
4001 )
4002 .expect("consumer compiles");
4003 store
4004 .data_mut()
4005 .install_type_info(consumer.type_info.clone());
4006 let consumer_module =
4007 wasmtime::Module::new(&engine, &consumer.wasm).expect("consumer module");
4008 let consumer_inst = linker
4009 .instantiate_async(&mut store, &consumer_module)
4010 .await
4011 .expect("instantiate consumer");
4012
4013 let err = crate::runtime::dispatch_main_async(&mut store, &consumer_inst)
4014 .await
4015 .expect_err("secrets.get from main is refused");
4016
4017 assert!(
4018 err.to_string().contains("caller=main"),
4019 "a leaked frame would attribute this to test:lib: {err}",
4020 );
4021 }
4022
4023 struct RecordingPerCaller {
4027 seen: std::sync::Mutex<Vec<(String, String)>>,
4028 deny_caller: Option<&'static str>,
4029 }
4030
4031 impl crate::runtime::SecurityCheck for RecordingPerCaller {
4032 fn check(
4033 &self,
4034 caller: &str,
4035 capability: &str,
4036 _context: &serde_json::Value,
4037 ) -> crate::runtime::CheckOutcome {
4038 self.seen
4039 .lock()
4040 .expect("recording security mutex")
4041 .push((caller.to_string(), capability.to_string()));
4042 if self.deny_caller == Some(caller) {
4043 crate::runtime::CheckOutcome::Deny {
4044 rule: None,
4045 reason: "blocked by test".to_string(),
4046 }
4047 } else {
4048 crate::runtime::CheckOutcome::Allow { rule: None }
4049 }
4050 }
4051 }
4052
4053 const PACKAGE_WITH_GATED_INITIALIZER: &str = r#"
4056 import { exists } from "submilli:fs";
4057 const _probe: boolean = exists("/init-probe");
4058 /** No-op export; the initializer is what this package is for. */
4059 export function noop(): void {}
4060 "#;
4061
4062 async fn install_package_with_gated_initializer(
4063 recording: std::sync::Arc<RecordingPerCaller>,
4064 ) -> (
4065 wasmtime::Store<crate::runtime::StoreData>,
4066 wasmtime::Result<()>,
4067 ) {
4068 let (lib_bytes, lib_decl, lib_type_info) =
4069 compile_package_modules("test:lib", &[("lib", PACKAGE_WITH_GATED_INITIALIZER)], &[]);
4070 let cfg = crate::runtime::RuntimeConfig::default();
4071 let engine = cfg.engine().expect("engine");
4072 let mut data =
4073 crate::runtime::StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
4074 data.security_check = recording;
4075 let mut store = cfg.store(&engine, data).expect("store");
4076 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
4077 crate::runtime::install_runtime_async(&mut linker, &mut store)
4078 .await
4079 .expect("install runtime");
4080 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
4081 let outcome = crate::runtime::install_package_modules_async(
4082 &mut linker,
4083 &mut store,
4084 &[crate::runtime::LinkedPackageModule {
4085 module: &lib_module,
4086 declaration: &lib_decl,
4087 type_info: &lib_type_info,
4088 }],
4089 )
4090 .await;
4091 (store, outcome)
4092 }
4093
4094 #[tokio::test]
4095 async fn package_initializers_are_attributed_to_the_package() {
4096 let recording = std::sync::Arc::new(RecordingPerCaller {
4097 seen: std::sync::Mutex::new(Vec::new()),
4098 deny_caller: None,
4099 });
4100 let (_store, outcome) = install_package_with_gated_initializer(recording.clone()).await;
4101 outcome.expect("package instantiates");
4102
4103 assert_eq!(
4104 recording
4105 .seen
4106 .lock()
4107 .expect("recording security mutex")
4108 .as_slice(),
4109 &[("test:lib".to_string(), "fs.stat".to_string())],
4110 );
4111 }
4112
4113 #[tokio::test]
4117 async fn package_initializer_denied_to_the_package_fails_and_restores_the_stack() {
4118 let recording = std::sync::Arc::new(RecordingPerCaller {
4119 seen: std::sync::Mutex::new(Vec::new()),
4120 deny_caller: Some("test:lib"),
4121 });
4122 let (_store, outcome) = install_package_with_gated_initializer(recording.clone()).await;
4123 let err = outcome.expect_err("denied initializer traps");
4124
4125 let message = err.to_string();
4130 assert!(
4131 message.contains("package `test:lib` failed to initialize"),
4132 "the failure must name the package: {message}",
4133 );
4134 assert!(
4135 message.contains("fs.stat") && message.contains("caller=test:lib"),
4136 "the failure must name the capability and caller: {message}",
4137 );
4138
4139 assert_eq!(
4140 recording
4141 .seen
4142 .lock()
4143 .expect("recording security mutex")
4144 .as_slice(),
4145 &[("test:lib".to_string(), "fs.stat".to_string())],
4146 );
4147 }
4148
4149 fn fake_globals_definitions() -> crate::PackageDeclaration {
4150 use crate::{PackageDeclaration, Span, Type, ValueKind, ValueSymbol};
4151 let mut defs = PackageDeclaration::with_package("test:globals");
4152 defs.values.insert(
4153 "counter".to_string(),
4154 ValueSymbol {
4155 name: "counter".to_string(),
4156 mangled_name: crate::mangle::package_symbol("test:globals", "counter"),
4157 declaration_span: Span::at(crate::FileId(0)),
4158 kind: ValueKind::Let {
4159 ty: Type::Number,
4160 doc: None,
4161 },
4162 },
4163 );
4164 defs.values.insert(
4165 "max_iterations".to_string(),
4166 ValueSymbol {
4167 name: "max_iterations".to_string(),
4168 mangled_name: crate::mangle::package_symbol("test:globals", "max_iterations"),
4169 declaration_span: Span::at(crate::FileId(0)),
4170 kind: ValueKind::Const {
4171 ty: Type::Number,
4172 doc: None,
4173 },
4174 },
4175 );
4176 defs
4177 }
4178
4179 fn compile_with_package_imports<'a>(
4180 source: &str,
4181 packages: &'a [&'a crate::PackageDeclaration],
4182 ) -> Vec<u8> {
4183 let ta = type_check_with_packages(source, packages);
4184 let (prelude_defs, host_defs, internal_defs) =
4185 prelude::cached_runtime_package_declarations();
4186 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
4187 dependencies.extend(host_defs.iter());
4188 dependencies.extend(internal_defs.iter());
4189 dependencies.extend_from_slice(packages);
4190 codegen(source, "script.subm", crate::FileId(0), &ta, &dependencies)
4191 .expect("code generation")
4192 }
4193
4194 fn imports_with_kind(bytes: &[u8]) -> Vec<(String, String, &'static str, bool)> {
4195 let mut out = Vec::new();
4196 for payload in Parser::new(0).parse_all(bytes) {
4197 if let Payload::ImportSection(reader) = payload.expect("payload") {
4198 for entry in reader {
4199 match entry.expect("import") {
4200 wasmparser::Imports::Single(_, imp) => {
4201 let (kind, mutable) = match imp.ty {
4202 wasmparser::TypeRef::Func(_) => ("func", false),
4203 wasmparser::TypeRef::Global(g) => ("global", g.mutable),
4204 _ => ("other", false),
4205 };
4206 out.push((imp.module.to_string(), imp.name.to_string(), kind, mutable));
4207 }
4208 other => panic!("unexpected import form {other:?}"),
4209 }
4210 }
4211 }
4212 }
4213 out
4214 }
4215
4216 #[test]
4217 fn let_and_const_become_global_imports() {
4218 let defs = fake_globals_definitions();
4219 let bytes = compile_with_package_imports(
4220 r#"import { counter, max_iterations } from "test:globals";
4221function main(): number { return counter + max_iterations; }"#,
4222 &[&defs],
4223 );
4224 let imp = imports_with_kind(&bytes);
4225 let counter_name = crate::mangle::package_symbol("test:globals", "counter");
4226 let counter = imp
4227 .iter()
4228 .find(|(_, n, _, _)| n == counter_name.as_str())
4229 .expect("counter import");
4230 assert_eq!(counter.0, "test:globals");
4231 assert_eq!(counter.2, "global");
4232 assert!(counter.3, "let imports as mutable global");
4233
4234 let max_iterations_name = crate::mangle::package_symbol("test:globals", "max_iterations");
4235 let max_it = imp
4236 .iter()
4237 .find(|(_, n, _, _)| n == max_iterations_name.as_str())
4238 .expect("max_iterations import");
4239 assert_eq!(max_it.0, "test:globals");
4240 assert_eq!(max_it.2, "global");
4241 assert!(
4245 max_it.3,
4246 "const imports as mutable global to match the exporter"
4247 );
4248 }
4249
4250 #[test]
4251 fn ported_methods_route_to_prelude_host_not_wasm_wrapper() {
4252 let src = "function main(): void { \
4253 [1, 2].forEach((x: number) => { }); \
4254 const r: string = \"ab\".repeat(2); \
4255 assert(r.length === 4); \
4256 const s = new Set<number>(); \
4257 s.add(1); \
4258 s.delete(1); \
4259 s.add(2); \
4260 assert(s.size === 1 && s.has(2)); \
4261 const f: string = (3.14).toFixed(1); \
4262 const b: string = true.toString(); \
4263 assert(Number.isInteger(4) && Number.parseInt(\"10\", 10) === 10); \
4264 assert(Number.EPSILON > 0); \
4265 assert(Math.PI > 3 && Math.abs(-2) === 2 && Math.imul(2, 3) === 6 && Math.min(3, 1, 2) === 1 && Math.random() >= 0); \
4266 const u = Uint8Array.fromHex(\"01ff\"); \
4267 u.fill(7); \
4268 assert(u.length === 2 && u.toHex() === \"0707\"); \
4269 const enc = new TextEncoder().encode(\"hi\"); \
4270 const dec: string = new TextDecoder().decode(enc); \
4271 assert(dec === \"hi\"); \
4272 const pd = Temporal.PlainDate.from(\"2024-03-09\"); \
4273 const pd2 = pd.add({ days: 1 }).subtract({ days: 1 }).with({ day: 10 }); \
4274 const pd3 = pd.toPlainYearMonth().toPlainDate({ day: 9 }); \
4275 const pd4 = pd.toPlainMonthDay().toPlainDate({ year: 2024 }); \
4276 const pdtFromDate = pd.toPlainDateTime(); \
4277 const zFromDate = pd.toZonedDateTime(\"UTC\"); \
4278 assert(pd.until(pd2).days === 1 && pd2.since(pd).days === 1); \
4279 assert(pd.year === 2024 && pd.toString() === \"2024-03-09\" && pd.toJSON() === \"2024-03-09\" && pd2.day === 10 && pd3.day === 9 && pd4.year === 2024 && pdtFromDate.hour === 0 && zFromDate.year === 2024); \
4280 const pt = Temporal.PlainTime.from(\"15:30:45.123456789\"); \
4281 const pt2 = pt.add({ hours: 1 }).with({ minute: 0 }); \
4282 assert(pt.until(pt2).hours === 1 && pt.hour === 15 && pt.nanosecond === 123456789 && pt2.minute === 0); \
4283 const pdt = Temporal.PlainDateTime.from(\"2024-03-09T15:30:45\"); \
4284 const pdt2 = pdt.subtract({ hours: 1 }).with({ second: 0 }); \
4285 const zFromDateTime = pdt.toZonedDateTime(\"UTC\"); \
4286 assert(pdt.since(pdt2).hours === 1 && pdt.toPlainDate().day === 9 && pdt.toPlainTime().hour === 15 && pdt.toPlainYearMonth().month === 3 && pdt.toPlainMonthDay().day === 9); \
4287 assert(pdt.month === 3 && pdt2.second === 0 && zFromDateTime.month === 3 && Temporal.PlainDateTime.compare(pdt, pdt) === 0); \
4288 const ym = Temporal.PlainYearMonth.from(\"2024-03\"); \
4289 const ym2 = ym.add({ months: 1 }).with({ month: 5 }); \
4290 assert(ym.until(ym2).months === 2 && ym.monthCode === \"M03\" && ym2.month === 5); \
4291 const md = Temporal.PlainMonthDay.from(\"03-09\"); \
4292 const md2 = md.with({ day: 10 }); \
4293 assert(md.day === 9 && md2.day === 10); \
4294 const i0 = Temporal.Instant.from(\"2024-03-09T15:30:45.123456789Z\"); \
4295 const i1 = Temporal.Instant.fromEpochMilliseconds(i0.epochMilliseconds); \
4296 const i2 = Temporal.Instant.fromEpochNanoseconds(i0.epochNanoseconds); \
4297 const i3 = i0.add({ hours: 1 }).subtract({ hours: 1 }).round({ smallestUnit: \"second\" }); \
4298 const id0 = i0.until(i1); const id1 = i1.since(i0); \
4299 const iz = i0.toZonedDateTimeISO(\"UTC\"); \
4300 assert(Temporal.Instant.compare(i0, i2) === 0 && i0.equals(i2) && i0.toString().length > 0 && i0.toJSON().length > 0 && i3.epochMilliseconds > 0 && id0.seconds === id1.seconds && iz.year === 2024); \
4301 const dur0 = new Temporal.Duration({ hours: 1 }); const dur1 = Temporal.Duration.from(\"PT1H\"); \
4302 assert(Temporal.Duration.compare(dur0, dur1) === 0 && Temporal.ZonedDateTime.compare(iz, iz) === 0); \
4303 const ni = Temporal.Now.instant(); const ntz = Temporal.Now.timeZoneId(); const nz = Temporal.Now.zonedDateTimeISO(\"UTC\"); \
4304 assert(ni.epochMilliseconds > 0 && ntz.length > 0 && nz.timeZoneId === \"UTC\"); \
4305 const re: RegExp = new RegExp(\"a\", \"g\"); \
4306 assert(re.test(\"a\") && re.global && re.source === \"a\" && re.flags === \"g\" && re.lastIndex >= 0); \
4307 const rmatch: RegExpMatch | null = re.exec(\"a\"); \
4308 assert(rmatch !== null); \
4309 if (rmatch !== null) { const gg = rmatch.groups; const ng = rmatch.namedGroups; \
4310 assert(rmatch.match === \"a\" && rmatch.index === 0 && rmatch.input === \"a\" && gg.length >= 0 && ng.size >= 0); } \
4311 const sm: RegExpMatch | null = \"a\".match(re); \
4312 const marr = \"aa\".matchAll(re); \
4313 const rep: string = \"a\".replace(\"a\", \"b\"); \
4314 const repall: string = \"aa\".replaceAll(\"a\", \"b\"); \
4315 const sp = \"a,b\".split(\",\"); \
4316 assert(sm === sm && \"a\".search(re) >= -1 && marr.length >= 0 && rep.length >= 0 && repall.length >= 0 && sp.length === 2); \
4317 assert(isNaN(NaN) && !isFinite(Infinity)); \
4318 const oo = { a: 1 }; \
4319 assert(Object.keys(oo).length === 1 && Object.values(oo).length === 1); \
4320 assert(Object.entries(oo).length === 1 && Object.hasOwn(oo, \"a\") && Object.is(1, 1)); }";
4321 let ta = type_check(src);
4322 let (prelude_defs, host_defs, internal_defs) =
4323 prelude::cached_runtime_package_declarations();
4324 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
4325 dependencies.extend(host_defs.iter());
4326 dependencies.extend(internal_defs.iter());
4327 let bytes = codegen(src, "script.subm", crate::FileId(0), &ta, &dependencies)
4328 .expect("code generation");
4329 let imp = imports_with_kind(&bytes);
4330
4331 for (iface, method) in [
4336 ("String", "repeat"),
4337 ("Array", "forEach"),
4338 ("SetConstructor", "new"),
4339 ("Set", "add"),
4340 ("Set", "has"),
4341 ("Set", "delete"),
4342 ("Set", "size"),
4343 ("Number", "toFixed"),
4344 ("Boolean", "toString"),
4345 ("NumberConstructor", "isInteger"),
4346 ("NumberConstructor", "parseInt"),
4347 ("NumberConstructor", "EPSILON"),
4349 ("Uint8Array", "fill"),
4351 ("Uint8Array", "length"),
4352 ("Uint8Array", "toHex"),
4353 ("Uint8ArrayConstructor", "fromHex"),
4354 ("TextEncoder", "encode"),
4356 ("TextDecoder", "decode"),
4357 ("TextEncoderConstructor", "new"),
4358 ("TextDecoderConstructor", "new"),
4359 ("RegExpConstructor", "new"),
4361 ("RegExp", "test"),
4362 ("RegExp", "exec"),
4363 ("RegExp", "source"),
4364 ("RegExp", "flags"),
4365 ("RegExp", "lastIndex"),
4366 ("RegExp", "global"),
4367 ("RegExpMatch", "match"),
4369 ("RegExpMatch", "index"),
4370 ("RegExpMatch", "input"),
4371 ("RegExpMatch", "groups"),
4372 ("RegExpMatch", "namedGroups"),
4373 ("String", "match"),
4375 ("String", "search"),
4376 ("String", "matchAll"),
4377 ("String", "replace"),
4378 ("String", "replaceAll"),
4379 ("String", "split"),
4380 ("ObjectConstructor", "keys"),
4382 ("ObjectConstructor", "values"),
4383 ("ObjectConstructor", "entries"),
4384 ("ObjectConstructor", "hasOwn"),
4385 ("ObjectConstructor", "is"),
4386 ] {
4387 let key = crate::mangle::extend(&crate::mangle::prelude(iface), method);
4388 assert!(
4389 imp.iter()
4390 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4391 && n == key.as_str()),
4392 "{iface}#{method} should import from the prelude host module"
4393 );
4394 }
4395
4396 for name in ["PI", "abs", "imul", "min", "random"] {
4397 let key = crate::runtime::prelude::math::math_key(name);
4398 assert!(
4399 imp.iter()
4400 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4401 && n == key.as_str()),
4402 "Math#{name} should import from the prelude host module"
4403 );
4404 }
4405
4406 for (iface, method) in [
4409 ("InstantConstructor", "from"),
4410 ("InstantConstructor", "fromEpochMilliseconds"),
4411 ("InstantConstructor", "fromEpochNanoseconds"),
4412 ("InstantConstructor", "compare"),
4413 ("Instant", "epochMilliseconds"),
4414 ("Instant", "epochNanoseconds"),
4415 ("Instant", "add"),
4416 ("Instant", "subtract"),
4417 ("Instant", "until"),
4418 ("Instant", "since"),
4419 ("Instant", "round"),
4420 ("Instant", "equals"),
4421 ("Instant", "toString"),
4422 ("Instant", "toJSON"),
4423 ("Instant", "toZonedDateTimeISO"),
4424 ("Now", "instant"),
4425 ("Now", "timeZoneId"),
4426 ("Now", "zonedDateTimeISO"),
4427 ("DurationConstructor", "new"),
4428 ("DurationConstructor", "from"),
4429 ("DurationConstructor", "compare"),
4430 ("Duration", "years"),
4431 ("Duration", "months"),
4432 ("Duration", "weeks"),
4433 ("Duration", "days"),
4434 ("Duration", "hours"),
4435 ("Duration", "minutes"),
4436 ("Duration", "seconds"),
4437 ("Duration", "milliseconds"),
4438 ("Duration", "microseconds"),
4439 ("Duration", "nanoseconds"),
4440 ("Duration", "sign"),
4441 ("Duration", "blank"),
4442 ("Duration", "add"),
4443 ("Duration", "subtract"),
4444 ("Duration", "negated"),
4445 ("Duration", "abs"),
4446 ("Duration", "with"),
4447 ("Duration", "round"),
4448 ("Duration", "total"),
4449 ("Duration", "toString"),
4450 ("Duration", "toJSON"),
4451 ("ZonedDateTimeConstructor", "from"),
4452 ("ZonedDateTimeConstructor", "compare"),
4453 ("ZonedDateTime", "timeZoneId"),
4454 ("ZonedDateTime", "offset"),
4455 ("ZonedDateTime", "offsetNanoseconds"),
4456 ("ZonedDateTime", "epochMilliseconds"),
4457 ("ZonedDateTime", "epochNanoseconds"),
4458 ("ZonedDateTime", "hoursInDay"),
4459 ("ZonedDateTime", "year"),
4460 ("ZonedDateTime", "monthCode"),
4461 ("ZonedDateTime", "dayOfWeek"),
4462 ("ZonedDateTime", "inLeapYear"),
4463 ("ZonedDateTime", "nanosecond"),
4464 ("ZonedDateTime", "add"),
4465 ("ZonedDateTime", "subtract"),
4466 ("ZonedDateTime", "until"),
4467 ("ZonedDateTime", "since"),
4468 ("ZonedDateTime", "with"),
4469 ("ZonedDateTime", "withTimeZone"),
4470 ("ZonedDateTime", "round"),
4471 ("ZonedDateTime", "startOfDay"),
4472 ("ZonedDateTime", "toInstant"),
4473 ("ZonedDateTime", "toPlainDate"),
4474 ("ZonedDateTime", "toPlainTime"),
4475 ("ZonedDateTime", "toPlainDateTime"),
4476 ("ZonedDateTime", "equals"),
4477 ("ZonedDateTime", "toString"),
4478 ("ZonedDateTime", "toJSON"),
4479 ("PlainDateConstructor", "from"),
4480 ("PlainDate", "year"),
4481 ("PlainDate", "toString"),
4482 ("PlainDate", "toJSON"),
4483 ("PlainDate", "add"),
4484 ("PlainDate", "subtract"),
4485 ("PlainDate", "until"),
4486 ("PlainDate", "since"),
4487 ("PlainDate", "with"),
4488 ("PlainDate", "toPlainYearMonth"),
4489 ("PlainDate", "toPlainMonthDay"),
4490 ("PlainDate", "toPlainDateTime"),
4491 ("PlainDate", "toZonedDateTime"),
4492 ("PlainTimeConstructor", "from"),
4493 ("PlainTime", "hour"),
4494 ("PlainTime", "nanosecond"),
4495 ("PlainTime", "add"),
4496 ("PlainTime", "until"),
4497 ("PlainTime", "with"),
4498 ("PlainDateTimeConstructor", "from"),
4499 ("PlainDateTime", "month"),
4500 ("PlainDateTime", "subtract"),
4501 ("PlainDateTime", "since"),
4502 ("PlainDateTime", "with"),
4503 ("PlainDateTime", "toPlainDate"),
4504 ("PlainDateTime", "toPlainTime"),
4505 ("PlainDateTime", "toPlainYearMonth"),
4506 ("PlainDateTime", "toPlainMonthDay"),
4507 ("PlainDateTime", "toZonedDateTime"),
4508 ("PlainDateTimeConstructor", "compare"),
4509 ("PlainYearMonthConstructor", "from"),
4510 ("PlainYearMonth", "monthCode"),
4511 ("PlainYearMonth", "add"),
4512 ("PlainYearMonth", "until"),
4513 ("PlainYearMonth", "with"),
4514 ("PlainYearMonth", "toPlainDate"),
4515 ("PlainMonthDayConstructor", "from"),
4516 ("PlainMonthDay", "day"),
4517 ("PlainMonthDay", "with"),
4518 ("PlainMonthDay", "toPlainDate"),
4519 ] {
4520 let key = crate::mangle::extend(
4521 &crate::mangle::extend(&crate::mangle::prelude("Temporal"), iface),
4522 method,
4523 );
4524 assert!(
4525 imp.iter()
4526 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4527 && n == key.as_str()),
4528 "Temporal#{iface}#{method} should import from the prelude host module"
4529 );
4530 }
4531
4532 for name in ["boxed_number_vtable", "boxed_boolean_vtable"] {
4534 let key = crate::mangle::prelude(name);
4535 assert!(
4536 imp.iter()
4537 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4538 && n == key.as_str()),
4539 "{name} should import from the prelude host module"
4540 );
4541 }
4542
4543 for name in ["isNaN", "isFinite", "Infinity"] {
4546 let key = crate::mangle::prelude(name);
4547 assert!(
4548 imp.iter()
4549 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4550 && n == key.as_str()),
4551 "globalThis {name} should import from the prelude host module"
4552 );
4553 }
4554 let unused = crate::mangle::extend(&crate::mangle::prelude("String"), "charAt");
4555 assert!(
4556 !imp.iter().any(|(_, n, _, _)| n == unused.as_str()),
4557 "unused ported methods should be tree-shaken",
4558 );
4559 }
4560
4561 #[test]
4562 fn global_imports_assigned_indices_starting_from_zero() {
4563 let defs = fake_globals_definitions();
4564 let ta = type_check("function main(): void { }");
4565 let prelude_defs = prelude::prelude_package_declaration();
4566 let prelude_host_defs = crate::runtime::prelude::package_declaration();
4567 let dependencies: [&crate::PackageDeclaration; 3] =
4568 [&prelude_defs, &prelude_host_defs, &defs];
4569 let _ = codegen(
4570 "function main(): void { }",
4571 "script.subm",
4572 crate::FileId(0),
4573 &ta,
4574 &dependencies,
4575 );
4576
4577 let mut map = SymbolTable::default();
4578 let mut next_global_idx: u32 = 0;
4579 for defs in [&prelude_defs, &defs] {
4580 for value in defs.values.values() {
4581 let ty = match &value.kind {
4582 crate::ValueKind::Let { ty, .. } | crate::ValueKind::Const { ty, .. } => {
4583 Some(ty)
4584 }
4585 _ => None,
4586 };
4587 if let Some(ty) = ty {
4588 if matches!(ty, crate::Type::InterfaceRef { .. }) {
4589 continue;
4590 }
4591 map.record_global(value.mangled_name.clone(), next_global_idx);
4592 next_global_idx += 1;
4593 }
4594 }
4595 }
4596 assert_eq!(map.prelude_global_idx("Infinity"), Some(0));
4597 assert_eq!(map.prelude_global_idx("NaN"), Some(1));
4598 assert_eq!(map.prelude_global_idx("string_comma"), Some(2));
4599 assert_eq!(map.prelude_global_idx("string_false"), Some(3));
4600 assert_eq!(map.prelude_global_idx("string_null"), Some(4));
4601 assert_eq!(map.prelude_global_idx("string_object_function"), Some(5));
4602 assert_eq!(map.prelude_global_idx("string_object_object"), Some(6));
4603 assert_eq!(map.prelude_global_idx("string_true"), Some(7));
4604 assert_eq!(
4605 map.global_idx(&crate::mangle::package_symbol("test:globals", "counter")),
4606 Some(8),
4607 );
4608 assert_eq!(
4609 map.global_idx(&crate::mangle::package_symbol(
4610 "test:globals",
4611 "max_iterations"
4612 )),
4613 Some(9),
4614 );
4615 assert_eq!(
4616 map.global_idx(&crate::mangle::package_symbol("test:globals", "missing")),
4617 None,
4618 );
4619 }
4620
4621 #[test]
4622 fn module_with_global_imports_validates() {
4623 let defs = fake_globals_definitions();
4624 let bytes = compile_with_package_imports("function main(): void { }", &[&defs]);
4625 wasmparser::Validator::new()
4626 .validate_all(&bytes)
4627 .expect("module with global imports validates");
4628 }
4629
4630 #[test]
4631 fn no_top_level_globals_still_emits_empty_start() {
4632 let bytes = compile("function main(): void { }");
4633 assert_eq!(
4634 start_function_idx(&bytes),
4635 Some(imported_func_count(&bytes))
4636 );
4637 assert_eq!(function_count(&bytes), 4);
4639 }
4640
4641 #[test]
4642 fn top_level_string_let_declares_one_mutable_ref_global() {
4643 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
4644 let baseline = compile("function main(): void { }");
4645 assert_eq!(global_count(&bytes), global_count(&baseline) + 1);
4646 assert!(
4647 global_decls(&bytes).contains(&(true, "ref")),
4648 "top-level string let should declare a mutable ref global",
4649 );
4650 }
4651
4652 #[test]
4653 fn top_level_const_also_declared_mutable_at_wasm_level() {
4654 let bytes = compile(r#"const x: string = "hi"; function main(): void { }"#);
4655 let baseline = compile("function main(): void { }");
4656 assert_eq!(global_count(&bytes), global_count(&baseline) + 1);
4657 assert!(
4658 global_decls(&bytes).contains(&(true, "ref")),
4659 "top-level string const should declare a mutable ref global",
4660 );
4661 }
4662
4663 #[test]
4664 fn top_level_let_emits_start_section_pointing_at_first_local_func() {
4665 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
4666 let first_local_func = imported_func_count(&bytes);
4667 assert_eq!(start_function_idx(&bytes), Some(first_local_func));
4668 assert_eq!(main_export_func_idx(&bytes), Some(first_local_func + 1));
4669 }
4670
4671 #[test]
4672 fn top_level_string_let_validates_and_links_with_prelude() {
4673 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
4674 instantiate_against_prelude(&bytes);
4675 }
4676
4677 #[test]
4678 fn arithmetic_precedence_returns_seven() {
4679 assert_eq!(
4680 run_main_f64("function main(): number { return 1 + 2 * 3; }"),
4681 7.0
4682 );
4683 }
4684
4685 #[test]
4686 fn arithmetic_subtraction_and_division() {
4687 assert_eq!(
4688 run_main_f64("function main(): number { return 10 - 4 / 2; }"),
4689 8.0
4690 );
4691 }
4692
4693 #[test]
4694 fn modulo_truncates_toward_zero() {
4695 assert_eq!(
4697 run_main_f64("function main(): number { return 10 % 3; }"),
4698 1.0
4699 );
4700 assert_eq!(
4701 run_main_f64("function main(): number { return -10 % 3; }"),
4702 -1.0
4703 );
4704 assert_eq!(
4705 run_main_f64("function main(): number { return 10 % -3; }"),
4706 1.0
4707 );
4708 }
4709
4710 #[test]
4711 fn division_by_zero_yields_infinity() {
4712 let r = run_main_f64("function main(): number { return 1 / 0; }");
4714 assert!(r.is_infinite() && r.is_sign_positive());
4715 }
4716
4717 #[test]
4718 fn zero_div_zero_yields_nan() {
4719 let r = run_main_f64("function main(): number { return 0 / 0; }");
4720 assert!(r.is_nan());
4721 }
4722
4723 #[test]
4724 fn unary_neg_negates_grouped_addition() {
4725 assert_eq!(
4726 run_main_f64("function main(): number { return -(2 + 3); }"),
4727 -5.0
4728 );
4729 }
4730
4731 #[test]
4732 fn unary_pos_is_identity() {
4733 assert_eq!(run_main_f64("function main(): number { return +5; }"), 5.0);
4734 }
4735
4736 #[test]
4737 fn nested_arithmetic_round_trip() {
4738 assert_eq!(
4739 run_main_f64("function main(): number { return (1 + 2) * (4 - 1); }"),
4740 9.0
4741 );
4742 }
4743
4744 #[test]
4745 fn top_level_let_referenced_from_main_body() {
4746 assert_eq!(
4747 run_main_f64("let x: number = 7; function main(): number { return x + 1; }"),
4748 8.0
4749 );
4750 }
4751
4752 #[test]
4753 fn while_counter_loop_sums_three_iterations() {
4754 let src = "function main(): number {\n let i = 0;\n let n = 0;\n while (i < 3) {\n n = n + i;\n i = i + 1;\n }\n return n;\n}";
4755 assert_eq!(run_main_f64(src), 3.0);
4756 }
4757
4758 #[test]
4759 fn while_zero_iterations_skips_body() {
4760 let src = "function main(): number {\n let i = 0;\n while (i < 0) {\n i = i + 1;\n }\n return i;\n}";
4761 assert_eq!(run_main_f64(src), 0.0);
4762 }
4763
4764 #[test]
4765 fn while_with_nested_if_counts_evens() {
4766 let src = "function main(): number {\n let i = 0;\n let evens = 0;\n while (i < 5) {\n if (i % 2 === 0) {\n evens = evens + 1;\n }\n i = i + 1;\n }\n return evens;\n}";
4767 assert_eq!(run_main_f64(src), 3.0);
4768 }
4769
4770 #[test]
4771 fn nested_while_loops_multiply_iterations() {
4772 let src = "function main(): number {\n let total = 0;\n let i = 0;\n while (i < 3) {\n let j = 0;\n while (j < 4) {\n total = total + 1;\n j = j + 1;\n }\n i = i + 1;\n }\n return total;\n}";
4773 assert_eq!(run_main_f64(src), 12.0);
4774 }
4775
4776 #[test]
4777 fn while_drives_top_level_global() {
4778 let src = "let g = 0; function main(): number {\n let i = 0;\n while (i < 4) {\n g = g + 1;\n i = i + 1;\n }\n return g;\n}";
4779 assert_eq!(run_main_f64(src), 4.0);
4780 }
4781
4782 #[test]
4783 fn if_true_takes_then_branch() {
4784 assert_eq!(
4785 run_main_f64("function main(): number { if (true) { return 1; } return 2; }"),
4786 1.0
4787 );
4788 }
4789
4790 #[test]
4791 fn if_false_skips_then_branch() {
4792 assert_eq!(
4793 run_main_f64("function main(): number { if (false) { return 1; } return 2; }"),
4794 2.0
4795 );
4796 }
4797
4798 #[test]
4799 fn if_else_picks_branch() {
4800 assert_eq!(
4801 run_main_f64("function main(): number { if (false) { return 1; } else { return 2; } }"),
4802 2.0
4803 );
4804 assert_eq!(
4805 run_main_f64("function main(): number { if (true) { return 1; } else { return 2; } }"),
4806 1.0
4807 );
4808 }
4809
4810 #[test]
4811 fn if_combines_with_assignment() {
4812 assert_eq!(
4813 run_main_f64(
4814 "function main(): number { let x: number = 0; if (1 < 2) { x = 1; } else { x = 2; } return x; }"
4815 ),
4816 1.0
4817 );
4818 }
4819
4820 #[test]
4821 fn else_if_chain_terminal_else() {
4822 let src = "function main(): number { let x: number = 3; if (x === 1) { return 10; } else if (x === 2) { return 20; } else { return 30; } }";
4823 assert_eq!(run_main_f64(src), 30.0);
4824 }
4825
4826 #[test]
4827 fn else_if_chain_middle_branch() {
4828 let src = "function main(): number { let x: number = 2; if (x === 1) { return 10; } else if (x === 2) { return 20; } else { return 30; } }";
4829 assert_eq!(run_main_f64(src), 20.0);
4830 }
4831
4832 #[test]
4833 fn nested_if_inside_if() {
4834 let src = "function main(): number { let x: number = 3; if (x > 0) { if (x > 2) { return 99; } return 11; } return 0; }";
4835 assert_eq!(run_main_f64(src), 99.0);
4836 }
4837
4838 #[test]
4839 fn function_local_let_round_trips_via_local() {
4840 assert_eq!(
4841 run_main_f64("function main(): number { let x: number = 1; return x; }"),
4842 1.0
4843 );
4844 }
4845
4846 #[test]
4847 fn function_local_assignment_overwrites_local() {
4848 assert_eq!(
4849 run_main_f64("function main(): number { let x: number = 1; x = 2; return x; }"),
4850 2.0
4851 );
4852 }
4853
4854 #[test]
4855 fn function_local_let_inferred_from_initializer() {
4856 assert_eq!(
4857 run_main_f64("function main(): number { let x = 5; return x + 1; }"),
4858 6.0
4859 );
4860 }
4861
4862 #[test]
4863 fn function_local_const_round_trips() {
4864 assert_eq!(
4865 run_main_f64("function main(): number { const c = 5; return c; }"),
4866 5.0
4867 );
4868 }
4869
4870 #[test]
4871 fn assignment_to_top_level_let_is_global_set() {
4872 assert_eq!(
4873 run_main_f64("let g = 1; function main(): number { g = 2; return g; }"),
4874 2.0
4875 );
4876 }
4877
4878 #[test]
4879 fn assignment_uses_self_reference_on_rhs() {
4880 assert_eq!(
4881 run_main_f64("function main(): number { let i = 0; i = i + 1; i = i + 1; return i; }"),
4882 2.0
4883 );
4884 }
4885
4886 #[test]
4887 fn const_reassignment_is_compile_error() {
4888 let mut asi = Asi::new(
4889 "function main(): number { const c = 5; c = 6; return c; }",
4890 crate::FileId(0),
4891 );
4892 let mut tokens: Vec<Token> = Vec::new();
4893 loop {
4894 let tok = asi.next_token();
4895 let is_eof = matches!(tok.kind, TokenKind::Eof);
4896 tokens.push(tok);
4897 if is_eof {
4898 break;
4899 }
4900 }
4901 let (ast, _) = parse(
4902 "function main(): number { const c = 5; c = 6; return c; }",
4903 tokens,
4904 crate::FileId(0),
4905 );
4906 let diags = infer_with_runtime_packages(
4907 "function main(): number { const c = 5; c = 6; return c; }",
4908 &ast,
4909 );
4910 assert!(
4911 diags
4912 .iter()
4913 .any(|d| d.message == "cannot assign to const binding `c`"),
4914 "expected const-reassignment diagnostic, got {diags:?}",
4915 );
4916 }
4917
4918 #[test]
4919 fn assignment_type_mismatch_is_compile_error() {
4920 let src = "function main(): number { let x: number = 1; x = 2; return x; }";
4921 let _ = compile(src);
4922
4923 let bad = r#"function main(): number { let x: number = 1; x = "hi"; return x; }"#;
4924 let mut asi = Asi::new(bad, crate::FileId(0));
4925 let mut tokens: Vec<Token> = Vec::new();
4926 loop {
4927 let tok = asi.next_token();
4928 let is_eof = matches!(tok.kind, TokenKind::Eof);
4929 tokens.push(tok);
4930 if is_eof {
4931 break;
4932 }
4933 }
4934 let (ast, _) = parse(bad, tokens, crate::FileId(0));
4935 let diags = infer_with_runtime_packages(bad, &ast);
4936 assert!(
4937 diags
4938 .iter()
4939 .any(|d| d.message.contains("expected `number`")),
4940 "expected type-mismatch diagnostic, got {diags:?}",
4941 );
4942 }
4943
4944 #[test]
4945 fn assignment_to_function_is_compile_error() {
4946 let bad = "function helper(): void { } function main(): void { helper = 1; }";
4947 let mut asi = Asi::new(bad, crate::FileId(0));
4948 let mut tokens: Vec<Token> = Vec::new();
4949 loop {
4950 let tok = asi.next_token();
4951 let is_eof = matches!(tok.kind, TokenKind::Eof);
4952 tokens.push(tok);
4953 if is_eof {
4954 break;
4955 }
4956 }
4957 let (ast, _) = parse(bad, tokens, crate::FileId(0));
4958 let diags = infer_with_runtime_packages(bad, &ast);
4959 assert!(
4960 diags
4961 .iter()
4962 .any(|d| d.message == "cannot assign to function `helper`"),
4963 "expected function-reassignment diagnostic, got {diags:?}",
4964 );
4965 }
4966
4967 #[test]
4968 fn boolean_literal_round_trips() {
4969 assert_eq!(run_main_i32("function main(): boolean { return true; }"), 1);
4970 assert_eq!(
4971 run_main_i32("function main(): boolean { return false; }"),
4972 0
4973 );
4974 }
4975
4976 #[test]
4977 fn numeric_comparisons() {
4978 assert_eq!(
4979 run_main_i32("function main(): boolean { return 1 < 2; }"),
4980 1
4981 );
4982 assert_eq!(
4983 run_main_i32("function main(): boolean { return 2 < 1; }"),
4984 0
4985 );
4986 assert_eq!(
4987 run_main_i32("function main(): boolean { return 2 > 1; }"),
4988 1
4989 );
4990 assert_eq!(
4991 run_main_i32("function main(): boolean { return 2 <= 2; }"),
4992 1
4993 );
4994 assert_eq!(
4995 run_main_i32("function main(): boolean { return 2 >= 3; }"),
4996 0
4997 );
4998 }
4999
5000 #[test]
5001 fn numeric_equality() {
5002 assert_eq!(
5003 run_main_i32("function main(): boolean { return 1 === 1; }"),
5004 1
5005 );
5006 assert_eq!(
5007 run_main_i32("function main(): boolean { const two: number = 2; return 1 !== two; }"),
5008 1
5009 );
5010 assert_eq!(
5011 run_main_i32("function main(): boolean { return 1 == 1; }"),
5012 1
5013 );
5014 assert_eq!(
5015 run_main_i32("function main(): boolean { return 1 != 1; }"),
5016 0
5017 );
5018 }
5019
5020 #[test]
5021 fn boolean_equality() {
5022 assert_eq!(
5023 run_main_i32("function main(): boolean { return true === true; }"),
5024 1
5025 );
5026 assert_eq!(
5027 run_main_i32(
5028 "function no(): boolean { return false; } function main(): boolean { return true !== no(); }"
5029 ),
5030 1
5031 );
5032 }
5033
5034 #[test]
5035 fn string_equality_via_runtime() {
5036 assert_eq!(
5037 run_main_i32(r#"function main(): boolean { return ("a" + "b") === "ab"; }"#),
5038 1
5039 );
5040 assert_eq!(
5041 run_main_i32(
5042 r#"function main(): boolean { const ac: string = "ac"; return "ab" !== ac; }"#
5043 ),
5044 1
5045 );
5046 assert_eq!(
5047 run_main_i32(
5048 r#"function main(): boolean { const ac: string = "ac"; return "ab" === ac; }"#
5049 ),
5050 0
5051 );
5052 }
5053
5054 #[test]
5055 fn unary_not() {
5056 assert_eq!(
5057 run_main_i32("function main(): boolean { return !true; }"),
5058 0
5059 );
5060 assert_eq!(
5061 run_main_i32("function main(): boolean { return !false; }"),
5062 1
5063 );
5064 assert_eq!(
5065 run_main_i32("function main(): boolean { return !!true; }"),
5066 1
5067 );
5068 }
5069
5070 #[test]
5071 fn logical_and_or_results() {
5072 assert_eq!(
5073 run_main_i32("function main(): boolean { return true && true; }"),
5074 1
5075 );
5076 assert_eq!(
5077 run_main_i32("function main(): boolean { return true && false; }"),
5078 0
5079 );
5080 assert_eq!(
5081 run_main_i32("function main(): boolean { return false || true; }"),
5082 1
5083 );
5084 assert_eq!(
5085 run_main_i32("function main(): boolean { return false || false; }"),
5086 0
5087 );
5088 }
5089
5090 #[test]
5091 fn logical_and_short_circuits_structurally() {
5092 let bytes = compile("function main(): boolean { return false && true; }");
5093 instantiate_against_prelude(&bytes);
5094 let main_idx = main_export_func_idx(&bytes).expect("main exported");
5095 let body_idx = (main_idx - imported_func_count(&bytes)) as usize;
5096 let mut bodies_seen = 0;
5097 let mut saw_if = false;
5098 for payload in Parser::new(0).parse_all(&bytes) {
5099 if let Payload::CodeSectionEntry(body) = payload.expect("payload") {
5100 if bodies_seen == body_idx {
5101 let mut reader = body
5102 .get_operators_reader()
5103 .expect("body operators readable");
5104 while !reader.eof() {
5105 if matches!(
5106 reader.read().expect("operator"),
5107 wasmparser::Operator::If { .. }
5108 ) {
5109 saw_if = true;
5110 break;
5111 }
5112 }
5113 break;
5114 }
5115 bodies_seen += 1;
5116 }
5117 }
5118 assert!(saw_if, "logical && should compile to a branchy `if` block");
5119 }
5120
5121 #[test]
5122 fn string_concat_compiles_and_links() {
5123 let bytes = compile(r#"function main(): string { return "a" + "b"; }"#);
5124 instantiate_against_prelude(&bytes);
5125
5126 let segments = data_segments(&bytes);
5127 assert_eq!(segments.len(), 2);
5128 assert_eq!(segments[0], b"a\0".to_vec());
5129 assert_eq!(segments[1], b"b\0".to_vec());
5130
5131 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5132 let calls = main_body_call_targets(&bytes);
5133 assert_eq!(
5134 calls,
5135 vec![concat_idx],
5136 "main should call string_concat exactly once",
5137 );
5138 }
5139
5140 #[test]
5141 fn string_concat_with_mixed_length_literals() {
5142 let bytes = compile(r#"function main(): string { return "Hello, " + "world"; }"#);
5143 instantiate_against_prelude(&bytes);
5144 assert_eq!(data_segments(&bytes).len(), 2);
5145 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5146 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx]);
5147 }
5148
5149 #[test]
5150 fn string_concat_chained_emits_two_calls() {
5151 let bytes = compile(r#"function main(): string { return "a" + "b" + "c"; }"#);
5152 instantiate_against_prelude(&bytes);
5153 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5154 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx, concat_idx]);
5155 }
5156
5157 #[test]
5158 fn string_concat_uses_top_level_global() {
5159 let bytes = compile(
5160 r#"let greeting: string = "Hello, "; function main(): string { return greeting + "world"; }"#,
5161 );
5162 instantiate_against_prelude(&bytes);
5163 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5164 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx]);
5165 }
5166
5167 #[test]
5168 fn user_global_idx_resolves_for_consumer_locals() {
5169 let mut map = SymbolTable::default();
5170 map.record_global(crate::mangle::package_symbol("main", "x"), 0);
5171 map.record_global(crate::mangle::package_symbol("main", "y"), 1);
5172 assert_eq!(
5173 map.global_idx(&crate::mangle::package_symbol("main", "x")),
5174 Some(0)
5175 );
5176 assert_eq!(
5177 map.global_idx(&crate::mangle::package_symbol("main", "y")),
5178 Some(1)
5179 );
5180 assert_eq!(
5181 map.global_idx(&crate::mangle::package_symbol("main", "missing")),
5182 None
5183 );
5184 }
5185
5186 #[test]
5187 fn dwarf_sections_emitted_and_validate() {
5188 let bytes = compile("function main(): void { }");
5189 assert!(!custom_section(&bytes, ".debug_info").unwrap().is_empty());
5190 assert!(!custom_section(&bytes, ".debug_abbrev").unwrap().is_empty());
5191 assert!(!custom_section(&bytes, ".debug_str").unwrap().is_empty());
5192 instantiate_against_prelude(&bytes);
5193 }
5194
5195 #[test]
5196 fn dwarf_subprogram_carries_main_name() {
5197 let bytes = compile("function main(): void { }");
5198 let strs = custom_section(&bytes, ".debug_str").unwrap();
5199 assert!(
5200 strs.windows(4).any(|w| w == b"main"),
5201 "expected `main` in .debug_str, got {strs:?}",
5202 );
5203 }
5204
5205 #[test]
5206 fn dwarf_names_the_start_function_as_the_top_level() {
5207 let bytes = compile("function main(): void { }");
5208 let strs = custom_section(&bytes, ".debug_str").unwrap();
5209 assert!(
5210 strs.windows(11).any(|w| w == b"<top level>"),
5211 "expected `<top level>` in .debug_str, got {strs:?}",
5212 );
5213 assert!(
5214 !strs.windows(6).any(|w| w == b"_start"),
5215 "_start should not appear in .debug_str",
5216 );
5217 }
5218
5219 #[test]
5220 fn dwarf_die_snapshot() {
5221 let bytes = compile("function main(): void { }");
5222 let dump = dump_dwarf_dies(&bytes);
5223 insta::assert_snapshot!(dump);
5224 }
5225
5226 #[test]
5227 fn forward_reference_call_resolves() {
5228 assert_eq!(
5229 run_main_f64(
5230 "function main(): number { return helper(); } \
5231 function helper(): number { return 42; }",
5232 ),
5233 42.0,
5234 );
5235 }
5236
5237 #[test]
5238 fn function_with_args_passes_through() {
5239 assert_eq!(
5240 run_main_f64(
5241 "function double(n: number): number { return n * 2; } \
5242 function main(): number { return double(21); }",
5243 ),
5244 42.0,
5245 );
5246 }
5247
5248 #[test]
5249 fn function_with_multiple_args_in_order() {
5250 assert_eq!(
5251 run_main_f64(
5252 "function sub(a: number, b: number): number { return a - b; } \
5253 function main(): number { return sub(10, 3); }",
5254 ),
5255 7.0,
5256 );
5257 }
5258
5259 #[test]
5260 fn nested_call_evaluates_inner_first() {
5261 assert_eq!(
5262 run_main_f64(
5263 "function inner(n: number): number { return n + 1; } \
5264 function outer(n: number): number { return n * 10; } \
5265 function main(): number { return outer(inner(5)); }",
5266 ),
5267 60.0,
5268 );
5269 }
5270
5271 #[test]
5272 fn recursion_works_via_self_call() {
5273 assert_eq!(
5274 run_main_f64(
5275 "function fact(n: number): number { \
5276 if (n <= 1) { return 1; } else { return n * fact(n - 1); } \
5277 } \
5278 function main(): number { return fact(5); }",
5279 ),
5280 120.0,
5281 );
5282 }
5283
5284 #[test]
5285 fn mvp_target_greeting_self_check() {
5286 let src = "\
5287function greet(name: string): string {\n\
5288 return \"Hello, \" + name;\n\
5289}\n\
5290function main(): boolean {\n\
5291 return greet(\"world\") === \"Hello, world\";\n\
5292}\n";
5293 assert_eq!(run_main_i32(src), 1);
5294 }
5295
5296 #[test]
5297 fn top_level_let_initializer_calls_user_function() {
5298 let src = "\
5299function compute(): number { return 7; }\n\
5300let cached: number = compute();\n\
5301function main(): number { return cached + 1; }\n";
5302 assert_eq!(run_main_f64(src), 8.0);
5303 }
5304
5305 #[test]
5306 fn dwarf_subprograms_for_each_user_function() {
5307 let bytes = compile("function main(): void { } function helper(): void { }");
5308 let dump = dump_dwarf_dies(&bytes);
5309 insta::assert_snapshot!(dump);
5310 }
5311
5312 #[test]
5313 fn dwarf_line_section_emitted() {
5314 let bytes = compile("function main(): void { }");
5315 assert!(
5316 !custom_section(&bytes, ".debug_line").unwrap().is_empty(),
5317 ".debug_line should be emitted",
5318 );
5319 instantiate_against_prelude(&bytes);
5320 }
5321
5322 #[test]
5323 fn dwarf_line_program_snapshot() {
5324 let src = "\
5325function main(): number {
5326 let x: number = 1;
5327 let y: number = 2;
5328 return x + y;
5329}
5330";
5331 let bytes = compile(src);
5332 let dump = dump_dwarf_lines(&bytes);
5333 insta::assert_snapshot!(dump);
5334 }
5335
5336 #[test]
5337 fn dwarf_line_program_covers_helper() {
5338 let src = "\
5339function main(): number {
5340 return helper();
5341}
5342function helper(): number {
5343 return 42;
5344}
5345";
5346 let bytes = compile(src);
5347 let dump = dump_dwarf_lines(&bytes);
5348 insta::assert_snapshot!(dump);
5349 }
5350
5351 #[test]
5352 fn assert_true_is_no_op() {
5353 assert_eq!(
5354 run_main_f64("function main(): number { assert(true, \"ok\"); return 1; }",),
5355 1.0,
5356 );
5357 }
5358
5359 #[test]
5360 fn assert_typechecks_with_string_message() {
5361 run_main_f64(
5362 "function main(): number { let m: string = \"ok\"; assert(true, m); return 0; }",
5363 );
5364 }
5365
5366 #[test]
5367 fn assert_evaluates_msg_for_side_effects() {
5368 assert_eq!(
5369 run_main_f64("function main(): number { assert(true, \"a\" + \"b\"); return 1; }",),
5370 1.0,
5371 );
5372 }
5373
5374 #[test]
5375 fn user_function_named_assert_is_rejected() {
5376 let src = "function assert(): void { } function main(): void { }";
5377 let mut asi = Asi::new(src, crate::FileId(0));
5378 let mut tokens: Vec<Token> = Vec::new();
5379 loop {
5380 let tok = asi.next_token();
5381 let is_eof = matches!(tok.kind, TokenKind::Eof);
5382 tokens.push(tok);
5383 if is_eof {
5384 break;
5385 }
5386 }
5387 let (ast, _) = parse(src, tokens, crate::FileId(0));
5388 let diags = infer_with_runtime_packages(src, &ast);
5389 assert!(
5390 diags.iter().any(|d| d.message
5391 == "`assert` is a reserved compiler intrinsic and cannot be redeclared"),
5392 "expected reserved-intrinsic diagnostic, got {diags:?}",
5393 );
5394 }
5395
5396 #[test]
5397 fn assert_false_throws_with_backtrace() {
5398 let dump = run_main_expecting_error("function main(): void { assert(false, \"oops\"); }");
5399 insta::assert_snapshot!(dump);
5400 }
5401
5402 #[test]
5403 fn multi_frame_backtrace() {
5404 let src = "\
5405function inner(): void { assert(false, \"x\"); }
5406function main(): void { inner(); }
5407";
5408 let dump = run_main_expecting_error(src);
5409 insta::assert_snapshot!(dump);
5410 }
5411
5412 #[test]
5413 fn three_frame_backtrace_labels_caller_in_middle() {
5414 let src = "\
5415function deepest(): void { assert(false, \"x\"); }
5416function middle(): void { deepest(); }
5417function main(): void { middle(); }
5418";
5419 let dump = run_main_expecting_error(src);
5420 insta::assert_snapshot!(dump);
5421 }
5422
5423 #[test]
5424 fn closure_method_accessor_and_constructor_frames_are_named() {
5425 let src = "\
5426class Refused { constructor() { assert(false, \"x\"); } }
5427class Holder {
5428 get refused(): Refused { return new Refused(); }
5429 read(): Refused { return this.refused; }
5430}
5431function main(): void {
5432 const named = function outer(): void {
5433 [1].forEach((n: number): void => { new Holder().read(); });
5434 };
5435 named();
5436}
5437";
5438 let dump = run_main_expecting_error(src);
5439 insta::assert_snapshot!(dump);
5440 }
5441
5442 #[test]
5443 fn implicit_constructor_frame_points_at_its_own_class() {
5444 let src = "\
5448class Base {
5449 constructor(n: number) { assert(n > 5, \"small\"); }
5450 describe(): string { return \"unrelated\"; }
5451}
5452class Derived extends Base {}
5453function main(): void { new Derived(1); }
5454";
5455 let dump = run_main_expecting_error(src);
5456 assert!(
5457 dump.contains(" at new Derived (script.subm:5:7) [caller]\n"),
5458 "{dump}"
5459 );
5460 }
5461
5462 #[test]
5463 fn a_method_named_like_an_accessor_is_not_framed_as_one() {
5464 let src = "\
5465class C { \"get x\"(): void { assert(false, \"x\"); } }
5466function main(): void { new C()[\"get x\"](); }
5467";
5468 let dump = run_main_expecting_error(src);
5469 assert!(dump.contains(" at C.get x (script.subm:1:"), "{dump}");
5470 }
5471
5472 #[test]
5473 fn a_method_key_with_control_characters_compiles_and_frames_escaped() {
5474 let src = "\
5475class C { \"a\\u0000\\nb\"(): void { assert(false, \"x\"); } }
5476function main(): void { new C()[\"a\\u0000\\nb\"](); }
5477";
5478 let dump = run_main_expecting_error(src);
5479 assert!(
5480 dump.contains(" at C.a\\u{0}\\nb (script.subm:1:"),
5481 "{dump}"
5482 );
5483 }
5484
5485 #[test]
5486 fn failed_cast_backtrace_points_at_the_cast() {
5487 let src = "\
5488interface P { a: number }
5489function g(n: number, p: P): number { return n; }
5490function main(): void {
5491 const r = g(1,
5492 JSON.parse(\"{}\") as P);
5493}
5494";
5495 let dump = run_main_expecting_error(src);
5496 insta::assert_snapshot!(dump);
5497 }
5498
5499 #[test]
5500 fn code_after_a_cast_keeps_its_backtrace_location() {
5501 let src = "\
5502interface P { a: number }
5503function g(n: number, p: P): number { assert(false, \"x\"); return n; }
5504function main(): void {
5505 const r = g(1, JSON.parse(\"{\\\"a\\\": 1}\") as P);
5506}
5507";
5508 let dump = run_main_expecting_error(src);
5509 insta::assert_snapshot!(dump);
5510 }
5511
5512 #[test]
5513 fn render_backtrace_returns_none_for_non_trap_error() {
5514 #[derive(Debug)]
5515 struct Plain;
5516 impl std::fmt::Display for Plain {
5517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5518 f.write_str("plain error")
5519 }
5520 }
5521 impl std::error::Error for Plain {}
5522 let err: wasmtime::Error = wasmtime::Error::new(Plain);
5523 let (sources, file) = crate::Sources::single("script.subm", "").unwrap();
5524 assert!(
5525 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full).is_none()
5526 );
5527 }
5528
5529 fn run_main_with_cfg_expecting_trap(
5530 cfg: &crate::runtime::RuntimeConfig,
5531 source: &str,
5532 ) -> String {
5533 let bytes = compile(source);
5534 let (mut store, inst) = link_consumer_with(cfg, &bytes);
5535 let main = inst
5536 .get_func(&mut store, "main")
5537 .expect("main signature is `() -> void` in expecting-trap fixtures");
5538 let err = pollster::block_on(main.call_async(
5539 &mut store,
5540 &[],
5541 &mut [wasmtime::Val::AnyRef(None)],
5542 ))
5543 .expect_err("expected main() to trap");
5544 let (sources, file) = crate::Sources::single("script.subm", source).unwrap();
5545 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full)
5546 .expect("backtrace empty — was wasm_backtrace_details enabled?")
5547 }
5548
5549 #[test]
5550 fn raw_trap_renders_an_error_header() {
5551 let cfg = crate::runtime::RuntimeConfig {
5552 max_wasm_stack: 64 * 1024,
5553 ..Default::default()
5554 };
5555 let dump = run_main_with_cfg_expecting_trap(
5556 &cfg,
5557 "function rec(): void { rec(); } function main(): void { rec(); }",
5558 );
5559 assert!(
5560 dump.starts_with("error: call stack exhausted\n"),
5561 "expected an `error:` header naming the trap, got:\n{dump}",
5562 );
5563 }
5564
5565 #[test]
5566 fn fuel_trap_header_does_not_leak_the_engine() {
5567 let cfg = crate::runtime::RuntimeConfig {
5570 fuel: 1024,
5571 ..Default::default()
5572 };
5573 let dump =
5574 run_main_with_cfg_expecting_trap(&cfg, "function main(): void { while (true) { } }");
5575 assert!(
5576 dump.starts_with("error: fuel exhausted\n"),
5577 "expected a curated `error:` header, got:\n{dump}",
5578 );
5579 assert!(
5580 !dump.contains("WebAssembly"),
5581 "engine wording leaked into the header:\n{dump}",
5582 );
5583 }
5584
5585 #[test]
5586 fn fuel_exhaustion_traps_with_label() {
5587 let cfg = crate::runtime::RuntimeConfig {
5588 fuel: 1024,
5589 ..Default::default()
5590 };
5591 let dump =
5592 run_main_with_cfg_expecting_trap(&cfg, "function main(): void { while (true) { } }");
5593 assert!(
5594 dump.contains("[fuel exhausted]"),
5595 "expected fuel-exhausted label, got:\n{dump}",
5596 );
5597 }
5598
5599 #[test]
5600 fn stack_overflow_traps_with_label() {
5601 let cfg = crate::runtime::RuntimeConfig {
5602 max_wasm_stack: 64 * 1024,
5603 ..Default::default()
5604 };
5605 let dump = run_main_with_cfg_expecting_trap(
5606 &cfg,
5607 "function rec(): void { rec(); } \
5608 function main(): void { rec(); }",
5609 );
5610 assert!(
5611 dump.contains("[stack overflow]"),
5612 "expected stack-overflow label, got:\n{dump}",
5613 );
5614 }
5615
5616 fn run_main_capturing_console(source: &str) -> String {
5617 use std::sync::{Arc, Mutex};
5618 struct Shared(Arc<Mutex<Vec<u8>>>);
5619 impl std::io::Write for Shared {
5620 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
5621 self.0.lock().unwrap().write(buf)
5622 }
5623 fn flush(&mut self) -> std::io::Result<()> {
5624 Ok(())
5625 }
5626 }
5627 let buf = Arc::new(Mutex::new(Vec::new()));
5628 let mut data = crate::runtime::StoreData::with_vfs(
5629 crate::runtime::Vfs::tempdir().expect("tempdir for codegen test VFS"),
5630 );
5631 data.console = Box::new(Shared(Arc::clone(&buf)));
5632 let cfg = crate::runtime::RuntimeConfig::default();
5633 let bytes = compile(source);
5634 let engine = cfg.engine().expect("engine");
5635 let mut store = cfg.store(&engine, data).expect("store");
5636 let consumer_module = wasmtime::Module::new(&engine, &bytes).expect("consumer module");
5637 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
5638 let inst = pollster::block_on(async {
5639 crate::runtime::install_runtime_async(&mut linker, &mut store)
5640 .await
5641 .expect("runtime");
5642 linker
5643 .instantiate_async(&mut store, &consumer_module)
5644 .await
5645 .expect("consumer instantiates")
5646 });
5647 let main = inst.get_func(&mut store, "main").expect("main () -> void");
5648 pollster::block_on(main.call_async(&mut store, &[], &mut [wasmtime::Val::AnyRef(None)]))
5649 .expect("main does not trap");
5650 let captured = buf.lock().unwrap().clone();
5651 String::from_utf8(captured).expect("utf8")
5652 }
5653
5654 #[test]
5655 fn console_log_writes_message_with_newline() {
5656 let out = run_main_capturing_console("function main(): void { console.log(\"hi\"); }");
5657 assert_eq!(out, "hi\n");
5658 }
5659
5660 #[test]
5661 fn console_log_uses_string_concat() {
5662 let out = run_main_capturing_console(
5663 "function main(): void { console.log(\"Hello, \" + \"world\"); }",
5664 );
5665 assert_eq!(out, "Hello, world\n");
5666 }
5667
5668 #[test]
5669 fn console_log_joins_multiple_primitive_args() {
5670 let out =
5671 run_main_capturing_console("function main(): void { console.log(\"a\", 1, true); }");
5672 assert_eq!(out, "a 1 true\n");
5673 }
5674
5675 #[test]
5676 fn console_log_coerces_array_and_object_args() {
5677 let out =
5678 run_main_capturing_console("function main(): void { console.log([1, 2], { x: 1 }); }");
5679 assert_eq!(out, "1,2 [object Object]\n");
5680 }
5681
5682 #[test]
5683 fn console_log_accepts_single_non_string_arg() {
5684 let out = run_main_capturing_console(
5685 "function main(): void { const items = [1, 2, 3]; console.log(items.length); }",
5686 );
5687 assert_eq!(out, "3\n");
5688 }
5689
5690 #[test]
5691 fn console_log_multiple_calls_appends() {
5692 let out = run_main_capturing_console(
5693 "function main(): void { console.log(\"a\"); console.log(\"b\"); }",
5694 );
5695 assert_eq!(out, "a\nb\n");
5696 }
5697
5698 #[test]
5699 fn console_log_inside_function_call() {
5700 let out = run_main_capturing_console(
5701 "function shout(s: string): void { console.log(s); } \
5702 function main(): void { shout(\"hello\"); }",
5703 );
5704 assert_eq!(out, "hello\n");
5705 }
5706
5707 #[test]
5708 fn user_function_named_console_is_rejected() {
5709 let src = "function console(): void { } function main(): void { }";
5710 let mut asi = Asi::new(src, crate::FileId(0));
5711 let mut tokens: Vec<Token> = Vec::new();
5712 loop {
5713 let tok = asi.next_token();
5714 let is_eof = matches!(tok.kind, TokenKind::Eof);
5715 tokens.push(tok);
5716 if is_eof {
5717 break;
5718 }
5719 }
5720 let (ast, _) = parse(src, tokens, crate::FileId(0));
5721 let diags = infer_with_runtime_packages(src, &ast);
5722 assert!(
5723 diags
5724 .iter()
5725 .any(|d| d.message.contains("duplicate declaration of `console`")),
5726 "expected duplicate-declaration diagnostic, got {diags:?}",
5727 );
5728 }
5729
5730 #[test]
5731 fn object_literal_round_trip() {
5732 let result =
5733 run_main_f64("function main(): number { let p = { x: 1, y: 2 }; return p.x + p.y; }");
5734 assert_eq!(result, 3.0);
5735 }
5736
5737 #[test]
5738 fn object_literal_field_order_independent() {
5739 let result =
5740 run_main_f64("function main(): number { let p = { y: 10, x: 5 }; return p.y; }");
5741 assert_eq!(result, 10.0);
5742 }
5743
5744 #[test]
5745 fn object_with_string_field_round_trip() {
5746 let bytes = compile(
5747 r#"function main(): string { let p = { name: "hi", age: 30 }; return p.name; }"#,
5748 );
5749 let cfg = crate::runtime::RuntimeConfig::default();
5750 let result = pollster::block_on(cfg.run(&bytes)).expect("run");
5751 assert_eq!(result.value.as_deref(), Some("hi"));
5752 }
5753
5754 #[test]
5755 fn object_passed_through_function_signature() {
5756 let result = run_main_f64(
5757 "function take(p: { v: number }): number { return p.v; } function main(): number { return take({ v: 7 }); }",
5758 );
5759 assert_eq!(result, 7.0);
5760 }
5761
5762 #[test]
5763 fn array_of_numbers_round_trip() {
5764 let result = run_main_f64("function main(): number { let a = [1, 2, 3]; return a[1]; }");
5765 assert_eq!(result, 2.0);
5766 }
5767
5768 #[test]
5769 fn array_of_numbers_annotated() {
5770 let result =
5771 run_main_f64("function main(): number { let a: number[] = [42]; return a[0]; }");
5772 assert_eq!(result, 42.0);
5773 }
5774
5775 #[test]
5776 fn array_of_strings_no_boxing() {
5777 let bytes = compile(r#"function main(): string { let a = ["a", "b", "c"]; return a[2]; }"#);
5778 let cfg = crate::runtime::RuntimeConfig::default();
5779 let result = pollster::block_on(cfg.run(&bytes)).expect("run");
5780 assert_eq!(result.value.as_deref(), Some("c"));
5781 }
5782
5783 #[test]
5784 fn array_of_objects_field_access() {
5785 let result = run_main_f64(
5786 "function main(): number { let xs: { v: number }[] = [{ v: 7 }]; return xs[0].v; }",
5787 );
5788 assert_eq!(result, 7.0);
5789 }
5790
5791 #[test]
5792 fn array_index_out_of_bounds_throws_catchable() {
5793 let bytes = compile(
5796 r#"function main(): string {
5797 let a = [1, 2, 3];
5798 try {
5799 let _ = a[10];
5800 return "no throw";
5801 } catch (e: Error) {
5802 return e.message;
5803 }
5804 }"#,
5805 );
5806 let cfg = crate::runtime::RuntimeConfig::default();
5807 let result =
5808 pollster::block_on(cfg.run(&bytes)).expect("OOB throw is catchable, not a trap");
5809 assert_eq!(result.value.as_deref(), Some("index out of range"));
5810 }
5811
5812 #[test]
5813 fn array_index_out_of_bounds_uncaught_is_not_a_trap() {
5814 let bytes = compile("function main(): number { let a = [1, 2, 3]; return a[10]; }");
5815 let cfg = crate::runtime::RuntimeConfig::default();
5816 let err = pollster::block_on(cfg.run(&bytes)).expect_err("uncaught OOB throw still aborts");
5817 assert!(
5818 err.downcast_ref::<wasmtime::Trap>().is_none(),
5819 "OOB should surface as a thrown exception, not a Wasm trap: {err:#}",
5820 );
5821 }
5822
5823 use crate::codegen::function_emitter::stmt::emit_statement;
5825 use crate::{ClosureBody, ExprId, StmtId, TypedExprKind, TypedStmtKind};
5826 use wasm_encoder::HeapType;
5827 use wasm_encoder::Ieee64;
5828 use wasm_encoder::Instruction;
5829 use wasm_encoder::{
5830 CodeSection, ConstExpr, ExportKind, ExportSection, Function, FunctionSection,
5831 GlobalSection, ImportSection, Module, RefType, TypeSection, ValType,
5832 };
5833
5834 fn boxed_typed_ast(source: &str) -> TypedAst {
5835 let mut asi = Asi::new(source, crate::FileId(0));
5836 let mut tokens: Vec<Token> = Vec::new();
5837 loop {
5838 let tok = asi.next_token();
5839 let is_eof = matches!(tok.kind, TokenKind::Eof);
5840 tokens.push(tok);
5841 if is_eof {
5842 break;
5843 }
5844 }
5845 let lex_diags = asi.into_diagnostics();
5846 assert!(lex_diags.is_empty(), "{lex_diags:?}");
5847 let (ast, parse_diags) = parse(source, tokens, crate::FileId(0));
5848 assert!(parse_diags.is_empty(), "{parse_diags:?}");
5849 let packages = runtime_packages(&[]);
5850 let (mut ta, infer_diags) = infer(source, "main", &ast, &packages);
5851 assert!(infer_diags.is_empty(), "{infer_diags:?}");
5852 let chk = check(&ta).unwrap();
5853 assert!(chk.is_empty(), "{chk:?}");
5854 ta = capture(ta).unwrap();
5855 ta = desugar(ta, crate::FileId(0)).unwrap();
5856 ta
5857 }
5858
5859 fn box_named(ta: &mut TypedAst, names: &[&str]) {
5860 let func_meta: Vec<(usize, Vec<String>, StmtId)> = ta
5861 .functions
5862 .iter()
5863 .enumerate()
5864 .map(|(i, f)| {
5865 let names = f.params.iter().map(|p| p.name.name.clone()).collect();
5866 (i, names, f.body)
5867 })
5868 .collect();
5869 for (fi, param_names, body) in func_meta {
5870 for (pi, pname) in param_names.iter().enumerate() {
5871 if names.contains(&pname.as_str()) {
5872 ta.functions[fi].params[pi].boxed = true;
5873 }
5874 }
5875 box_walk_stmt(ta, body, names);
5876 }
5877 let stmt_ids: Vec<StmtId> = ta.top_level_statements.clone();
5878 for sid in stmt_ids {
5879 box_walk_stmt(ta, sid, names);
5880 }
5881 }
5882
5883 fn box_walk_stmt(ta: &mut TypedAst, id: StmtId, names: &[&str]) {
5884 let kind = ta.try_stmt(id).unwrap().kind.clone();
5885 match kind {
5886 TypedStmtKind::Block(stmts) => {
5887 for s in stmts {
5888 box_walk_stmt(ta, s, names);
5889 }
5890 }
5891 TypedStmtKind::Let { name, value, .. } => {
5892 if names.iter().any(|n| *n == name.name)
5893 && let TypedStmtKind::Let { boxed, .. } = &mut ta.try_stmt_mut(id).unwrap().kind
5894 {
5895 *boxed = true;
5896 }
5897 box_walk_expr(ta, value, names);
5898 }
5899 TypedStmtKind::Const { value, .. } => box_walk_expr(ta, value, names),
5900 TypedStmtKind::If {
5901 condition,
5902 then_block,
5903 else_block,
5904 } => {
5905 box_walk_expr(ta, condition, names);
5906 box_walk_stmt(ta, then_block, names);
5907 if let Some(eb) = else_block {
5908 box_walk_stmt(ta, eb, names);
5909 }
5910 }
5911 TypedStmtKind::While { condition, body } => {
5912 box_walk_expr(ta, condition, names);
5913 box_walk_stmt(ta, body, names);
5914 }
5915 TypedStmtKind::For {
5916 init,
5917 condition,
5918 update,
5919 body,
5920 } => {
5921 if let Some(i) = init {
5922 box_walk_stmt(ta, i, names);
5923 }
5924 if let Some(c) = condition {
5925 box_walk_expr(ta, c, names);
5926 }
5927 if let Some(u) = update {
5928 box_walk_stmt(ta, u, names);
5929 }
5930 box_walk_stmt(ta, body, names);
5931 }
5932 TypedStmtKind::ForOf { iter, body, .. } => {
5933 box_walk_expr(ta, iter, names);
5934 box_walk_stmt(ta, body, names);
5935 }
5936 TypedStmtKind::DoWhile { body, condition } => {
5937 box_walk_stmt(ta, body, names);
5938 box_walk_expr(ta, condition, names);
5939 }
5940 TypedStmtKind::Switch {
5941 discriminant,
5942 cases,
5943 default,
5944 ..
5945 } => {
5946 box_walk_expr(ta, discriminant, names);
5947 for comparison in cases
5948 .iter()
5949 .flat_map(crate::TypedSwitchCase::label_comparisons)
5950 {
5951 box_walk_expr(ta, comparison, names);
5952 }
5953 for case in cases {
5954 box_walk_stmt(ta, case.body, names);
5955 }
5956 if let Some(d) = default {
5957 box_walk_stmt(ta, d, names);
5958 }
5959 }
5960 TypedStmtKind::Break | TypedStmtKind::Continue | TypedStmtKind::ReboxLocal { .. } => {}
5961 TypedStmtKind::Return(value) => {
5962 if let Some(v) = value {
5963 box_walk_expr(ta, v, names);
5964 }
5965 }
5966 TypedStmtKind::Expr(e) => box_walk_expr(ta, e, names),
5967 TypedStmtKind::AssignLocal { ident, value, .. } => {
5968 if names.iter().any(|n| *n == ident.name)
5969 && let TypedStmtKind::AssignLocal { boxed, .. } =
5970 &mut ta.try_stmt_mut(id).unwrap().kind
5971 {
5972 *boxed = true;
5973 }
5974 box_walk_expr(ta, value, names);
5975 }
5976 TypedStmtKind::AssignGlobal { value, .. } => {
5977 box_walk_expr(ta, value, names);
5978 }
5979 TypedStmtKind::AssignField {
5980 receiver, value, ..
5981 } => {
5982 box_walk_expr(ta, receiver, names);
5983 box_walk_expr(ta, value, names);
5984 }
5985 TypedStmtKind::AssignIndex {
5986 receiver,
5987 index,
5988 value,
5989 ..
5990 } => {
5991 box_walk_expr(ta, receiver, names);
5992 box_walk_expr(ta, index, names);
5993 box_walk_expr(ta, value, names);
5994 }
5995 TypedStmtKind::NarrowRegion { source, body, .. } => {
5996 box_walk_expr(ta, source, names);
5997 box_walk_stmt(ta, body, names);
5998 }
5999 TypedStmtKind::Throw { value } => box_walk_expr(ta, value, names),
6000 TypedStmtKind::Try {
6001 body,
6002 catches,
6003 finally,
6004 } => {
6005 box_walk_stmt(ta, body, names);
6006 for (i, c) in catches.iter().enumerate() {
6007 if names.iter().any(|n| *n == c.binding.name)
6008 && let TypedStmtKind::Try { catches: cs, .. } =
6009 &mut ta.try_stmt_mut(id).unwrap().kind
6010 {
6011 cs[i].boxed = true;
6012 }
6013 box_walk_stmt(ta, c.body, names);
6014 }
6015 if let Some(f) = finally {
6016 box_walk_stmt(ta, f, names);
6017 }
6018 }
6019 }
6020 }
6021
6022 fn box_walk_expr(ta: &mut TypedAst, id: ExprId, names: &[&str]) {
6023 let kind = ta.try_expr(id).unwrap().kind.clone();
6024 match kind {
6025 TypedExprKind::LocalRef { ident, .. } => {
6026 if names.iter().any(|n| *n == ident.name)
6027 && let TypedExprKind::LocalRef { boxed, .. } =
6028 &mut ta.try_expr_mut(id).unwrap().kind
6029 {
6030 *boxed = true;
6031 }
6032 }
6033 TypedExprKind::LocalNarrowRef { .. } => {}
6034 TypedExprKind::Closure { params, body, .. } => {
6035 for (i, p) in params.iter().enumerate() {
6036 if names.iter().any(|n| *n == p.name.name)
6037 && let TypedExprKind::Closure { params, .. } =
6038 &mut ta.try_expr_mut(id).unwrap().kind
6039 {
6040 params[i].boxed = true;
6041 }
6042 }
6043 match body {
6044 ClosureBody::Expr(e) => box_walk_expr(ta, e, names),
6045 ClosureBody::Block(b) => box_walk_stmt(ta, b, names),
6046 }
6047 }
6048 TypedExprKind::Binary { lhs, rhs, .. } => {
6049 box_walk_expr(ta, lhs, names);
6050 box_walk_expr(ta, rhs, names);
6051 }
6052 TypedExprKind::Unary { operand, .. } => box_walk_expr(ta, operand, names),
6053 TypedExprKind::TypeofTag { value, .. } | TypedExprKind::InstanceOf { value, .. } => {
6054 box_walk_expr(ta, value, names);
6055 }
6056 TypedExprKind::Call { args, .. }
6057 | TypedExprKind::McpCall { args, .. }
6058 | TypedExprKind::SuperCtorCall { args, .. }
6059 | TypedExprKind::SuperMethodCall { args, .. } => {
6060 for a in args {
6061 box_walk_expr(ta, a, names);
6062 }
6063 }
6064 TypedExprKind::CallClosure { callee, args } => {
6065 box_walk_expr(ta, callee, names);
6066 for a in args {
6067 box_walk_expr(ta, a, names);
6068 }
6069 }
6070 TypedExprKind::GenericCall { args, .. } => {
6071 for a in args {
6072 box_walk_expr(ta, a.expr, names);
6073 }
6074 }
6075 TypedExprKind::MethodCall { receiver, args, .. } => {
6076 box_walk_expr(ta, receiver, names);
6077 for a in args {
6078 box_walk_expr(ta, a, names);
6079 }
6080 }
6081 TypedExprKind::GenericMethodCall { receiver, args, .. } => {
6082 box_walk_expr(ta, receiver, names);
6083 for a in args {
6084 box_walk_expr(ta, a.expr, names);
6085 }
6086 }
6087 TypedExprKind::IntrinsicCall { args, .. } => {
6088 for a in args {
6089 box_walk_expr(ta, a, names);
6090 }
6091 }
6092 TypedExprKind::ObjectLiteral { members, .. } => {
6093 for member in members {
6094 for expression in member.expressions() {
6095 box_walk_expr(ta, expression, names);
6096 }
6097 }
6098 }
6099 TypedExprKind::ArrayLiteral { elements, .. } => {
6100 for e in elements {
6101 box_walk_expr(ta, e.expr_id(), names);
6102 }
6103 }
6104 TypedExprKind::TupleLiteral { elements, .. } => {
6105 for e in elements {
6106 box_walk_expr(ta, e, names);
6107 }
6108 }
6109 TypedExprKind::FieldAccess { receiver, .. }
6110 | TypedExprKind::InterfacePropertyAccess { receiver, .. } => {
6111 box_walk_expr(ta, receiver, names);
6112 }
6113 TypedExprKind::IndexAccess { receiver, index } => {
6114 box_walk_expr(ta, receiver, names);
6115 box_walk_expr(ta, index, names);
6116 }
6117 TypedExprKind::Narrowed { source, inner, .. } => {
6118 box_walk_expr(ta, source, names);
6119 box_walk_expr(ta, inner, names);
6120 }
6121 TypedExprKind::Ternary { cond, then_, else_ } => {
6122 box_walk_expr(ta, cond, names);
6123 box_walk_expr(ta, then_, names);
6124 box_walk_expr(ta, else_, names);
6125 }
6126 TypedExprKind::NullishCoalesce { lhs, rhs } => {
6127 box_walk_expr(ta, lhs, names);
6128 box_walk_expr(ta, rhs, names);
6129 }
6130 TypedExprKind::OptionalChain { base, parts } => {
6131 box_walk_expr(ta, base, names);
6132 let to_walk: Vec<ExprId> = parts
6133 .iter()
6134 .flat_map(|p| match p {
6135 crate::TypedChainPart::Index { idx, .. } => vec![*idx],
6136 crate::TypedChainPart::Call { args, .. }
6137 | crate::TypedChainPart::MethodCall { args, .. } => args.clone(),
6138 crate::TypedChainPart::Field { .. }
6139 | crate::TypedChainPart::InterfaceProperty { .. }
6140 | crate::TypedChainPart::NonNull { .. } => Vec::new(),
6141 })
6142 .collect();
6143 for id in to_walk {
6144 box_walk_expr(ta, id, names);
6145 }
6146 }
6147 TypedExprKind::PostfixUnary { target, .. } => match target {
6148 crate::PostfixTarget::Local { ident, .. } => {
6149 if names.iter().any(|n| *n == ident.name)
6150 && let TypedExprKind::PostfixUnary {
6151 target: crate::PostfixTarget::Local { boxed, .. },
6152 ..
6153 } = &mut ta.try_expr_mut(id).unwrap().kind
6154 {
6155 *boxed = true;
6156 }
6157 }
6158 crate::PostfixTarget::Global { .. } => {}
6159 crate::PostfixTarget::Field { receiver, .. } => {
6160 box_walk_expr(ta, receiver, names);
6161 }
6162 crate::PostfixTarget::Index {
6163 receiver, index, ..
6164 } => {
6165 box_walk_expr(ta, receiver, names);
6166 box_walk_expr(ta, index, names);
6167 }
6168 },
6169 TypedExprKind::NonNullAssert { value } | TypedExprKind::Cast { value, .. } => {
6170 box_walk_expr(ta, value, names);
6171 }
6172 TypedExprKind::EffectThen { effect, result } => {
6173 box_walk_expr(ta, effect, names);
6174 box_walk_expr(ta, result, names);
6175 }
6176 TypedExprKind::Sequence { stmts, result } => {
6177 for stmt in stmts {
6178 box_walk_stmt(ta, stmt, names);
6179 }
6180 box_walk_expr(ta, result, names);
6181 }
6182 TypedExprKind::Number(_)
6183 | TypedExprKind::BigInt(_)
6184 | TypedExprKind::String(_)
6185 | TypedExprKind::Boolean(_)
6186 | TypedExprKind::Null
6187 | TypedExprKind::Undefined
6188 | TypedExprKind::This
6189 | TypedExprKind::Regex { .. }
6190 | TypedExprKind::GlobalRef { .. }
6191 | TypedExprKind::FunctionRef { .. }
6192 | TypedExprKind::NumberEnumMember { .. }
6193 | TypedExprKind::StringEnumMember { .. } => {}
6194 }
6195 }
6196
6197 fn compile_with_boxed(source: &str, names: &[&str]) -> Vec<u8> {
6198 let mut ta = boxed_typed_ast(source);
6199 box_named(&mut ta, names);
6200
6201 let (prelude_defs, host_defs, internal_defs) =
6202 prelude::cached_runtime_package_declarations();
6203 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
6204 dependencies.extend(host_defs.iter());
6205 dependencies.extend(internal_defs.iter());
6208 codegen(source, "script.subm", crate::FileId(0), &ta, &dependencies)
6209 .expect("code generation")
6210 }
6211
6212 fn run_main_f64_boxed(source: &str, names: &[&str]) -> f64 {
6213 run_main_number(&compile_with_boxed(source, names))
6214 }
6215
6216 fn run_main_string_boxed(source: &str, names: &[&str]) -> String {
6217 let bytes = compile_with_boxed(source, names);
6218 let cfg = crate::runtime::RuntimeConfig::default();
6219 let result = pollster::block_on(cfg.run(&bytes)).expect("main does not trap");
6220 result.value.expect("main returns a string")
6221 }
6222
6223 #[test]
6224 fn boxed_let_number_round_trip() {
6225 let source = "function main(): number { let x: number = 1; x = x + 1; return x; }";
6226 let result = run_main_f64_boxed(source, &["x"]);
6227 assert_eq!(result, 2.0);
6228 }
6229
6230 #[test]
6231 fn boxed_let_string_round_trip() {
6232 let source =
6233 r#"function main(): string { let s: string = "hello"; s = "world"; return s; }"#;
6234 let result = run_main_string_boxed(source, &["s"]);
6235 assert_eq!(result, "world");
6236 }
6237
6238 #[test]
6239 fn boxed_let_user_object_round_trip() {
6240 let source = "
6241 function main(): number {
6242 let p: { v: number } = { v: 42 };
6243 p = { v: 99 };
6244 return p.v;
6245 }
6246 ";
6247 let result = run_main_f64_boxed(source, &["p"]);
6248 assert_eq!(result, 99.0);
6249 }
6250
6251 #[test]
6252 fn boxed_param_round_trip() {
6253 let source = "
6254 function helper(p: number): number { return p + 1; }
6255 function main(): number { return helper(5); }
6256 ";
6257 let result = run_main_f64_boxed(source, &["p"]);
6258 assert_eq!(result, 6.0);
6259 }
6260
6261 #[test]
6262 fn boxed_param_with_assignment() {
6263 let source = "
6264 function helper(p: number): number { p = p + 10; return p; }
6265 function main(): number { return helper(7); }
6266 ";
6267 let result = run_main_f64_boxed(source, &["p"]);
6268 assert_eq!(result, 17.0);
6269 }
6270
6271 #[test]
6272 fn mixed_boxed_and_unboxed_locals_in_one_function() {
6273 let source = "
6274 function main(): number {
6275 let b: number = 10;
6276 let u: number = 20;
6277 b = b + 1;
6278 u = u + 2;
6279 return b + u;
6280 }
6281 ";
6282 let result = run_main_f64_boxed(source, &["b"]);
6283 assert_eq!(result, 33.0);
6284 }
6285
6286 #[test]
6287 fn multiple_distinct_box_types_coexist() {
6288 let source = r#"
6289 function main(): string {
6290 let n: number = 1;
6291 let s: string = "x";
6292 n = n + 41;
6293 s = "answer";
6294 return s;
6295 }
6296 "#;
6297 let result = run_main_string_boxed(source, &["n", "s"]);
6298 assert_eq!(result, "answer");
6299 let mut ta = boxed_typed_ast(source);
6300 box_named(&mut ta, &["n", "s"]);
6301 let collected = super::box_types::collect(&ta, &mock_symbols_with_intrinsics()).unwrap();
6302 assert_eq!(collected.len(), 2);
6303 }
6304
6305 #[test]
6306 fn no_boxed_bindings_produces_no_box_types() {
6307 let ta = boxed_typed_ast("function main(): void { let x: number = 1; }");
6308 let collected = super::box_types::collect(&ta, &mock_symbols_with_intrinsics()).unwrap();
6309 assert!(collected.is_empty(), "{collected:?}");
6310 }
6311
6312 #[test]
6313 fn record_box_type_is_idempotent_per_value_type() {
6314 use crate::Type;
6315 use wasm_encoder::ValType;
6316 let mut symbols = SymbolTable::default();
6317 symbols.record_box_type(ValType::F64, 42);
6318 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(42));
6319 symbols.record_box_type(ValType::F64, 42);
6320 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(42));
6321 }
6322
6323 #[test]
6324 fn literal_typed_and_base_share_box_wrapper() {
6325 use crate::Type;
6326 use crate::types::LiteralF64;
6327 use wasm_encoder::ValType;
6328 let mut symbols = SymbolTable::default();
6329 symbols.record_box_type(ValType::F64, 7);
6330 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(7));
6331 assert_eq!(
6332 symbols
6333 .box_type_idx(&Type::NumberLiteral(LiteralF64(42.0)))
6334 .unwrap(),
6335 Some(7),
6336 );
6337 }
6338
6339 pub(super) fn mock_symbols_with_intrinsics() -> SymbolTable {
6340 let mut map = SymbolTable::default();
6341 map.set_intrinsic_type_indices(super::intrinsics::IntrinsicTypeIndices {
6342 raw_string: 0,
6343 vtable: 1,
6344 object: 2,
6345 undefined: 51,
6346 string: 3,
6347 boxed_number: 4,
6348 boxed_boolean: 5,
6349 field_names: 6,
6350 object_fields: 7,
6351 object_shape: 8,
6352 to_string_fn: 9,
6353 to_json_fn: 10,
6354 equals_fn: 11,
6355 hash_fn: 12,
6356 field_getter: 13,
6357 field_setter: 14,
6358 raw_array: 15,
6359 array: 16,
6360 raw_uint8_array: 17,
6361 uint8_array: 18,
6362 closure: 19,
6363 class_vtable: 20,
6364 error_vtable: 21,
6365 error: 22,
6366 raw_bigint: 23,
6367 bigint: 24,
6368 regex_capture_array: 25,
6369 regex_match: 26,
6370 regex: 27,
6371 regex_match_box: 28,
6372 temporal_instant: 29,
6373 temporal_duration: 30,
6374 temporal_zdt: 31,
6375 raw_index_array: 32,
6376 map: 33,
6377 set: 34,
6378 url: 35,
6379 fs_stat: 41,
6380 fs_peek: 42,
6381 fs_dir_entry: 43,
6382 fs_info: 44,
6383 fs_file_writer: 45,
6384 http_response: 46,
6385 http_download_result: 47,
6386 session_entry: 48,
6387 session_page: 49,
6388 fs_mount_info: 50,
6389
6390 temporal_plain_date: 36,
6391 temporal_plain_time: 37,
6392 temporal_plain_date_time: 38,
6393 temporal_plain_year_month: 39,
6394 temporal_plain_month_day: 40,
6395 });
6396 map
6397 }
6398
6399 fn compile_with_closures(source: &str) -> Vec<u8> {
6400 compile(source)
6401 }
6402
6403 #[test]
6404 fn closure_zero_captures_validates() {
6405 let source = "function main(): void { let f = (x: number) => x * 2; }";
6406 let bytes = compile_with_closures(source);
6407 instantiate_against_prelude(&bytes);
6408 }
6409
6410 #[test]
6411 fn closure_const_capture_validates() {
6412 let source = "
6413 function main(): void {
6414 const y: number = 5;
6415 let f = (x: number) => x + y;
6416 }
6417 ";
6418 let bytes = compile_with_closures(source);
6419 instantiate_against_prelude(&bytes);
6420 }
6421
6422 #[test]
6423 fn closure_let_capture_validates_with_box() {
6424 let source = "
6425 function main(): void {
6426 let y: number = 5;
6427 let f = (x: number) => x + y;
6428 }
6429 ";
6430 let bytes = compile_with_closures(source);
6431 instantiate_against_prelude(&bytes);
6432 }
6433
6434 #[test]
6435 fn closure_param_capture_validates_with_box() {
6436 let source = "
6437 function host(p: number): void {
6438 let f = (x: number) => p + x;
6439 }
6440 function main(): void { host(7); }
6441 ";
6442 let bytes = compile_with_closures(source);
6443 instantiate_against_prelude(&bytes);
6444 }
6445
6446 #[test]
6447 fn closure_block_body_with_assignment_validates() {
6448 let source = "
6449 function main(): void {
6450 let counter: number = 0;
6451 let inc = (): void => { counter = counter + 1; };
6452 }
6453 ";
6454 let bytes = compile_with_closures(source);
6455 instantiate_against_prelude(&bytes);
6456 }
6457
6458 #[test]
6459 fn two_arrows_same_signature_share_closure_struct() {
6460 let source = "
6461 function main(): void {
6462 let f = (x: number) => x + 1;
6463 let g = (y: number) => y * 2;
6464 }
6465 ";
6466 let ta = boxed_typed_ast(source);
6467 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6468 .unwrap()
6469 .closure_metas;
6470 assert_eq!(metas.len(), 2);
6471 assert_eq!(metas[0].signature, metas[1].signature);
6472 let bytes = compile_with_closures(source);
6473 instantiate_against_prelude(&bytes);
6474 }
6475
6476 #[test]
6477 fn nested_closure_validates() {
6478 let source = "
6479 function main(): void {
6480 let y: number = 1;
6481 let outer = (): number => {
6482 let inner = (x: number) => x + y;
6483 return 0;
6484 };
6485 }
6486 ";
6487 let bytes = compile_with_closures(source);
6488 instantiate_against_prelude(&bytes);
6489 }
6490
6491 #[test]
6492 fn function_typed_binding_lowers_to_closure_struct_ref() {
6493 let source = "function main(): void { let f = (x: number) => x; }";
6494 let bytes = compile_with_closures(source);
6495 instantiate_against_prelude(&bytes);
6496 }
6497
6498 #[test]
6499 fn no_closures_no_closure_types_emitted() {
6500 let ta = boxed_typed_ast("function main(): void { let x: number = 1; }");
6501 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6502 .unwrap()
6503 .closure_metas;
6504 assert!(metas.is_empty(), "{metas:?}");
6505 }
6506
6507 #[test]
6508 fn inline_arrow_call_returns_value() {
6509 let result = run_main_f64("function main(): number { return ((x: number) => x + 1)(5); }");
6510 assert_eq!(result, 6.0);
6511 }
6512
6513 #[test]
6514 fn let_stored_arrow_call_returns_value() {
6515 let result =
6516 run_main_f64("function main(): number { let f = (x: number) => x + 1; return f(5); }");
6517 assert_eq!(result, 6.0);
6518 }
6519
6520 #[test]
6521 fn closure_call_with_multiple_args() {
6522 let result = run_main_f64(
6523 "function main(): number { let add = (a: number, b: number) => a + b; return add(2, 3); }",
6524 );
6525 assert_eq!(result, 5.0);
6526 }
6527
6528 #[test]
6529 fn captured_let_counter_returns_three() {
6530 let result = run_main_f64(
6531 "function main(): number {
6532 let count: number = 0;
6533 let inc = (): number => { count = count + 1; return count; };
6534 inc();
6535 inc();
6536 return inc();
6537 }",
6538 );
6539 assert_eq!(result, 3.0);
6540 }
6541
6542 #[test]
6543 fn higher_order_function_calls_callback() {
6544 let result = run_main_f64(
6545 "function apply(f: (x: number) => number, x: number): number { return f(x); }
6546 function main(): number { return apply((n: number) => n + 10, 5); }",
6547 );
6548 assert_eq!(result, 15.0);
6549 }
6550
6551 #[test]
6552 fn function_as_value_let_bound_call_returns_underlying_result() {
6553 let result = run_main_f64(
6554 "function greet(x: number): number { return x + 1; }
6555 function main(): number { let f = greet; return f(2); }",
6556 );
6557 assert_eq!(result, 3.0);
6558 }
6559
6560 #[test]
6561 fn function_as_value_passed_to_hof_invokes_underlying() {
6562 let result = run_main_f64(
6563 "function greet(x: number): number { return x + 1; }
6564 function apply(f: (n: number) => number, n: number): number { return f(n); }
6565 function main(): number { return apply(greet, 5); }",
6566 );
6567 assert_eq!(result, 6.0);
6568 }
6569
6570 #[test]
6571 fn function_as_value_flows_through_multiple_bindings() {
6572 let result = run_main_f64(
6573 "function greet(x: number): number { return x + 10; }
6574 function main(): number { let f = greet; let g = f; return g(2); }",
6575 );
6576 assert_eq!(result, 12.0);
6577 }
6578
6579 #[test]
6580 fn direct_call_path_unchanged_when_function_also_used_as_value() {
6581 let result = run_main_f64(
6582 "function greet(x: number): number { return x + 1; }
6583 function apply(f: (n: number) => number, n: number): number { return f(n); }
6584 function main(): number {
6585 let direct = greet(10);
6586 let indirect = apply(greet, 20);
6587 return direct + indirect;
6588 }",
6589 );
6590 assert_eq!(result, 11.0 + 21.0);
6591 }
6592
6593 #[test]
6594 fn no_function_as_value_no_adapters_emitted() {
6595 let ta = boxed_typed_ast(
6596 "function greet(x: number): number { return x + 1; }
6597 function main(): void { greet(5); }",
6598 );
6599 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6600 .unwrap()
6601 .adapter_metas;
6602 assert!(metas.is_empty(), "{metas:?}");
6603 }
6604
6605 #[test]
6606 fn direct_callees_in_nested_positions_are_not_collected_as_adapters() {
6607 let ta = boxed_typed_ast(
6608 "function greet(x: number): number { return x + 1; }
6609 function nested(x: number): number { return x; }
6610 function inner(): number { return 1; }
6611 function outer(x: number): number { return x; }
6612 function main(): void {
6613 greet(5);
6614 nested(greet(5));
6615 outer(inner());
6616 }",
6617 );
6618 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6619 .unwrap()
6620 .adapter_metas;
6621 assert!(
6622 metas.is_empty(),
6623 "direct-only callsites should produce no adapters, got {metas:?}",
6624 );
6625 }
6626
6627 #[test]
6628 fn function_as_value_collects_one_adapter_per_distinct_function() {
6629 let ta = boxed_typed_ast(
6630 "function f1(x: number): number { return x + 1; }
6631 function f2(x: number): number { return x + 2; }
6632 function apply(g: (n: number) => number, n: number): number { return g(n); }
6633 function main(): void {
6634 let a = f1;
6635 let b = f1;
6636 apply(f2, 0);
6637 }",
6638 );
6639 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6640 .unwrap()
6641 .adapter_metas;
6642 assert_eq!(metas.len(), 2, "{metas:?}");
6643 let names: Vec<&str> = metas.iter().map(|m| m.name.as_str()).collect();
6644 assert!(names.contains(&"f1"));
6645 assert!(names.contains(&"f2"));
6646 }
6647
6648 #[test]
6649 fn higher_order_function_with_capture() {
6650 let result = run_main_f64(
6651 "function apply(f: (x: number) => number, x: number): number { return f(x); }
6652 function main(): number {
6653 let bias: number = 100;
6654 return apply((n: number) => n + bias, 7);
6655 }",
6656 );
6657 assert_eq!(result, 107.0);
6658 }
6659
6660 #[allow(dead_code)]
6661 fn _silence_unused() {
6662 let _ = (
6663 CodeSection::new(),
6664 ConstExpr::i32_const(0),
6665 ExportKind::Func,
6666 ExportSection::new(),
6667 FunctionSection::new(),
6668 GlobalSection::new(),
6669 ImportSection::new(),
6670 Module::new(),
6671 RefType {
6672 nullable: false,
6673 heap_type: HeapType::Concrete(0),
6674 },
6675 TypeSection::new(),
6676 ValType::F64,
6677 Function::new(vec![]),
6678 Ieee64::from(0.0_f64),
6679 Instruction::Nop,
6680 emit_statement,
6681 );
6682 }
6683}