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