1use std::collections::{BTreeMap, BTreeSet};
7use std::time::{Duration, Instant};
8
9use crate::compile_capabilities;
10use crate::compiler_error::{CompileError, CompilerFailure, CompilerStage};
11use crate::runtime::prelude;
12use crate::stdlib::Stdlib;
13use crate::typechecker::infer::{TestModule, TestModules};
14use crate::typechecker::rules::check_script;
15use crate::typed_ast::TypedAst;
16use crate::{
17 Asi, Ast, DerivedCapability, Diagnostic, FileId, ModulePath, PackageDeclaration, Severity,
18 Sources, StmtKind, Token, TokenKind, capture, desugar, lower_patterns, runtime,
19};
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
26pub struct PhaseTimings {
27 pub lex: Duration,
28 pub parse: Duration,
30 pub typecheck: Duration,
32 pub capture: Duration,
33 pub desugar: Duration,
34 pub codegen: Duration,
35}
36
37#[derive(Clone, Debug, Default, PartialEq, Eq)]
38pub struct ScriptImports {
39 pub stdlib: BTreeSet<String>,
40 pub registry_packages: BTreeSet<String>,
41 pub mcp_servers: BTreeSet<String>,
42 pub tools: bool,
44}
45
46#[derive(Clone, Debug)]
47pub struct ParsedScript {
48 file: FileId,
49 source: String,
50 failure: Option<CompileError>,
51 ast: Ast,
52 diagnostics: Vec<Diagnostic>,
53 timings: PhaseTimings,
54}
55
56impl ParsedScript {
57 pub fn external_imports(&self) -> Result<ScriptImports, CompileError> {
58 self.external_imports_with_stdlib(&runtime::stdlib_package_declarations())
59 }
60
61 pub fn external_imports_with_stdlib(
64 &self,
65 stdlib: &[PackageDeclaration],
66 ) -> Result<ScriptImports, CompileError> {
67 let stdlib_names: BTreeSet<&str> = stdlib
68 .iter()
69 .map(|defs| defs.package_name.as_str())
70 .collect();
71 let mut imports = ScriptImports::default();
72 for stmt_id in &self.ast.top_level {
73 let StmtKind::Import { module, .. } = &self
74 .ast
75 .try_stmt(*stmt_id)
76 .map_err(|error| {
77 CompileError::from(
78 error.into_compiler_failure(crate::compiler_error::CompilerStage::Parse),
79 )
80 .with_prior_diagnostics(&self.diagnostics)
81 })?
82 .kind
83 else {
84 continue;
85 };
86 if crate::source::is_relative_specifier(module) {
87 continue;
88 }
89 if let Some(server) = module.strip_prefix("@mcp/") {
90 imports.mcp_servers.insert(server.to_string());
91 } else if module == runtime::TOOLS_PACKAGE {
92 imports.tools = true;
93 } else if stdlib_names.contains(module.as_str())
94 || crate::stdlib::OptionalPackage::from_module_name(module).is_some()
95 {
96 imports.stdlib.insert(module.clone());
100 } else {
101 imports.registry_packages.insert(module.clone());
102 }
103 }
104 Ok(imports)
105 }
106
107 pub fn has_errors(&self) -> bool {
108 self.failure.is_some() || has_errors(&self.diagnostics)
109 }
110
111 pub fn diagnostics(&self) -> &[Diagnostic] {
112 &self.diagnostics
113 }
114}
115
116pub fn parse_script(source: &str, file: FileId) -> ParsedScript {
117 let mut timings = PhaseTimings::default();
118
119 let lex_start = Instant::now();
120 let mut asi = Asi::new(source, file);
121 let mut tokens: Vec<Token> = Vec::new();
122 loop {
123 let tok = asi.next_token();
124 let is_eof = matches!(tok.kind, TokenKind::Eof);
125 tokens.push(tok);
126 if is_eof {
127 break;
128 }
129 }
130 let (mut diagnostics, mut failure) = match asi.finish() {
131 Ok(diagnostics) => (diagnostics, None),
132 Err(error) => (error.clone().into_diagnostics(file), Some(error)),
133 };
134 timings.lex = lex_start.elapsed();
135
136 let parse_start = Instant::now();
137 let (mut ast, parse_diags) = if failure.is_some() {
138 (Ast::new(), Vec::new())
139 } else {
140 match crate::parser::parse_checked(source, tokens, file) {
141 Ok(result) => result,
142 Err(mut error) => {
143 error.diagnostics.splice(0..0, diagnostics.clone());
144 let rendered = error.clone().into_diagnostics(file);
145 diagnostics.clear();
146 failure = Some(error);
147 (Ast::new(), rendered)
148 }
149 }
150 };
151 diagnostics.extend(parse_diags);
152 if failure.is_none() {
154 match lower_patterns(ast) {
155 Ok(lowered) => ast = lowered,
156 Err(fatal) => {
157 let error = CompileError {
158 diagnostics: diagnostics.clone(),
159 fatal: Some(fatal),
160 };
161 diagnostics = error.clone().into_diagnostics(file);
162 failure = Some(error);
163 ast = Ast::new();
164 }
165 }
166 }
167 timings.parse = parse_start.elapsed();
168
169 ParsedScript {
170 file,
171 source: source.to_owned(),
172 failure,
173 ast,
174 diagnostics,
175 timings,
176 }
177}
178
179fn front_end(
188 source: &str,
189 parsed: &ParsedScript,
190 stdlib_defs: &[PackageDeclaration],
191 packages: &[&PackageDeclaration],
192) -> Result<(TypedAst, Vec<Diagnostic>, PhaseTimings), CompileError> {
193 front_end_with_transitive(None, source, parsed, stdlib_defs, packages, &[], None)
194}
195
196fn front_end_with_transitive(
197 owning_package: Option<&str>,
198 source: &str,
199 parsed: &ParsedScript,
200 stdlib_defs: &[PackageDeclaration],
201 packages: &[&PackageDeclaration],
202 transitive: &[&PackageDeclaration],
203 test_modules: Option<TestModules<'_>>,
204) -> Result<(TypedAst, Vec<Diagnostic>, PhaseTimings), CompileError> {
205 if source != parsed.source {
206 return Err(CompilerFailure::Internal {
207 stage: CompilerStage::Infer,
208 span: None,
209 message: "parsed script does not belong to the supplied source".into(),
210 }
211 .into());
212 }
213 if let Some(error) = &parsed.failure {
214 return Err(error.clone());
215 }
216 let mut timings = parsed.timings;
217 let mut diags = parsed.diagnostics.clone();
218 let typecheck_start = Instant::now();
219 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
220 let mut infer_refs: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
221 infer_refs.extend(host_defs.iter());
222 infer_refs.extend(stdlib_defs.iter());
223 infer_refs.extend_from_slice(packages);
224 let (ta, infer_diags) = crate::typechecker::infer::infer_with_transitive_for_owner_checked(
225 owning_package,
226 source,
227 crate::mangle::USER_PACKAGE,
228 &parsed.ast,
229 &infer_refs,
230 transitive,
231 test_modules,
232 )
233 .map_err(|mut error| {
234 error.diagnostics.splice(0..0, diags.clone());
235 error
236 })?;
237 diags.extend(infer_diags);
238 infer_refs.extend_from_slice(transitive);
239 diags.extend(
240 check_script(&ta, &infer_refs).map_err(|error| error.with_prior_diagnostics(&diags))?,
241 );
242 timings.typecheck = typecheck_start.elapsed();
243
244 Ok((ta, diags, timings))
245}
246
247#[derive(Clone, Debug)]
248pub struct PackageSourceModule<'a> {
249 pub path: ModulePath,
250 pub source: &'a str,
251}
252
253#[derive(Clone, Debug, PartialEq)]
254pub struct CompiledPackage {
255 pub wasm: Vec<u8>,
256 pub type_info: crate::TypeInfoTable,
257 pub declaration: PackageDeclaration,
258 pub required_capabilities: Vec<DerivedCapability>,
259 pub authority_map: crate::AuthorityMap,
260 pub warnings: Vec<Diagnostic>,
261 pub test_surface: Option<PackageTestSurface>,
264}
265
266#[derive(Clone, Copy, Debug, PartialEq, Eq)]
268pub enum PackageSurface {
269 Public,
271 WithTestModules,
280}
281
282#[derive(Clone, Debug, Default, PartialEq)]
286pub struct PackageTestSurface {
287 pub modules: BTreeMap<ModulePath, crate::InternalModuleExports>,
288}
289
290#[derive(Clone, Debug)]
291pub struct CompiledScript {
292 pub wasm: Vec<u8>,
293 pub type_info: crate::TypeInfoTable,
294 pub warnings: Vec<Diagnostic>,
295 pub timings: PhaseTimings,
296}
297
298pub fn typecheck_checked(source: &str, file: FileId) -> Result<Vec<Diagnostic>, CompileError> {
302 typecheck_script(source, file).map_err(|error| {
303 error.with_limit_span_cut(|span_file| (span_file == file).then_some(source))
304 })
305}
306
307fn typecheck_script(source: &str, file: FileId) -> Result<Vec<Diagnostic>, CompileError> {
308 let parsed = parse_script(source, file);
309 let stdlib_defs = runtime::stdlib_package_declarations();
310 typecheck_parsed_script(source, &parsed, &stdlib_defs)
311}
312
313pub fn typecheck_parsed_checked(
316 source: &str,
317 parsed: &ParsedScript,
318 stdlib_defs: &[PackageDeclaration],
319) -> Result<Vec<Diagnostic>, CompileError> {
320 let file = parsed.file;
321 typecheck_parsed_script(source, parsed, stdlib_defs).map_err(|error| {
322 error.with_limit_span_cut(|span_file| (span_file == file).then_some(source))
323 })
324}
325
326fn typecheck_parsed_script(
327 source: &str,
328 parsed: &ParsedScript,
329 stdlib_defs: &[PackageDeclaration],
330) -> Result<Vec<Diagnostic>, CompileError> {
331 let file = parsed.file;
332 let (ta, diags, _timings) = front_end(source, parsed, stdlib_defs, &[])?;
333 if has_errors(&diags) {
334 return Err(diags.into());
335 }
336 let with_front_end_diagnostics = |failure| CompileError {
339 diagnostics: diags.clone(),
340 fatal: Some(failure),
341 };
342 let ta = capture(ta).map_err(with_front_end_diagnostics)?;
343 desugar(ta, file).map_err(with_front_end_diagnostics)?;
344 Ok(warnings_only(diags))
345}
346
347pub fn typecheck_to_typed_ast(source: &str, file: FileId) -> (TypedAst, Vec<Diagnostic>) {
352 let parsed = parse_script(source, file);
353 let stdlib_defs = runtime::stdlib_package_declarations();
354 match front_end(source, &parsed, &stdlib_defs, &[]) {
355 Ok((ta, diags, _)) => (ta, diags),
356 Err(error) => (
357 TypedAst::new(),
358 error
359 .with_limit_span_cut(|span_file| (span_file == file).then_some(source))
360 .into_diagnostics(file),
361 ),
362 }
363}
364
365pub fn typecheck_package_to_typed_ast(
371 package_name: &str,
372 root_module: ModulePath,
373 modules: &[PackageSourceModule<'_>],
374) -> (TypedAst, Vec<Diagnostic>) {
375 let mut sources = Sources::new();
376 let mut diagnostics = Vec::new();
377 let Some(parsed_modules) = parse_modules_past_errors(&mut sources, modules, &mut diagnostics)
378 else {
379 return (TypedAst::new(), diagnostics);
380 };
381 let module_refs = parsed_modules
382 .iter()
383 .map(|(path, file, ast)| (path.clone(), *file, ast))
384 .collect();
385 let stdlib_defs = runtime::stdlib_package_declarations();
386 match crate::typechecker::infer::infer_package_checked(
387 package_name,
388 root_module,
389 module_refs,
390 &sources,
391 runtime_external_packages(&stdlib_defs),
392 BTreeMap::new(),
393 ) {
394 Ok((ta, _, package_diagnostics)) => {
395 diagnostics.extend(package_diagnostics);
396 (ta, diagnostics)
397 }
398 Err(error) => {
399 let error =
400 error.with_limit_span_cut(|file| sources.get(file).map(crate::SourceFile::text));
401 diagnostics.extend(error.into_diagnostics(FileId(0)));
402 (TypedAst::new(), diagnostics)
403 }
404 }
405}
406
407fn lower_recovered_module(
410 (ast, diagnostics): (Ast, Vec<Diagnostic>),
411) -> Result<(Ast, Vec<Diagnostic>), CompileError> {
412 if ast.lowered_local_types.is_some() {
413 return Ok((ast, diagnostics));
414 }
415 match lower_patterns(ast) {
416 Ok(ast) => Ok((ast, diagnostics)),
417 Err(fatal) => Err(CompileError {
418 diagnostics,
419 fatal: Some(fatal),
420 }),
421 }
422}
423
424fn parse_modules_past_errors(
428 sources: &mut Sources,
429 modules: &[PackageSourceModule<'_>],
430 diagnostics: &mut Vec<Diagnostic>,
431) -> Option<Vec<(ModulePath, FileId, Ast)>> {
432 let mut parsed_modules = Vec::with_capacity(modules.len());
433 for module in modules {
434 let file = match sources.add(module.path.clone(), module.source) {
435 Ok(file) => file,
436 Err(error) => {
437 let error = CompileError::from(error.into_compiler_failure(CompilerStage::Parse));
438 diagnostics.extend(error.into_diagnostics(FileId(0)));
439 return None;
440 }
441 };
442 let (ast, mut module_diagnostics) = match if module.path.as_str() == "config" {
444 parse_config_module(module.source, file, &BTreeMap::new())
445 } else {
446 parse_package_module(module.source, file).and_then(lower_recovered_module)
447 } {
448 Ok(parsed) => parsed,
449 Err(error) => {
450 diagnostics.extend(error.into_diagnostics(file));
451 return None;
452 }
453 };
454 diagnostics.append(&mut module_diagnostics);
455 parsed_modules.push((module.path.clone(), file, ast));
456 }
457 Some(parsed_modules)
458}
459
460fn runtime_external_packages(
463 stdlib_defs: &[PackageDeclaration],
464) -> BTreeMap<String, PackageDeclaration> {
465 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
466 stdlib_defs
467 .iter()
468 .chain(prelude_defs)
469 .chain(host_defs)
470 .map(|defs| (defs.package_name.clone(), defs.clone()))
471 .collect()
472}
473
474pub fn compile_script_checked(
484 source: &str,
485 filename: &str,
486 file: FileId,
487 packages: &[&PackageDeclaration],
488 mcps: &[&PackageDeclaration],
489) -> Result<CompiledScript, CompileError> {
490 let parsed = parse_script(source, file);
491 let stdlib_defs = runtime::stdlib_package_declarations();
492 compile_parsed_script_timed_checked(source, filename, &parsed, &stdlib_defs, packages, mcps)
493}
494
495pub fn compile_script_owned_by_checked(
500 owning_package: &str,
501 source: &str,
502 filename: &str,
503 file: FileId,
504 packages: &[&PackageDeclaration],
505 mcps: &[&PackageDeclaration],
506) -> Result<CompiledScript, CompileError> {
507 let parsed = parse_script(source, file);
508 let stdlib_defs = runtime::stdlib_package_declarations();
509 let declarations = [packages, mcps].concat();
510 compile_parsed_script_owned_by(
511 Some(owning_package),
512 source,
513 filename,
514 &parsed,
515 &stdlib_defs,
516 &declarations,
517 &[],
518 None,
519 )
520}
521
522pub fn compile_package_test_checked(
531 owner: &PackageDeclaration,
532 surface: &PackageTestSurface,
533 importer: &ModulePath,
534 source: &str,
535 filename: &str,
536 file: FileId,
537 packages: &[&PackageDeclaration],
538) -> Result<CompiledScript, CompileError> {
539 let modules: BTreeMap<ModulePath, TestModule> = surface
540 .modules
541 .iter()
542 .map(|(path, exports)| (path.clone(), test_module(owner, exports)))
543 .collect();
544 crate::typechecker::infer::check_declaration_types(
545 modules.values().map(|module| &module.declaration),
546 )?;
547 let codegen_owner = test_codegen_owner(owner, surface);
548 let mut declarations = packages.to_vec();
549 if !declarations
550 .iter()
551 .any(|declaration| declaration.package_name == owner.package_name)
552 {
553 declarations.push(owner);
554 }
555 let parsed = parse_script(source, file);
556 let stdlib_defs = runtime::stdlib_package_declarations();
557 compile_parsed_script_owned_by(
558 Some(&owner.package_name),
559 source,
560 filename,
561 &parsed,
562 &stdlib_defs,
563 &declarations,
564 &[],
565 Some(TestBuild {
566 modules: TestModules {
567 importer,
568 owner: &owner.package_name,
569 modules: &modules,
570 },
571 codegen_owner: &codegen_owner,
572 }),
573 )
574}
575
576#[derive(Clone, Copy)]
578struct TestBuild<'a> {
579 modules: TestModules<'a>,
580 codegen_owner: &'a PackageDeclaration,
585}
586
587fn test_module(owner: &PackageDeclaration, exports: &crate::InternalModuleExports) -> TestModule {
590 let mut declaration = PackageDeclaration::with_package(owner.package_name.clone());
591 declaration.values = exports.values.clone();
592 declaration.types = exports.types.clone();
593 declaration.refresh_shapes();
594 TestModule {
595 declaration,
596 unexported: exports.unexported.clone(),
597 }
598}
599
600fn test_codegen_owner(
605 owner: &PackageDeclaration,
606 surface: &PackageTestSurface,
607) -> PackageDeclaration {
608 let mut declaration = owner.clone();
609 let public_values: BTreeSet<&crate::MangledName> = owner
610 .values
611 .values()
612 .map(|value| &value.mangled_name)
613 .collect();
614 let public_types: BTreeSet<&crate::MangledName> = owner
615 .types
616 .values()
617 .map(|symbol| &symbol.mangled_name)
618 .collect();
619 for exports in surface.modules.values() {
620 for value in exports.values.values() {
621 if !public_values.contains(&value.mangled_name) {
622 declaration
623 .values
624 .entry(value.mangled_name.to_string())
625 .or_insert_with(|| value.clone());
626 }
627 }
628 for symbol in exports.types.values() {
629 if matches!(symbol.kind, crate::TypeKind::Class { .. })
630 && !public_types.contains(&symbol.mangled_name)
631 {
632 declaration
633 .types
634 .entry(symbol.mangled_name.to_string())
635 .or_insert_with(|| symbol.clone());
636 }
637 }
638 }
639 declaration.refresh_shapes();
640 declaration
641}
642
643pub fn compile_parsed_script_timed_checked(
644 source: &str,
645 filename: &str,
646 parsed: &ParsedScript,
647 stdlib_defs: &[PackageDeclaration],
648 packages: &[&PackageDeclaration],
649 mcps: &[&PackageDeclaration],
650) -> Result<CompiledScript, CompileError> {
651 compile_parsed_script_with_transitive_checked(
652 source,
653 filename,
654 parsed,
655 stdlib_defs,
656 packages,
657 mcps,
658 &[],
659 )
660}
661
662pub fn compile_parsed_script_with_transitive_checked(
665 source: &str,
666 filename: &str,
667 parsed: &ParsedScript,
668 stdlib_defs: &[PackageDeclaration],
669 packages: &[&PackageDeclaration],
670 mcps: &[&PackageDeclaration],
671 transitive: &[&PackageDeclaration],
672) -> Result<CompiledScript, CompileError> {
673 let declarations = [packages, mcps].concat();
674 compile_parsed_script_owned_by(
675 None,
676 source,
677 filename,
678 parsed,
679 stdlib_defs,
680 &declarations,
681 transitive,
682 None,
683 )
684}
685
686#[allow(clippy::too_many_arguments)]
687fn compile_parsed_script_owned_by(
688 owning_package: Option<&str>,
689 source: &str,
690 filename: &str,
691 parsed: &ParsedScript,
692 stdlib_defs: &[PackageDeclaration],
693 external_declarations: &[&PackageDeclaration],
694 transitive: &[&PackageDeclaration],
695 test: Option<TestBuild<'_>>,
696) -> Result<CompiledScript, CompileError> {
697 compile_parsed_script(
698 owning_package,
699 source,
700 filename,
701 parsed,
702 stdlib_defs,
703 external_declarations,
704 transitive,
705 test,
706 )
707 .map_err(|error| error.with_limit_span_cut(|file| (file == parsed.file).then_some(source)))
708}
709
710#[allow(clippy::too_many_arguments)]
711fn compile_parsed_script(
712 owning_package: Option<&str>,
713 source: &str,
714 filename: &str,
715 parsed: &ParsedScript,
716 stdlib_defs: &[PackageDeclaration],
717 external_declarations: &[&PackageDeclaration],
718 transitive: &[&PackageDeclaration],
719 test: Option<TestBuild<'_>>,
720) -> Result<CompiledScript, CompileError> {
721 let (mut ta, diags, mut timings) = front_end_with_transitive(
722 owning_package,
723 source,
724 parsed,
725 stdlib_defs,
726 external_declarations,
727 transitive,
728 test.map(|test| test.modules),
729 )?;
730 if has_errors(&diags) {
731 return Err(diags.into());
732 }
733 let capture_start = Instant::now();
734 ta = capture(ta).map_err(|fatal| CompileError {
735 diagnostics: diags.clone(),
736 fatal: Some(fatal),
737 })?;
738 timings.capture = capture_start.elapsed();
739
740 let desugar_start = Instant::now();
741 ta = desugar(ta, parsed.file).map_err(|fatal| CompileError {
742 diagnostics: diags.clone(),
743 fatal: Some(fatal),
744 })?;
745 timings.desugar = desugar_start.elapsed();
746
747 let (prelude_defs, host_defs, internal_defs) = prelude::cached_runtime_package_declarations();
748 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
749 for defs in host_defs {
750 dependencies.push(defs);
751 }
752 for defs in internal_defs {
753 dependencies.push(defs);
754 }
755 for defs in stdlib_defs {
756 dependencies.push(defs);
757 }
758 dependencies.extend(external_declarations.iter().map(|declaration| match test {
759 Some(test) if test.codegen_owner.package_name == declaration.package_name => {
760 test.codegen_owner
761 }
762 _ => *declaration,
763 }));
764 dependencies.extend_from_slice(transitive);
765
766 let codegen_start = Instant::now();
767 let generated = match owning_package {
768 Some(pkg) => {
769 crate::codegen::codegen_owned_by(pkg, source, filename, parsed.file, &ta, &dependencies)
770 }
771 None => crate::codegen::codegen_with_type_info(
772 source,
773 filename,
774 parsed.file,
775 &ta,
776 &dependencies,
777 ),
778 }
779 .map_err(|fatal| CompileError {
780 diagnostics: diags.clone(),
781 fatal: Some(fatal),
782 })?;
783 timings.codegen = codegen_start.elapsed();
784
785 Ok(CompiledScript {
786 wasm: generated.wasm,
787 type_info: generated.type_info,
788 warnings: warnings_only(diags),
789 timings,
790 })
791}
792
793pub fn compile_package_checked(
794 package_name: &str,
795 root_module: ModulePath,
796 modules: &[PackageSourceModule<'_>],
797 dependencies: &[&PackageDeclaration],
798) -> Result<CompiledPackage, CompileError> {
799 compile_package_with_transitive_checked(package_name, root_module, modules, dependencies, &[])
800}
801
802pub fn compile_package_with_transitive_checked(
814 package_name: &str,
815 root_module: ModulePath,
816 modules: &[PackageSourceModule<'_>],
817 dependencies: &[&PackageDeclaration],
818 transitive: &[&PackageDeclaration],
819) -> Result<CompiledPackage, CompileError> {
820 compile_package_with_transitive_checked_for(
821 Stdlib::core(),
822 package_name,
823 root_module,
824 modules,
825 dependencies,
826 transitive,
827 )
828}
829
830pub fn compile_package_with_transitive_checked_for(
833 stdlib: Stdlib,
834 package_name: &str,
835 root_module: ModulePath,
836 modules: &[PackageSourceModule<'_>],
837 dependencies: &[&PackageDeclaration],
838 transitive: &[&PackageDeclaration],
839) -> Result<CompiledPackage, CompileError> {
840 compile_package_configured(
841 stdlib,
842 package_name,
843 root_module,
844 modules,
845 dependencies,
846 transitive,
847 &BTreeMap::new(),
848 PackageSurface::Public,
849 )
850}
851
852#[allow(clippy::too_many_arguments)]
854pub fn compile_package_configured(
855 stdlib: Stdlib,
856 package_name: &str,
857 root_module: ModulePath,
858 modules: &[PackageSourceModule<'_>],
859 dependencies: &[&PackageDeclaration],
860 transitive: &[&PackageDeclaration],
861 config: &BTreeMap<String, String>,
862 surface: PackageSurface,
863) -> Result<CompiledPackage, CompileError> {
864 if !modules
865 .iter()
866 .any(|module| module.path.as_str() == "config")
867 {
868 crate::package_config::resolve(&[], config, FileId(0))?;
869 }
870 let mut sources = Sources::new();
871 let stdlib_defs = stdlib.package_declarations();
872 compile_package_sources(
873 &mut sources,
874 &stdlib_defs,
875 package_name,
876 root_module,
877 modules,
878 dependencies,
879 transitive,
880 config,
881 surface,
882 )
883 .map_err(|error| {
884 error.with_limit_span_cut(|file| sources.get(file).map(crate::SourceFile::text))
885 })
886}
887
888#[allow(clippy::too_many_arguments)]
889fn compile_package_sources(
890 sources: &mut Sources,
891 stdlib_defs: &[PackageDeclaration],
892 package_name: &str,
893 root_module: ModulePath,
894 modules: &[PackageSourceModule<'_>],
895 dependencies: &[&PackageDeclaration],
896 transitive: &[&PackageDeclaration],
897 config: &BTreeMap<String, String>,
898 surface: PackageSurface,
899) -> Result<CompiledPackage, CompileError> {
900 crate::typechecker::infer::check_declaration_types(
903 dependencies.iter().chain(transitive).copied(),
904 )?;
905 let mut parsed_modules = Vec::with_capacity(modules.len());
906 let mut diagnostics = Vec::new();
907 for module in modules {
908 let file = sources
909 .add(module.path.clone(), module.source)
910 .map_err(|error| {
911 CompileError::from(error.into_compiler_failure(CompilerStage::Parse))
912 .with_prior_diagnostics(&diagnostics)
913 })?;
914 let (ast, mut module_diags) = if module.path.as_str() == "config" {
915 parse_config_module(module.source, file, config)
916 } else {
917 parse_package_module(module.source, file)
918 }
919 .map_err(|mut error| {
920 error.diagnostics.splice(0..0, diagnostics.clone());
921 error
922 })?;
923 diagnostics.append(&mut module_diags);
924 parsed_modules.push((module.path.clone(), file, ast));
925 }
926 if has_errors(&diagnostics) {
927 return Err(diagnostics.into());
928 }
929
930 let module_refs: Vec<_> = parsed_modules
931 .iter()
932 .map(|(path, file, ast)| (path.clone(), *file, ast))
933 .collect();
934 let mut external_packages = runtime_external_packages(stdlib_defs);
935 for defs in dependencies {
936 external_packages.insert(defs.package_name.clone(), (*defs).clone());
937 }
938 let transitive_packages: BTreeMap<String, PackageDeclaration> = transitive
939 .iter()
940 .filter(|defs| !external_packages.contains_key(&defs.package_name))
941 .map(|defs| (defs.package_name.clone(), (*defs).clone()))
942 .collect();
943 let (mut ta, mut declaration, mut package_diags) =
944 crate::typechecker::infer::infer_package_checked(
945 package_name,
946 root_module.clone(),
947 module_refs,
948 sources,
949 external_packages,
950 transitive_packages,
951 )
952 .map_err(|mut error| {
953 error.diagnostics.splice(0..0, diagnostics.clone());
954 error
955 })?;
956 diagnostics.append(&mut package_diags);
957 if has_errors(&diagnostics) {
958 return Err(diagnostics.into());
959 }
960 let internal_modules = std::mem::take(&mut ta.internal_modules);
961 let test_surface = match surface {
962 PackageSurface::Public => None,
963 PackageSurface::WithTestModules => Some(own_test_surface(&ta, internal_modules)),
964 };
965 let (required_capabilities, capability_warnings) =
966 compile_capabilities::derive_package_requirements(
967 &declaration,
968 &ta,
969 stdlib_defs,
970 dependencies,
971 transitive,
972 )
973 .map_err(|error| error.with_prior_diagnostics(&diagnostics))?;
974 diagnostics.extend(capability_warnings);
975 let (prelude_defs, host_defs, internal_defs) = prelude::cached_runtime_package_declarations();
976 let authority_map = crate::authority::analyse(
977 &declaration,
978 &ta,
979 sources,
980 stdlib_defs
981 .iter()
982 .chain(prelude_defs)
983 .chain(host_defs)
984 .chain(dependencies.iter().copied())
985 .chain(transitive.iter().copied()),
986 )
987 .map_err(|fatal| CompileError {
988 diagnostics: diagnostics.clone(),
989 fatal: Some(fatal),
990 })?;
991 ta = capture(ta).map_err(|fatal| CompileError {
992 diagnostics: diagnostics.clone(),
993 fatal: Some(fatal),
994 })?;
995 let root_file = parsed_modules
996 .iter()
997 .find(|(path, _, _)| *path == root_module)
998 .map(|(_, file, _)| *file)
999 .ok_or_else(|| CompilerFailure::Internal {
1000 stage: CompilerStage::Infer,
1001 span: None,
1002 message: "compiled package root module is missing".into(),
1003 })?;
1004 ta = desugar(ta, root_file).map_err(|fatal| CompileError {
1005 diagnostics: diagnostics.clone(),
1006 fatal: Some(fatal),
1007 })?;
1008 if let Some(surface) = &test_surface {
1009 ta.test_exports = test_exports(surface, &ta.exports);
1010 }
1011
1012 let mut codegen_deps: Vec<&PackageDeclaration> = prelude_defs.iter().collect();
1013 codegen_deps.extend(host_defs.iter());
1014 codegen_deps.extend(internal_defs.iter());
1015 codegen_deps.extend(stdlib_defs.iter());
1016 codegen_deps.extend_from_slice(dependencies);
1017 codegen_deps.extend_from_slice(transitive);
1018 let generated =
1019 crate::codegen::codegen_package_with_type_info(sources, root_file, &ta, &codegen_deps)
1020 .map_err(|fatal| CompileError {
1021 diagnostics: diagnostics.clone(),
1022 fatal: Some(fatal),
1023 })?;
1024 declaration.runtime_functions = generated.runtime_functions;
1025 declaration.runtime_globals = generated.runtime_globals;
1026 declaration.closure_caches = generated.closure_caches;
1027 Ok(CompiledPackage {
1028 wasm: generated.wasm,
1029 type_info: generated.type_info,
1030 declaration,
1031 required_capabilities,
1032 authority_map,
1033 warnings: warnings_only(diagnostics),
1034 test_surface,
1035 })
1036}
1037
1038fn own_test_surface(
1041 ta: &TypedAst,
1042 modules: BTreeMap<ModulePath, crate::InternalModuleExports>,
1043) -> PackageTestSurface {
1044 let own_values: BTreeSet<&crate::MangledName> = ta
1045 .functions
1046 .iter()
1047 .map(|function| &function.mangled_name)
1048 .chain(ta.globals.iter().map(|global| &global.mangled_name))
1049 .collect();
1050 let own_classes: BTreeSet<&crate::MangledName> = ta
1051 .types
1052 .iter()
1053 .filter_map(|declaration| match declaration {
1054 crate::TypedTypeDecl::Class(class) => Some(&class.mangled_name),
1055 _ => None,
1056 })
1057 .collect();
1058 let modules = modules
1059 .into_iter()
1060 .map(|(path, mut exports)| {
1061 exports
1062 .values
1063 .retain(|_, value| own_values.contains(&value.mangled_name));
1064 exports.types.retain(|_, symbol| {
1065 !matches!(symbol.kind, crate::TypeKind::Class { .. })
1066 || own_classes.contains(&symbol.mangled_name)
1067 });
1068 (path, exports)
1069 })
1070 .collect();
1071 PackageTestSurface { modules }
1072}
1073
1074fn test_exports(
1079 surface: &PackageTestSurface,
1080 public: &[crate::ExportEntry],
1081) -> Vec<crate::ExportEntry> {
1082 let mut seen: BTreeSet<crate::MangledName> = public
1083 .iter()
1084 .map(|entry| entry.public_name.clone())
1085 .collect();
1086 let mut exports = Vec::new();
1087 for module in surface.modules.values() {
1088 for value in module.values.values() {
1089 exports.push(crate::ExportEntry {
1090 public_name: value.mangled_name.clone(),
1091 target: value.mangled_name.clone(),
1092 kind: match value.kind {
1093 crate::ValueKind::Function { .. } => crate::ExportKind::Function,
1094 crate::ValueKind::Let { .. } | crate::ValueKind::Const { .. } => {
1095 crate::ExportKind::Global
1096 }
1097 },
1098 span: value.declaration_span,
1099 });
1100 }
1101 for symbol in module.types.values() {
1102 exports.extend(crate::typechecker::infer::static_export_entries(
1103 &symbol.kind,
1104 &symbol.mangled_name,
1105 symbol.declaration_span,
1106 ));
1107 }
1108 }
1109 exports.retain(|entry| seen.insert(entry.public_name.clone()));
1110 exports
1111}
1112
1113fn parse_config_module(
1114 source: &str,
1115 file: FileId,
1116 config: &BTreeMap<String, String>,
1117) -> Result<(Ast, Vec<Diagnostic>), CompileError> {
1118 let tokens = crate::package_config::resolved_tokens(source, file, config)?;
1119 let (ast, diagnostics) = crate::parser::parse_checked(source, tokens, file)?;
1120 Ok((ast, diagnostics))
1121}
1122
1123fn parse_package_module(
1124 source: &str,
1125 file: FileId,
1126) -> Result<(crate::Ast, Vec<Diagnostic>), CompileError> {
1127 let mut asi = Asi::new(source, file);
1128 let mut tokens: Vec<Token> = Vec::new();
1129 loop {
1130 let tok = asi.next_token();
1131 let is_eof = matches!(tok.kind, TokenKind::Eof);
1132 tokens.push(tok);
1133 if is_eof {
1134 break;
1135 }
1136 }
1137 let mut diagnostics = asi.finish()?;
1138 let (mut ast, parse_diags) =
1139 crate::parser::parse_checked(source, tokens, file).map_err(|mut error| {
1140 error.diagnostics.splice(0..0, diagnostics.clone());
1141 error
1142 })?;
1143 diagnostics.extend(parse_diags);
1144 if !has_errors(&diagnostics) {
1145 ast = lower_patterns(ast).map_err(|fatal| CompileError {
1146 diagnostics: diagnostics.clone(),
1147 fatal: Some(fatal),
1148 })?;
1149 }
1150 Ok((ast, diagnostics))
1151}
1152
1153fn has_errors(diags: &[Diagnostic]) -> bool {
1154 diags.iter().any(|d| d.severity == Severity::Error)
1155}
1156
1157fn warnings_only(diags: Vec<Diagnostic>) -> Vec<Diagnostic> {
1158 diags
1159 .into_iter()
1160 .filter(|d| d.severity == Severity::Warning)
1161 .collect()
1162}
1163
1164pub fn typecheck(source: &str, file: FileId) -> Result<Vec<Diagnostic>, Vec<Diagnostic>> {
1166 typecheck_checked(source, file).map_err(|error| error.into_diagnostics(file))
1167}
1168
1169pub fn compile_script(
1171 source: &str,
1172 filename: &str,
1173 file: FileId,
1174 packages: &[&PackageDeclaration],
1175 mcps: &[&PackageDeclaration],
1176) -> Result<CompiledScript, Vec<Diagnostic>> {
1177 compile_script_checked(source, filename, file, packages, mcps)
1178 .map_err(|error| error.into_diagnostics(file))
1179}
1180
1181pub fn compile_script_owned_by(
1183 owning_package: &str,
1184 source: &str,
1185 filename: &str,
1186 file: FileId,
1187 packages: &[&PackageDeclaration],
1188 mcps: &[&PackageDeclaration],
1189) -> Result<CompiledScript, Vec<Diagnostic>> {
1190 compile_script_owned_by_checked(owning_package, source, filename, file, packages, mcps)
1191 .map_err(|error| error.into_diagnostics(file))
1192}
1193
1194pub fn compile_package_test(
1196 owner: &PackageDeclaration,
1197 surface: &PackageTestSurface,
1198 importer: &ModulePath,
1199 source: &str,
1200 filename: &str,
1201 file: FileId,
1202 packages: &[&PackageDeclaration],
1203) -> Result<CompiledScript, Vec<Diagnostic>> {
1204 compile_package_test_checked(owner, surface, importer, source, filename, file, packages)
1205 .map_err(|error| error.into_diagnostics(file))
1206}
1207
1208pub fn compile_parsed_script_timed(
1210 source: &str,
1211 filename: &str,
1212 parsed: &ParsedScript,
1213 stdlib_defs: &[PackageDeclaration],
1214 packages: &[&PackageDeclaration],
1215 mcps: &[&PackageDeclaration],
1216) -> Result<CompiledScript, Vec<Diagnostic>> {
1217 compile_parsed_script_timed_checked(source, filename, parsed, stdlib_defs, packages, mcps)
1218 .map_err(|error| error.into_diagnostics(parsed.file))
1219}
1220
1221pub fn compile_parsed_script_with_transitive(
1223 source: &str,
1224 filename: &str,
1225 parsed: &ParsedScript,
1226 stdlib_defs: &[PackageDeclaration],
1227 packages: &[&PackageDeclaration],
1228 mcps: &[&PackageDeclaration],
1229 transitive: &[&PackageDeclaration],
1230) -> Result<CompiledScript, Vec<Diagnostic>> {
1231 compile_parsed_script_with_transitive_checked(
1232 source,
1233 filename,
1234 parsed,
1235 stdlib_defs,
1236 packages,
1237 mcps,
1238 transitive,
1239 )
1240 .map_err(|error| error.into_diagnostics(parsed.file))
1241}
1242
1243pub fn compile_package(
1245 package_name: &str,
1246 root_module: ModulePath,
1247 modules: &[PackageSourceModule<'_>],
1248 dependencies: &[&PackageDeclaration],
1249) -> Result<CompiledPackage, Vec<Diagnostic>> {
1250 compile_package_checked(package_name, root_module, modules, dependencies)
1251 .map_err(|error| error.into_diagnostics(FileId(0)))
1252}
1253
1254pub fn compile_package_with_transitive(
1256 package_name: &str,
1257 root_module: ModulePath,
1258 modules: &[PackageSourceModule<'_>],
1259 dependencies: &[&PackageDeclaration],
1260 transitive: &[&PackageDeclaration],
1261) -> Result<CompiledPackage, Vec<Diagnostic>> {
1262 compile_package_with_transitive_checked(
1263 package_name,
1264 root_module,
1265 modules,
1266 dependencies,
1267 transitive,
1268 )
1269 .map_err(|error| error.into_diagnostics(FileId(0)))
1270}
1271
1272#[cfg(test)]
1273mod parsed_script_tests {
1274 use super::*;
1275
1276 #[test]
1277 fn a_package_build_reports_every_module_syntax_error() {
1278 crate::type_size::tests::on_compiler_stack(|| {
1279 let modules = [
1283 PackageSourceModule {
1284 path: ModulePath::from("lib"),
1285 source: "export function f(): void {\n \
1286 const x = g<number>(({ a }: { a: number }) => a;\n}\n",
1287 },
1288 PackageSourceModule {
1289 path: ModulePath::from("other"),
1290 source: "export const y = ;\n",
1291 },
1292 ];
1293 let error = compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1294 .expect_err("syntax errors");
1295 assert!(error.fatal.is_none(), "{error:?}");
1296 let files: std::collections::BTreeSet<u32> =
1297 error.diagnostics.iter().map(|d| d.span.file.0).collect();
1298 assert_eq!(files.len(), 2, "{:?}", error.diagnostics);
1299 });
1300 }
1301
1302 #[test]
1303 fn package_inference_past_syntax_errors_sees_lowered_modules() {
1304 crate::type_size::tests::on_compiler_stack(|| {
1305 let modules = [
1310 PackageSourceModule {
1311 path: ModulePath::from("lib"),
1312 source: "export function f([n]: number[]): number {\n \
1313 interface Row { n: number }\n const r: Row = { n };\n \
1314 return r.n;\n}\nconst broken = ;\n",
1315 },
1316 PackageSourceModule {
1317 path: ModulePath::from("other"),
1318 source: "const text: string = 1;\n",
1319 },
1320 ];
1321 let (_, diagnostics) =
1322 typecheck_package_to_typed_ast("test", ModulePath::from("lib"), &modules);
1323 let messages: Vec<&str> = diagnostics.iter().map(|d| d.message.as_str()).collect();
1324 assert!(
1325 messages.iter().any(|m| m.contains("expected expression")),
1326 "{messages:?}"
1327 );
1328 assert!(
1329 messages.iter().any(|m| m.contains("expected `string`")),
1330 "{messages:?}"
1331 );
1332 assert!(
1335 !messages
1336 .iter()
1337 .any(|m| m.contains("internal compiler failure")),
1338 "{messages:?}"
1339 );
1340 });
1341 }
1342
1343 #[test]
1344 fn typed_compile_errors_distinguish_source_limits_and_internal_failures() {
1345 let source_error = compile_script_checked(
1346 "function main(): number { return false; }",
1347 "test.ts",
1348 FileId(0),
1349 &[],
1350 &[],
1351 )
1352 .expect_err("source type mismatch");
1353 assert!(source_error.fatal.is_none());
1354 assert!(!source_error.diagnostics.is_empty());
1355
1356 let nested = format!(
1357 "function main(): number {{ return {}1{}; }}",
1358 "(".repeat(2048),
1359 ")".repeat(2048)
1360 );
1361 let limit = compile_script_checked(&nested, "test.ts", FileId(0), &[], &[])
1362 .expect_err("parser limit");
1363 assert!(matches!(
1364 limit.fatal,
1365 Some(CompilerFailure::Limit {
1366 stage: CompilerStage::Parse,
1367 ..
1368 })
1369 ));
1370 let rendered = limit.into_diagnostics(FileId(0));
1371 assert_eq!(
1372 rendered
1373 .iter()
1374 .filter(|d| d.message.contains("parser recursion limit exceeded"))
1375 .count(),
1376 1
1377 );
1378
1379 let parsed = parse_script("function main(): number { return 42; }", FileId(0));
1380 let internal = compile_parsed_script_timed_checked(
1381 "different source",
1382 "test.ts",
1383 &parsed,
1384 &[],
1385 &[],
1386 &[],
1387 )
1388 .expect_err("source metadata mismatch");
1389 assert!(matches!(
1390 internal.fatal,
1391 Some(CompilerFailure::Internal {
1392 stage: CompilerStage::Infer,
1393 ..
1394 })
1395 ));
1396 }
1397
1398 #[test]
1399 fn external_imports_come_from_parsed_imports_only() {
1400 let parsed = parse_script(
1401 r#"
1402 // import fake from "@mcp/commented";
1403 const s: string = "import x from \"@pied-piper/string\"";
1404 import { v4 } from "submilli:uuid";
1405 import linear from "@mcp/linear";
1406 import { answer } from "@pied-piper/util";
1407 function main(): string { return v4(); }
1408 "#,
1409 FileId(0),
1410 );
1411
1412 let imports = parsed.external_imports().unwrap();
1413
1414 assert_eq!(
1415 imports.stdlib,
1416 BTreeSet::from(["submilli:uuid".to_string()])
1417 );
1418 assert_eq!(imports.mcp_servers, BTreeSet::from(["linear".to_string()]));
1419 assert_eq!(
1420 imports.registry_packages,
1421 BTreeSet::from(["@pied-piper/util".to_string()])
1422 );
1423 }
1424
1425 #[test]
1426 fn relative_imports_are_not_external_imports() {
1427 let parsed = parse_script(
1428 r#"
1429 import { local } from "./util";
1430 function main(): number { return 1; }
1431 "#,
1432 FileId(0),
1433 );
1434
1435 assert_eq!(parsed.external_imports().unwrap(), ScriptImports::default());
1436 }
1437}
1438
1439#[cfg(test)]
1440mod importless_library_tests {
1441 use std::collections::BTreeMap;
1449
1450 use crate::mangle::package_symbol;
1451 use crate::package_declaration::{Dispatch, MethodSig, PropertySig, TypeKind, ValueKind};
1452 use crate::{ObjectField, Package, PackageDeclaration, Span, Type, TypeSymbol, ValueSymbol};
1453
1454 const SYNTH: &str = "submilli:synth";
1455 const PIED_PIPER: &str = "@pied-piper/util";
1456
1457 fn synth_package() -> PackageDeclaration {
1458 let mut defs = PackageDeclaration::with_package(SYNTH);
1459
1460 let mut properties = BTreeMap::new();
1461 properties.insert(
1462 "label".to_string(),
1463 PropertySig {
1464 ty: Type::String,
1465 readonly: true,
1466 intrinsic: false,
1467 optional: false,
1468 doc: None,
1469 },
1470 );
1471 let mut methods = BTreeMap::new();
1472 methods.insert(
1473 "size".to_string(),
1474 MethodSig {
1475 optional: false,
1476 generics: Default::default(),
1477 params: Vec::new(),
1478 ret: Type::Number,
1479 predicate: None,
1480 doc: None,
1481 },
1482 );
1483 defs.types.insert(
1484 "Widget".to_string(),
1485 TypeSymbol {
1486 name: "Widget".to_string(),
1487 mangled_name: package_symbol(SYNTH, "Widget"),
1488 declaration_span: Span::at(crate::FileId(0)),
1489 kind: TypeKind::Interface {
1490 index: None,
1491 generics: Default::default(),
1492 methods,
1493 properties,
1494 dispatch: Dispatch::Direct,
1495 doc: None,
1496 },
1497 },
1498 );
1499
1500 defs.types.insert(
1501 "Color".to_string(),
1502 TypeSymbol {
1503 name: "Color".to_string(),
1504 mangled_name: package_symbol(SYNTH, "Color"),
1505 declaration_span: Span::at(crate::FileId(0)),
1506 kind: TypeKind::NumberEnum {
1507 variants: vec![("Red".to_string(), 0.0), ("Green".to_string(), 1.0)],
1508 doc: None,
1509 },
1510 },
1511 );
1512
1513 let tree_ref = || Type::AliasRef {
1517 mangled: crate::mangle::package_symbol(SYNTH, "Tree"),
1518 package: Package(SYNTH.to_string()),
1519 name: "Tree".to_string(),
1520 args: Vec::new(),
1521 };
1522 let tree_body = || {
1523 let mut fields = BTreeMap::new();
1524 fields.insert("value".to_string(), ObjectField::required(Type::Number));
1525 fields.insert(
1526 "children".to_string(),
1527 ObjectField::required(Type::Array(Box::new(tree_ref()))),
1528 );
1529 Type::Object {
1530 index: None,
1531 fields,
1532 }
1533 };
1534 defs.types.insert(
1535 "Tree".to_string(),
1536 TypeSymbol {
1537 name: "Tree".to_string(),
1538 mangled_name: package_symbol(SYNTH, "Tree"),
1539 declaration_span: Span::at(crate::FileId(0)),
1540 kind: TypeKind::Alias {
1541 generics: Default::default(),
1542 ty: tree_body(),
1543 doc: None,
1544 },
1545 },
1546 );
1547
1548 let synth_ref = |name: &str, ty: Type| ValueSymbol {
1549 name: name.to_string(),
1550 mangled_name: package_symbol(SYNTH, name),
1551 declaration_span: Span::at(crate::FileId(0)),
1552 kind: ValueKind::Function {
1553 generics: Default::default(),
1554 params: Vec::new(),
1555 ret: ty,
1556 type_predicate: None,
1557 doc: None,
1558 },
1559 };
1560 defs.values.insert(
1561 "makeWidget".to_string(),
1562 synth_ref(
1563 "makeWidget",
1564 Type::InterfaceRef {
1565 mangled: crate::mangle::package_symbol(SYNTH, "Widget"),
1566 package: Package(SYNTH.to_string()),
1567 name: "Widget".to_string(),
1568 args: Vec::new(),
1569 },
1570 ),
1571 );
1572 defs.values.insert(
1573 "pickColor".to_string(),
1574 synth_ref(
1575 "pickColor",
1576 Type::NumberEnum {
1577 mangled: crate::mangle::package_symbol(SYNTH, "Color"),
1578 package: Package(SYNTH.to_string()),
1579 name: "Color".to_string(),
1580 member: None,
1581 },
1582 ),
1583 );
1584 defs.values.insert(
1585 "makeTree".to_string(),
1586 synth_ref(
1587 "makeTree",
1588 Type::Alias {
1589 mangled: crate::mangle::package_symbol(SYNTH, "Tree"),
1590 package: Package(SYNTH.to_string()),
1591 name: "Tree".to_string(),
1592 args: Vec::new(),
1593 ty: Box::new(tree_body()),
1594 },
1595 ),
1596 );
1597 defs
1598 }
1599
1600 fn pied_piper_package() -> PackageDeclaration {
1601 let mut defs = PackageDeclaration::with_package(PIED_PIPER);
1602 defs.values.insert(
1603 "greet".to_string(),
1604 ValueSymbol {
1605 name: "greet".to_string(),
1606 mangled_name: package_symbol(PIED_PIPER, "greet"),
1607 declaration_span: Span::at(crate::FileId(0)),
1608 kind: ValueKind::Function {
1609 generics: Default::default(),
1610 params: Vec::new(),
1611 ret: Type::String,
1612 type_predicate: None,
1613 doc: None,
1614 },
1615 },
1616 );
1617 defs
1618 }
1619
1620 fn errors(source: &str, packages: &[&PackageDeclaration]) -> Vec<String> {
1621 let parsed = super::parse_script(source, crate::FileId(0));
1622 let stdlib_defs = crate::runtime::stdlib_package_declarations();
1623 let (_ta, diags, _timings) =
1624 super::front_end(source, &parsed, &stdlib_defs, packages).expect("front end");
1625 diags
1626 .into_iter()
1627 .filter(|d| d.severity == crate::Severity::Error)
1628 .map(|d| d.message)
1629 .collect()
1630 }
1631
1632 fn compile_errors(source: &str, packages: &[&PackageDeclaration]) -> Vec<String> {
1633 super::compile_script(source, "main.subm", crate::FileId(0), packages, &[])
1634 .expect_err("expected compile error")
1635 .into_iter()
1636 .filter(|d| d.severity == crate::Severity::Error)
1637 .map(|d| d.message)
1638 .collect()
1639 }
1640
1641 #[test]
1642 fn package_declarations_passed_to_compile_are_importable() {
1643 let pied_piper = pied_piper_package();
1644 let source = r#"
1645 import { greet } from "@pied-piper/util";
1646 function main(): void {}
1647 "#;
1648 super::compile_script(source, "main.subm", crate::FileId(0), &[&pied_piper], &[])
1649 .expect("package compiles");
1650 }
1651
1652 #[test]
1653 fn package_not_passed_to_compile_uses_not_found_diagnostic() {
1654 let pied_piper = pied_piper_package();
1655 let source = r#"
1656 import { greet } from "@pied-piper/util";
1657 function main(): void {}
1658 "#;
1659 let _package_not_supplied = pied_piper;
1660 let errs = compile_errors(source, &[]);
1661 assert!(
1662 errs.iter()
1663 .any(|m| m.contains("package `@pied-piper/util` not found")),
1664 "expected not-found diagnostic, got: {errs:?}"
1665 );
1666 }
1667
1668 #[test]
1669 fn unknown_package_still_uses_not_found_diagnostic() {
1670 let source = r#"
1671 import { greet } from "@pied-piper/missing";
1672 function main(): void {}
1673 "#;
1674 let errs = compile_errors(source, &[]);
1675 assert!(
1676 errs.iter()
1677 .any(|m| m.contains("package `@pied-piper/missing` not found")),
1678 "expected not-found diagnostic, got: {errs:?}"
1679 );
1680 }
1681
1682 #[test]
1683 fn stdlib_imports_do_not_require_blueprint_packages() {
1684 let source = r#"
1685 import { v4 } from "submilli:uuid";
1686 function main(): void {}
1687 "#;
1688 super::compile_script(source, "main.subm", crate::FileId(0), &[], &[])
1689 .expect("stdlib package compiles without package declarations");
1690 }
1691
1692 #[test]
1693 fn importless_interface_member_access() {
1694 let synth = synth_package();
1695 let source = r#"
1697 import { makeWidget } from "submilli:synth";
1698 function main(): void {
1699 const w = makeWidget();
1700 assert(w.label === "x", "property read");
1701 const n: number = w.size();
1702 assert(n >= 0, "method call");
1703 }
1704 "#;
1705 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1706 }
1707
1708 #[test]
1709 fn importless_enum_value_use() {
1710 let synth = synth_package();
1711 let source = r#"
1713 import { pickColor } from "submilli:synth";
1714 function main(): void {
1715 const a = pickColor();
1716 const b = pickColor();
1717 assert(a === b, "enum values compare");
1718 }
1719 "#;
1720 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1721 }
1722
1723 #[test]
1724 fn importless_recursive_alias_deep_member_access() {
1725 let synth = synth_package();
1730 let source = r#"
1731 import { makeTree } from "submilli:synth";
1732 function main(): void {
1733 const t = makeTree();
1734 const root: number = t.value;
1735 assert(root >= 0, "root value");
1736 const child: number = t.children[0].value;
1737 assert(child >= 0, "deep recursive-alias value");
1738 const grand: number = t.children[0].children[0].value;
1739 assert(grand >= 0, "two levels deep");
1740 }
1741 "#;
1742 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1743 }
1744
1745 #[test]
1746 fn importless_recursive_alias_resolves_body_shape() {
1747 let synth = synth_package();
1752 let source = r#"
1753 import { makeTree } from "submilli:synth";
1754 function main(): void {
1755 const t = makeTree();
1756 const _x = t.children[0].nope;
1757 }
1758 "#;
1759 let errs = errors(source, &[&synth]);
1760 assert!(
1761 errs.iter().any(|m| m.contains("nope")),
1762 "expected a field-not-found error naming `nope`; got: {errs:?}"
1763 );
1764 }
1765
1766 #[test]
1767 fn unimported_type_name_still_unresolved_in_source() {
1768 let synth = synth_package();
1771 let source = r#"
1772 import { makeWidget } from "submilli:synth";
1773 function main(): void {
1774 const w: Widget = makeWidget();
1775 }
1776 "#;
1777 let errs = errors(source, &[&synth]);
1778 assert!(
1779 errs.iter().any(|m| m.contains("unknown type `Widget`")),
1780 "expected unknown-type error for unimported source annotation; got: {errs:?}"
1781 );
1782 }
1783}
1784
1785#[cfg(test)]
1786mod untyped_arena_failures {
1787 use super::*;
1788 use crate::arena::{ArenaKind, with_node_limit};
1789 use crate::compiler_error::{CompilerFailure, CompilerStage};
1790
1791 #[test]
1792 fn parser_arena_limit_is_fatal_and_keeps_prior_diagnostics() {
1793 let source = "let = 0; function main(): number { return 1; }";
1794 for arena in [ArenaKind::Expressions, ArenaKind::Statements] {
1795 let error = with_node_limit(arena, 0, || {
1796 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1797 })
1798 .unwrap_err();
1799 assert!(matches!(
1800 error.fatal,
1801 Some(CompilerFailure::Limit {
1802 stage: CompilerStage::Parse,
1803 span: None,
1804 ..
1805 })
1806 ));
1807 assert!(
1808 !error.diagnostics.is_empty(),
1809 "earlier syntax diagnostics must survive"
1810 );
1811 }
1812 assert!(
1813 !compile_script_checked(
1814 "function main(): number { return 1; }",
1815 "healthy.ts",
1816 FileId(0),
1817 &[],
1818 &[]
1819 )
1820 .unwrap()
1821 .wasm
1822 .is_empty()
1823 );
1824 }
1825
1826 #[test]
1827 fn lowering_arena_limit_stops_script_before_inference() {
1828 let source = "function main(): number { const [x] = [1]; return x; }";
1829 let mut lexer = crate::Asi::new(source, FileId(0));
1830 let mut tokens = Vec::new();
1831 loop {
1832 let token = lexer.next_token();
1833 let done = matches!(token.kind, crate::TokenKind::Eof);
1834 tokens.push(token);
1835 if done {
1836 break;
1837 }
1838 }
1839 let (ast, _) = crate::parser::parse_checked(source, tokens, FileId(0)).unwrap();
1840 for (arena, count) in [
1841 (ArenaKind::Expressions, ast.exprs_len()),
1842 (ArenaKind::Statements, ast.stmts_len()),
1843 ] {
1844 let error = with_node_limit(arena, u32::try_from(count).unwrap(), || {
1845 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1846 })
1847 .unwrap_err();
1848 assert!(matches!(
1849 error.fatal,
1850 Some(CompilerFailure::Limit {
1851 stage: CompilerStage::Infer,
1852 span: None,
1853 ..
1854 })
1855 ));
1856 }
1857 assert!(
1858 !compile_script_checked(source, "healthy.ts", FileId(0), &[], &[])
1859 .unwrap()
1860 .wasm
1861 .is_empty()
1862 );
1863 }
1864
1865 #[test]
1866 fn package_arena_limit_returns_no_artifact() {
1867 let modules = [PackageSourceModule {
1868 path: ModulePath::from("lib"),
1869 source: "export function value(): number { return 1; }",
1870 }];
1871 let error = with_node_limit(ArenaKind::Expressions, 0, || {
1872 compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1873 })
1874 .unwrap_err();
1875 assert!(matches!(
1876 error.fatal,
1877 Some(CompilerFailure::Limit {
1878 stage: CompilerStage::Parse,
1879 ..
1880 })
1881 ));
1882 compile_package_checked("test", ModulePath::from("lib"), &modules, &[]).unwrap();
1883 }
1884
1885 #[test]
1886 fn corrupt_parsed_imports_return_an_internal_failure() {
1887 let mut parsed = parse_script("function main(): number { return 1; }", FileId(0));
1888 parsed.ast.top_level.push(crate::StmtId(u32::MAX));
1889 let error = parsed.external_imports().unwrap_err();
1890 assert!(matches!(
1891 error.fatal,
1892 Some(CompilerFailure::Internal { span: None, .. })
1893 ));
1894 }
1895}
1896
1897#[cfg(test)]
1898mod typed_arena_failures {
1899 use super::*;
1900 use crate::arena::{ArenaKind, with_node_limit};
1901
1902 fn assert_limit(error: CompileError) {
1903 assert!(
1904 matches!(error.fatal, Some(CompilerFailure::Limit { span: None, .. })),
1905 "{error:?}"
1906 );
1907 }
1908
1909 #[test]
1910 fn typed_allocation_failure_stops_script_and_package_compilation() {
1911 let source = "function main(): number { return 1; }";
1912 let modules = [PackageSourceModule {
1913 path: ModulePath::from("lib"),
1914 source: "export function value(): number { return 1; }",
1915 }];
1916 for arena in [ArenaKind::TypedExpressions, ArenaKind::TypedStatements] {
1917 assert_limit(
1918 with_node_limit(arena, 0, || {
1919 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1920 })
1921 .unwrap_err(),
1922 );
1923 assert_limit(
1924 with_node_limit(arena, 0, || {
1925 compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1926 })
1927 .unwrap_err(),
1928 );
1929 }
1930 assert!(
1931 !compile_script_checked(source, "healthy.ts", FileId(0), &[], &[])
1932 .unwrap()
1933 .wasm
1934 .is_empty()
1935 );
1936 assert!(
1937 !compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1938 .unwrap()
1939 .wasm
1940 .is_empty()
1941 );
1942 }
1943
1944 #[test]
1945 fn typed_allocation_failure_keeps_prior_inference_diagnostics() {
1946 let source = "function main(): number { let value: number = \"wrong\"; return 2; }";
1947 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1948 assert!(!diagnostics.is_empty());
1949 for (arena, count) in [
1950 (ArenaKind::TypedExpressions, ast.exprs_len()),
1951 (ArenaKind::TypedStatements, ast.stmts_len()),
1952 ] {
1953 let error = with_node_limit(arena, u32::try_from(count - 1).unwrap(), || {
1954 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1955 })
1956 .unwrap_err();
1957 assert!(!error.diagnostics.is_empty(), "{error:?}");
1958 assert_limit(error);
1959 }
1960 }
1961
1962 #[test]
1963 fn codegen_allocation_failure_returns_no_wasm() {
1964 let source = "function main(): number { let value: number | null = 1; if (value !== null) { return value; } return 0; }";
1965 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1966 assert!(diagnostics.is_empty());
1967 let ast = desugar(capture(ast).unwrap(), FileId(0)).unwrap();
1968 let (prelude, host, internal) = prelude::cached_runtime_package_declarations();
1969 let dependencies: Vec<_> = prelude.iter().chain(host).chain(internal).collect();
1970 let failure = with_node_limit(
1971 ArenaKind::TypedExpressions,
1972 u32::try_from(ast.exprs_len()).unwrap(),
1973 || crate::codegen::codegen(source, "limit.ts", FileId(0), &ast, &dependencies),
1974 )
1975 .unwrap_err();
1976 assert!(
1977 matches!(
1978 failure,
1979 CompilerFailure::Limit {
1980 stage: CompilerStage::Codegen,
1981 span: None,
1982 ..
1983 }
1984 ),
1985 "{failure}"
1986 );
1987 assert!(
1988 !crate::codegen::codegen(source, "healthy.ts", FileId(0), &ast, &dependencies)
1989 .unwrap()
1990 .is_empty()
1991 );
1992 }
1993
1994 #[test]
1995 fn desugaring_capacity_failure_does_not_return_a_transformed_tree() {
1996 let source = "function main(): void { do {} while (false); }";
1997 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1998 assert!(diagnostics.is_empty());
1999 for (arena, count) in [
2000 (ArenaKind::TypedExpressions, ast.exprs_len()),
2001 (ArenaKind::TypedStatements, ast.stmts_len()),
2002 ] {
2003 let failure = with_node_limit(arena, u32::try_from(count).unwrap(), || {
2004 desugar(ast.clone(), FileId(0))
2005 })
2006 .unwrap_err();
2007 assert_limit(failure.into());
2008 }
2009 desugar(ast, FileId(0)).unwrap();
2010 }
2011}