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