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::rules::check_script;
14use crate::typed_ast::TypedAst;
15use crate::{
16 Asi, Ast, DerivedCapability, Diagnostic, FileId, ModulePath, PackageDeclaration, Severity,
17 Sources, StmtKind, Token, TokenKind, capture, desugar, lower_patterns, runtime,
18};
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct PhaseTimings {
26 pub lex: Duration,
27 pub parse: Duration,
29 pub typecheck: Duration,
31 pub capture: Duration,
32 pub desugar: Duration,
33 pub codegen: Duration,
34}
35
36#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct ScriptImports {
38 pub stdlib: BTreeSet<String>,
39 pub registry_packages: BTreeSet<String>,
40 pub mcp_servers: BTreeSet<String>,
41}
42
43#[derive(Clone, Debug)]
44pub struct ParsedScript {
45 file: FileId,
46 source: String,
47 failure: Option<CompileError>,
48 ast: Ast,
49 diagnostics: Vec<Diagnostic>,
50 timings: PhaseTimings,
51}
52
53impl ParsedScript {
54 pub fn external_imports(&self) -> Result<ScriptImports, CompileError> {
55 let stdlib_names: BTreeSet<String> = runtime::stdlib_package_declarations()
56 .into_iter()
57 .map(|defs| defs.package_name)
58 .collect();
59 let mut imports = ScriptImports::default();
60 for stmt_id in &self.ast.top_level {
61 let StmtKind::Import { module, .. } = &self
62 .ast
63 .try_stmt(*stmt_id)
64 .map_err(|error| {
65 CompileError::from(
66 error.into_compiler_failure(crate::compiler_error::CompilerStage::Parse),
67 )
68 .with_prior_diagnostics(&self.diagnostics)
69 })?
70 .kind
71 else {
72 continue;
73 };
74 if crate::source::is_relative_specifier(module) {
75 continue;
76 }
77 if let Some(server) = module.strip_prefix("@mcp/") {
78 imports.mcp_servers.insert(server.to_string());
79 } else if stdlib_names.contains(module)
80 || crate::stdlib::OptionalPackage::from_module_name(module).is_some()
81 {
82 imports.stdlib.insert(module.clone());
86 } else {
87 imports.registry_packages.insert(module.clone());
88 }
89 }
90 Ok(imports)
91 }
92
93 pub fn has_errors(&self) -> bool {
94 self.failure.is_some() || has_errors(&self.diagnostics)
95 }
96
97 pub fn diagnostics(&self) -> &[Diagnostic] {
98 &self.diagnostics
99 }
100}
101
102pub fn parse_script(source: &str, file: FileId) -> ParsedScript {
103 let mut timings = PhaseTimings::default();
104
105 let lex_start = Instant::now();
106 let mut asi = Asi::new(source, file);
107 let mut tokens: Vec<Token> = Vec::new();
108 loop {
109 let tok = asi.next_token();
110 let is_eof = matches!(tok.kind, TokenKind::Eof);
111 tokens.push(tok);
112 if is_eof {
113 break;
114 }
115 }
116 let (mut diagnostics, mut failure) = match asi.finish() {
117 Ok(diagnostics) => (diagnostics, None),
118 Err(error) => (error.clone().into_diagnostics(file), Some(error)),
119 };
120 timings.lex = lex_start.elapsed();
121
122 let parse_start = Instant::now();
123 let (mut ast, parse_diags) = if failure.is_some() {
124 (Ast::new(), Vec::new())
125 } else {
126 match crate::parser::parse_checked(source, tokens, file) {
127 Ok(result) => result,
128 Err(mut error) => {
129 error.diagnostics.splice(0..0, diagnostics.clone());
130 let rendered = error.clone().into_diagnostics(file);
131 diagnostics.clear();
132 failure = Some(error);
133 (Ast::new(), rendered)
134 }
135 }
136 };
137 diagnostics.extend(parse_diags);
138 if failure.is_none() {
140 match lower_patterns(ast) {
141 Ok(lowered) => ast = lowered,
142 Err(fatal) => {
143 let error = CompileError {
144 diagnostics: diagnostics.clone(),
145 fatal: Some(fatal),
146 };
147 diagnostics = error.clone().into_diagnostics(file);
148 failure = Some(error);
149 ast = Ast::new();
150 }
151 }
152 }
153 timings.parse = parse_start.elapsed();
154
155 ParsedScript {
156 file,
157 source: source.to_owned(),
158 failure,
159 ast,
160 diagnostics,
161 timings,
162 }
163}
164
165fn front_end(
174 source: &str,
175 parsed: &ParsedScript,
176 stdlib_defs: &[PackageDeclaration],
177 packages: &[&PackageDeclaration],
178) -> Result<(TypedAst, Vec<Diagnostic>, PhaseTimings), CompileError> {
179 front_end_with_transitive(source, parsed, stdlib_defs, packages, &[])
180}
181
182fn front_end_with_transitive(
183 source: &str,
184 parsed: &ParsedScript,
185 stdlib_defs: &[PackageDeclaration],
186 packages: &[&PackageDeclaration],
187 transitive: &[&PackageDeclaration],
188) -> Result<(TypedAst, Vec<Diagnostic>, PhaseTimings), CompileError> {
189 if source != parsed.source {
190 return Err(CompilerFailure::Internal {
191 stage: CompilerStage::Infer,
192 span: None,
193 message: "parsed script does not belong to the supplied source".into(),
194 }
195 .into());
196 }
197 if let Some(error) = &parsed.failure {
198 return Err(error.clone());
199 }
200 let mut timings = parsed.timings;
201 let mut diags = parsed.diagnostics.clone();
202 let typecheck_start = Instant::now();
203 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
204 let mut infer_refs: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
205 infer_refs.extend(host_defs.iter());
206 infer_refs.extend(stdlib_defs.iter());
207 infer_refs.extend_from_slice(packages);
208 let (ta, infer_diags) = crate::typechecker::infer::infer_with_transitive_checked(
209 source,
210 crate::mangle::USER_PACKAGE,
211 &parsed.ast,
212 &infer_refs,
213 transitive,
214 )
215 .map_err(|mut error| {
216 error.diagnostics.splice(0..0, diags.clone());
217 error
218 })?;
219 diags.extend(infer_diags);
220 infer_refs.extend_from_slice(transitive);
221 diags.extend(
222 check_script(&ta, &infer_refs).map_err(|error| error.with_prior_diagnostics(&diags))?,
223 );
224 timings.typecheck = typecheck_start.elapsed();
225
226 Ok((ta, diags, timings))
227}
228
229#[derive(Clone, Debug)]
230pub struct PackageSourceModule<'a> {
231 pub path: ModulePath,
232 pub source: &'a str,
233}
234
235#[derive(Clone, Debug, PartialEq)]
236pub struct CompiledPackage {
237 pub wasm: Vec<u8>,
238 pub type_info: crate::TypeInfoTable,
239 pub declaration: PackageDeclaration,
240 pub required_capabilities: Vec<DerivedCapability>,
241 pub authority_map: crate::AuthorityMap,
242 pub warnings: Vec<Diagnostic>,
243}
244
245#[derive(Clone, Debug)]
246pub struct CompiledScript {
247 pub wasm: Vec<u8>,
248 pub type_info: crate::TypeInfoTable,
249 pub warnings: Vec<Diagnostic>,
250 pub timings: PhaseTimings,
251}
252
253pub fn typecheck_checked(source: &str, file: FileId) -> Result<Vec<Diagnostic>, CompileError> {
257 typecheck_script(source, file).map_err(|error| {
258 error.with_limit_span_cut(|span_file| (span_file == file).then_some(source))
259 })
260}
261
262fn typecheck_script(source: &str, file: FileId) -> Result<Vec<Diagnostic>, CompileError> {
263 let parsed = parse_script(source, file);
264 let stdlib_defs = runtime::stdlib_package_declarations();
265 typecheck_parsed_script(source, &parsed, &stdlib_defs)
266}
267
268pub fn typecheck_parsed_checked(
271 source: &str,
272 parsed: &ParsedScript,
273 stdlib_defs: &[PackageDeclaration],
274) -> Result<Vec<Diagnostic>, CompileError> {
275 let file = parsed.file;
276 typecheck_parsed_script(source, parsed, stdlib_defs).map_err(|error| {
277 error.with_limit_span_cut(|span_file| (span_file == file).then_some(source))
278 })
279}
280
281fn typecheck_parsed_script(
282 source: &str,
283 parsed: &ParsedScript,
284 stdlib_defs: &[PackageDeclaration],
285) -> Result<Vec<Diagnostic>, CompileError> {
286 let file = parsed.file;
287 let (ta, diags, _timings) = front_end(source, parsed, stdlib_defs, &[])?;
288 if has_errors(&diags) {
289 return Err(diags.into());
290 }
291 let with_front_end_diagnostics = |failure| CompileError {
294 diagnostics: diags.clone(),
295 fatal: Some(failure),
296 };
297 let ta = capture(ta).map_err(with_front_end_diagnostics)?;
298 desugar(ta, file).map_err(with_front_end_diagnostics)?;
299 Ok(warnings_only(diags))
300}
301
302pub fn typecheck_to_typed_ast(source: &str, file: FileId) -> (TypedAst, Vec<Diagnostic>) {
307 let parsed = parse_script(source, file);
308 let stdlib_defs = runtime::stdlib_package_declarations();
309 match front_end(source, &parsed, &stdlib_defs, &[]) {
310 Ok((ta, diags, _)) => (ta, diags),
311 Err(error) => (
312 TypedAst::new(),
313 error
314 .with_limit_span_cut(|span_file| (span_file == file).then_some(source))
315 .into_diagnostics(file),
316 ),
317 }
318}
319
320pub fn compile_script_checked(
330 source: &str,
331 filename: &str,
332 file: FileId,
333 packages: &[&PackageDeclaration],
334 mcps: &[&PackageDeclaration],
335) -> Result<CompiledScript, CompileError> {
336 let parsed = parse_script(source, file);
337 let stdlib_defs = runtime::stdlib_package_declarations();
338 compile_parsed_script_timed_checked(source, filename, &parsed, &stdlib_defs, packages, mcps)
339}
340
341pub fn compile_script_owned_by_checked(
346 owning_package: &str,
347 source: &str,
348 filename: &str,
349 file: FileId,
350 packages: &[&PackageDeclaration],
351 mcps: &[&PackageDeclaration],
352) -> Result<CompiledScript, CompileError> {
353 let parsed = parse_script(source, file);
354 let stdlib_defs = runtime::stdlib_package_declarations();
355 let declarations = [packages, mcps].concat();
356 compile_parsed_script_owned_by(
357 Some(owning_package),
358 source,
359 filename,
360 &parsed,
361 &stdlib_defs,
362 &declarations,
363 &[],
364 )
365}
366
367pub fn compile_parsed_script_timed_checked(
368 source: &str,
369 filename: &str,
370 parsed: &ParsedScript,
371 stdlib_defs: &[PackageDeclaration],
372 packages: &[&PackageDeclaration],
373 mcps: &[&PackageDeclaration],
374) -> Result<CompiledScript, CompileError> {
375 compile_parsed_script_with_transitive_checked(
376 source,
377 filename,
378 parsed,
379 stdlib_defs,
380 packages,
381 mcps,
382 &[],
383 )
384}
385
386pub fn compile_parsed_script_with_transitive_checked(
389 source: &str,
390 filename: &str,
391 parsed: &ParsedScript,
392 stdlib_defs: &[PackageDeclaration],
393 packages: &[&PackageDeclaration],
394 mcps: &[&PackageDeclaration],
395 transitive: &[&PackageDeclaration],
396) -> Result<CompiledScript, CompileError> {
397 let declarations = [packages, mcps].concat();
398 compile_parsed_script_owned_by(
399 None,
400 source,
401 filename,
402 parsed,
403 stdlib_defs,
404 &declarations,
405 transitive,
406 )
407}
408
409fn compile_parsed_script_owned_by(
410 owning_package: Option<&str>,
411 source: &str,
412 filename: &str,
413 parsed: &ParsedScript,
414 stdlib_defs: &[PackageDeclaration],
415 external_declarations: &[&PackageDeclaration],
416 transitive: &[&PackageDeclaration],
417) -> Result<CompiledScript, CompileError> {
418 compile_parsed_script(
419 owning_package,
420 source,
421 filename,
422 parsed,
423 stdlib_defs,
424 external_declarations,
425 transitive,
426 )
427 .map_err(|error| error.with_limit_span_cut(|file| (file == parsed.file).then_some(source)))
428}
429
430fn compile_parsed_script(
431 owning_package: Option<&str>,
432 source: &str,
433 filename: &str,
434 parsed: &ParsedScript,
435 stdlib_defs: &[PackageDeclaration],
436 external_declarations: &[&PackageDeclaration],
437 transitive: &[&PackageDeclaration],
438) -> Result<CompiledScript, CompileError> {
439 let (mut ta, diags, mut timings) = front_end_with_transitive(
440 source,
441 parsed,
442 stdlib_defs,
443 external_declarations,
444 transitive,
445 )?;
446 if has_errors(&diags) {
447 return Err(diags.into());
448 }
449 let capture_start = Instant::now();
450 ta = capture(ta).map_err(|fatal| CompileError {
451 diagnostics: diags.clone(),
452 fatal: Some(fatal),
453 })?;
454 timings.capture = capture_start.elapsed();
455
456 let desugar_start = Instant::now();
457 ta = desugar(ta, parsed.file).map_err(|fatal| CompileError {
458 diagnostics: diags.clone(),
459 fatal: Some(fatal),
460 })?;
461 timings.desugar = desugar_start.elapsed();
462
463 let (prelude_defs, host_defs, internal_defs) = prelude::cached_runtime_package_declarations();
464 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
465 for defs in host_defs {
466 dependencies.push(defs);
467 }
468 for defs in internal_defs {
469 dependencies.push(defs);
470 }
471 for defs in stdlib_defs {
472 dependencies.push(defs);
473 }
474 dependencies.extend_from_slice(external_declarations);
475 dependencies.extend_from_slice(transitive);
476
477 let codegen_start = Instant::now();
478 let generated = match owning_package {
479 Some(pkg) => {
480 crate::codegen::codegen_owned_by(pkg, source, filename, parsed.file, &ta, &dependencies)
481 }
482 None => crate::codegen::codegen_with_type_info(
483 source,
484 filename,
485 parsed.file,
486 &ta,
487 &dependencies,
488 ),
489 }
490 .map_err(|fatal| CompileError {
491 diagnostics: diags.clone(),
492 fatal: Some(fatal),
493 })?;
494 timings.codegen = codegen_start.elapsed();
495
496 Ok(CompiledScript {
497 wasm: generated.wasm,
498 type_info: generated.type_info,
499 warnings: warnings_only(diags),
500 timings,
501 })
502}
503
504pub fn compile_package_checked(
505 package_name: &str,
506 root_module: ModulePath,
507 modules: &[PackageSourceModule<'_>],
508 dependencies: &[&PackageDeclaration],
509) -> Result<CompiledPackage, CompileError> {
510 compile_package_with_transitive_checked(package_name, root_module, modules, dependencies, &[])
511}
512
513pub fn compile_package_with_transitive_checked(
525 package_name: &str,
526 root_module: ModulePath,
527 modules: &[PackageSourceModule<'_>],
528 dependencies: &[&PackageDeclaration],
529 transitive: &[&PackageDeclaration],
530) -> Result<CompiledPackage, CompileError> {
531 compile_package_with_transitive_checked_for(
532 Stdlib::core(),
533 package_name,
534 root_module,
535 modules,
536 dependencies,
537 transitive,
538 )
539}
540
541pub fn compile_package_with_transitive_checked_for(
544 stdlib: Stdlib,
545 package_name: &str,
546 root_module: ModulePath,
547 modules: &[PackageSourceModule<'_>],
548 dependencies: &[&PackageDeclaration],
549 transitive: &[&PackageDeclaration],
550) -> Result<CompiledPackage, CompileError> {
551 let mut sources = Sources::new();
552 let stdlib_defs = stdlib.package_declarations();
553 compile_package_sources(
554 &mut sources,
555 &stdlib_defs,
556 package_name,
557 root_module,
558 modules,
559 dependencies,
560 transitive,
561 )
562 .map_err(|error| {
563 error.with_limit_span_cut(|file| sources.get(file).map(crate::SourceFile::text))
564 })
565}
566
567fn compile_package_sources(
568 sources: &mut Sources,
569 stdlib_defs: &[PackageDeclaration],
570 package_name: &str,
571 root_module: ModulePath,
572 modules: &[PackageSourceModule<'_>],
573 dependencies: &[&PackageDeclaration],
574 transitive: &[&PackageDeclaration],
575) -> Result<CompiledPackage, CompileError> {
576 crate::typechecker::infer::check_declaration_types(
579 dependencies.iter().chain(transitive).copied(),
580 )?;
581 let mut parsed_modules = Vec::with_capacity(modules.len());
582 let mut diagnostics = Vec::new();
583 for module in modules {
584 let file = sources
585 .add(module.path.clone(), module.source)
586 .map_err(|error| {
587 CompileError::from(error.into_compiler_failure(CompilerStage::Parse))
588 .with_prior_diagnostics(&diagnostics)
589 })?;
590 let (ast, mut module_diags) =
591 parse_package_module(module.source, file).map_err(|mut error| {
592 error.diagnostics.splice(0..0, diagnostics.clone());
593 error
594 })?;
595 diagnostics.append(&mut module_diags);
596 parsed_modules.push((module.path.clone(), file, ast));
597 }
598 if has_errors(&diagnostics) {
599 return Err(diagnostics.into());
600 }
601
602 let module_refs: Vec<_> = parsed_modules
603 .iter()
604 .map(|(path, file, ast)| (path.clone(), *file, ast))
605 .collect();
606 let mut external_packages: BTreeMap<String, PackageDeclaration> = stdlib_defs
607 .iter()
608 .map(|defs| (defs.package_name.clone(), defs.clone()))
609 .collect();
610 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
611 for defs in prelude_defs {
612 external_packages.insert(defs.package_name.clone(), defs.clone());
613 }
614 for defs in host_defs {
615 external_packages.insert(defs.package_name.clone(), defs.clone());
616 }
617 for defs in dependencies {
618 external_packages.insert(defs.package_name.clone(), (*defs).clone());
619 }
620 let transitive_packages: BTreeMap<String, PackageDeclaration> = transitive
621 .iter()
622 .filter(|defs| !external_packages.contains_key(&defs.package_name))
623 .map(|defs| (defs.package_name.clone(), (*defs).clone()))
624 .collect();
625 let (mut ta, mut declaration, mut package_diags) =
626 crate::typechecker::infer::infer_package_checked(
627 package_name,
628 root_module.clone(),
629 module_refs,
630 sources,
631 external_packages,
632 transitive_packages,
633 )
634 .map_err(|mut error| {
635 error.diagnostics.splice(0..0, diagnostics.clone());
636 error
637 })?;
638 diagnostics.append(&mut package_diags);
639 if has_errors(&diagnostics) {
640 return Err(diagnostics.into());
641 }
642 let (required_capabilities, capability_warnings) =
643 compile_capabilities::derive_package_requirements(
644 &declaration,
645 &ta,
646 stdlib_defs,
647 dependencies,
648 transitive,
649 )
650 .map_err(|error| error.with_prior_diagnostics(&diagnostics))?;
651 diagnostics.extend(capability_warnings);
652 let (prelude_defs, host_defs, internal_defs) = prelude::cached_runtime_package_declarations();
653 let authority_map = crate::authority::analyse(
654 &declaration,
655 &ta,
656 sources,
657 stdlib_defs
658 .iter()
659 .chain(prelude_defs)
660 .chain(host_defs)
661 .chain(dependencies.iter().copied())
662 .chain(transitive.iter().copied()),
663 )
664 .map_err(|fatal| CompileError {
665 diagnostics: diagnostics.clone(),
666 fatal: Some(fatal),
667 })?;
668 ta = capture(ta).map_err(|fatal| CompileError {
669 diagnostics: diagnostics.clone(),
670 fatal: Some(fatal),
671 })?;
672 let root_file = parsed_modules
673 .iter()
674 .find(|(path, _, _)| *path == root_module)
675 .map(|(_, file, _)| *file)
676 .ok_or_else(|| CompilerFailure::Internal {
677 stage: CompilerStage::Infer,
678 span: None,
679 message: "compiled package root module is missing".into(),
680 })?;
681 ta = desugar(ta, root_file).map_err(|fatal| CompileError {
682 diagnostics: diagnostics.clone(),
683 fatal: Some(fatal),
684 })?;
685
686 let mut codegen_deps: Vec<&PackageDeclaration> = prelude_defs.iter().collect();
687 codegen_deps.extend(host_defs.iter());
688 codegen_deps.extend(internal_defs.iter());
689 codegen_deps.extend(stdlib_defs.iter());
690 codegen_deps.extend_from_slice(dependencies);
691 codegen_deps.extend_from_slice(transitive);
692 let generated =
693 crate::codegen::codegen_package_with_type_info(sources, root_file, &ta, &codegen_deps)
694 .map_err(|fatal| CompileError {
695 diagnostics: diagnostics.clone(),
696 fatal: Some(fatal),
697 })?;
698 declaration.runtime_functions = generated.runtime_functions;
699 declaration.runtime_globals = generated.runtime_globals;
700 declaration.closure_caches = generated.closure_caches;
701 Ok(CompiledPackage {
702 wasm: generated.wasm,
703 type_info: generated.type_info,
704 declaration,
705 required_capabilities,
706 authority_map,
707 warnings: warnings_only(diagnostics),
708 })
709}
710
711fn parse_package_module(
712 source: &str,
713 file: FileId,
714) -> Result<(crate::Ast, Vec<Diagnostic>), CompileError> {
715 let mut asi = Asi::new(source, file);
716 let mut tokens: Vec<Token> = Vec::new();
717 loop {
718 let tok = asi.next_token();
719 let is_eof = matches!(tok.kind, TokenKind::Eof);
720 tokens.push(tok);
721 if is_eof {
722 break;
723 }
724 }
725 let mut diagnostics = asi.finish()?;
726 let (mut ast, parse_diags) =
727 crate::parser::parse_checked(source, tokens, file).map_err(|mut error| {
728 error.diagnostics.splice(0..0, diagnostics.clone());
729 error
730 })?;
731 diagnostics.extend(parse_diags);
732 if !has_errors(&diagnostics) {
733 ast = lower_patterns(ast).map_err(|fatal| CompileError {
734 diagnostics: diagnostics.clone(),
735 fatal: Some(fatal),
736 })?;
737 }
738 Ok((ast, diagnostics))
739}
740
741fn has_errors(diags: &[Diagnostic]) -> bool {
742 diags.iter().any(|d| d.severity == Severity::Error)
743}
744
745fn warnings_only(diags: Vec<Diagnostic>) -> Vec<Diagnostic> {
746 diags
747 .into_iter()
748 .filter(|d| d.severity == Severity::Warning)
749 .collect()
750}
751
752pub fn typecheck(source: &str, file: FileId) -> Result<Vec<Diagnostic>, Vec<Diagnostic>> {
754 typecheck_checked(source, file).map_err(|error| error.into_diagnostics(file))
755}
756
757pub fn compile_script(
759 source: &str,
760 filename: &str,
761 file: FileId,
762 packages: &[&PackageDeclaration],
763 mcps: &[&PackageDeclaration],
764) -> Result<CompiledScript, Vec<Diagnostic>> {
765 compile_script_checked(source, filename, file, packages, mcps)
766 .map_err(|error| error.into_diagnostics(file))
767}
768
769pub fn compile_script_owned_by(
771 owning_package: &str,
772 source: &str,
773 filename: &str,
774 file: FileId,
775 packages: &[&PackageDeclaration],
776 mcps: &[&PackageDeclaration],
777) -> Result<CompiledScript, Vec<Diagnostic>> {
778 compile_script_owned_by_checked(owning_package, source, filename, file, packages, mcps)
779 .map_err(|error| error.into_diagnostics(file))
780}
781
782pub fn compile_parsed_script_timed(
784 source: &str,
785 filename: &str,
786 parsed: &ParsedScript,
787 stdlib_defs: &[PackageDeclaration],
788 packages: &[&PackageDeclaration],
789 mcps: &[&PackageDeclaration],
790) -> Result<CompiledScript, Vec<Diagnostic>> {
791 compile_parsed_script_timed_checked(source, filename, parsed, stdlib_defs, packages, mcps)
792 .map_err(|error| error.into_diagnostics(parsed.file))
793}
794
795pub fn compile_parsed_script_with_transitive(
797 source: &str,
798 filename: &str,
799 parsed: &ParsedScript,
800 stdlib_defs: &[PackageDeclaration],
801 packages: &[&PackageDeclaration],
802 mcps: &[&PackageDeclaration],
803 transitive: &[&PackageDeclaration],
804) -> Result<CompiledScript, Vec<Diagnostic>> {
805 compile_parsed_script_with_transitive_checked(
806 source,
807 filename,
808 parsed,
809 stdlib_defs,
810 packages,
811 mcps,
812 transitive,
813 )
814 .map_err(|error| error.into_diagnostics(parsed.file))
815}
816
817pub fn compile_package(
819 package_name: &str,
820 root_module: ModulePath,
821 modules: &[PackageSourceModule<'_>],
822 dependencies: &[&PackageDeclaration],
823) -> Result<CompiledPackage, Vec<Diagnostic>> {
824 compile_package_checked(package_name, root_module, modules, dependencies)
825 .map_err(|error| error.into_diagnostics(FileId(0)))
826}
827
828pub fn compile_package_with_transitive(
830 package_name: &str,
831 root_module: ModulePath,
832 modules: &[PackageSourceModule<'_>],
833 dependencies: &[&PackageDeclaration],
834 transitive: &[&PackageDeclaration],
835) -> Result<CompiledPackage, Vec<Diagnostic>> {
836 compile_package_with_transitive_checked(
837 package_name,
838 root_module,
839 modules,
840 dependencies,
841 transitive,
842 )
843 .map_err(|error| error.into_diagnostics(FileId(0)))
844}
845
846#[cfg(test)]
847mod parsed_script_tests {
848 use super::*;
849
850 #[test]
851 fn typed_compile_errors_distinguish_source_limits_and_internal_failures() {
852 let source_error = compile_script_checked(
853 "function main(): number { return false; }",
854 "test.ts",
855 FileId(0),
856 &[],
857 &[],
858 )
859 .expect_err("source type mismatch");
860 assert!(source_error.fatal.is_none());
861 assert!(!source_error.diagnostics.is_empty());
862
863 let nested = format!(
864 "function main(): number {{ return {}1{}; }}",
865 "(".repeat(2048),
866 ")".repeat(2048)
867 );
868 let limit = compile_script_checked(&nested, "test.ts", FileId(0), &[], &[])
869 .expect_err("parser limit");
870 assert!(matches!(
871 limit.fatal,
872 Some(CompilerFailure::Limit {
873 stage: CompilerStage::Parse,
874 ..
875 })
876 ));
877 let rendered = limit.into_diagnostics(FileId(0));
878 assert_eq!(
879 rendered
880 .iter()
881 .filter(|d| d.message.contains("parser recursion limit exceeded"))
882 .count(),
883 1
884 );
885
886 let parsed = parse_script("function main(): number { return 42; }", FileId(0));
887 let internal = compile_parsed_script_timed_checked(
888 "different source",
889 "test.ts",
890 &parsed,
891 &[],
892 &[],
893 &[],
894 )
895 .expect_err("source metadata mismatch");
896 assert!(matches!(
897 internal.fatal,
898 Some(CompilerFailure::Internal {
899 stage: CompilerStage::Infer,
900 ..
901 })
902 ));
903 }
904
905 #[test]
906 fn external_imports_come_from_parsed_imports_only() {
907 let parsed = parse_script(
908 r#"
909 // import fake from "@mcp/commented";
910 const s: string = "import x from \"@acme/string\"";
911 import { v4 } from "submilli:uuid";
912 import linear from "@mcp/linear";
913 import { answer } from "@acme/util";
914 function main(): string { return v4(); }
915 "#,
916 FileId(0),
917 );
918
919 let imports = parsed.external_imports().unwrap();
920
921 assert_eq!(
922 imports.stdlib,
923 BTreeSet::from(["submilli:uuid".to_string()])
924 );
925 assert_eq!(imports.mcp_servers, BTreeSet::from(["linear".to_string()]));
926 assert_eq!(
927 imports.registry_packages,
928 BTreeSet::from(["@acme/util".to_string()])
929 );
930 }
931
932 #[test]
933 fn relative_imports_are_not_external_imports() {
934 let parsed = parse_script(
935 r#"
936 import { local } from "./util";
937 function main(): number { return 1; }
938 "#,
939 FileId(0),
940 );
941
942 assert_eq!(parsed.external_imports().unwrap(), ScriptImports::default());
943 }
944}
945
946#[cfg(test)]
947mod importless_library_tests {
948 use std::collections::BTreeMap;
956
957 use crate::mangle::package_symbol;
958 use crate::package_declaration::{Dispatch, MethodSig, PropertySig, TypeKind, ValueKind};
959 use crate::{ObjectField, Package, PackageDeclaration, Span, Type, TypeSymbol, ValueSymbol};
960
961 const SYNTH: &str = "submilli:synth";
962 const ACME: &str = "@acme/util";
963
964 fn synth_package() -> PackageDeclaration {
965 let mut defs = PackageDeclaration::with_package(SYNTH);
966
967 let mut properties = BTreeMap::new();
968 properties.insert(
969 "label".to_string(),
970 PropertySig {
971 ty: Type::String,
972 readonly: true,
973 intrinsic: false,
974 optional: false,
975 doc: None,
976 },
977 );
978 let mut methods = BTreeMap::new();
979 methods.insert(
980 "size".to_string(),
981 MethodSig {
982 optional: false,
983 generics: Vec::new(),
984 params: Vec::new(),
985 ret: Type::Number,
986 predicate: None,
987 doc: None,
988 },
989 );
990 defs.types.insert(
991 "Widget".to_string(),
992 TypeSymbol {
993 name: "Widget".to_string(),
994 mangled_name: package_symbol(SYNTH, "Widget"),
995 declaration_span: Span::at(crate::FileId(0)),
996 kind: TypeKind::Interface {
997 index: None,
998 generics: Vec::new(),
999 methods,
1000 properties,
1001 dispatch: Dispatch::Direct,
1002 doc: None,
1003 },
1004 },
1005 );
1006
1007 defs.types.insert(
1008 "Color".to_string(),
1009 TypeSymbol {
1010 name: "Color".to_string(),
1011 mangled_name: package_symbol(SYNTH, "Color"),
1012 declaration_span: Span::at(crate::FileId(0)),
1013 kind: TypeKind::NumberEnum {
1014 variants: vec![("Red".to_string(), 0.0), ("Green".to_string(), 1.0)],
1015 doc: None,
1016 },
1017 },
1018 );
1019
1020 let tree_ref = || Type::AliasRef {
1024 mangled: crate::mangle::package_symbol(SYNTH, "Tree"),
1025 package: Package(SYNTH.to_string()),
1026 name: "Tree".to_string(),
1027 args: Vec::new(),
1028 };
1029 let tree_body = || {
1030 let mut fields = BTreeMap::new();
1031 fields.insert("value".to_string(), ObjectField::required(Type::Number));
1032 fields.insert(
1033 "children".to_string(),
1034 ObjectField::required(Type::Array(Box::new(tree_ref()))),
1035 );
1036 Type::Object {
1037 index: None,
1038 fields,
1039 }
1040 };
1041 defs.types.insert(
1042 "Tree".to_string(),
1043 TypeSymbol {
1044 name: "Tree".to_string(),
1045 mangled_name: package_symbol(SYNTH, "Tree"),
1046 declaration_span: Span::at(crate::FileId(0)),
1047 kind: TypeKind::Alias {
1048 generics: Vec::new(),
1049 ty: tree_body(),
1050 doc: None,
1051 },
1052 },
1053 );
1054
1055 let synth_ref = |name: &str, ty: Type| ValueSymbol {
1056 name: name.to_string(),
1057 mangled_name: package_symbol(SYNTH, name),
1058 declaration_span: Span::at(crate::FileId(0)),
1059 kind: ValueKind::Function {
1060 generics: Vec::new(),
1061 params: Vec::new(),
1062 ret: ty,
1063 type_predicate: None,
1064 doc: None,
1065 },
1066 };
1067 defs.values.insert(
1068 "makeWidget".to_string(),
1069 synth_ref(
1070 "makeWidget",
1071 Type::InterfaceRef {
1072 mangled: crate::mangle::package_symbol(SYNTH, "Widget"),
1073 package: Package(SYNTH.to_string()),
1074 name: "Widget".to_string(),
1075 args: Vec::new(),
1076 },
1077 ),
1078 );
1079 defs.values.insert(
1080 "pickColor".to_string(),
1081 synth_ref(
1082 "pickColor",
1083 Type::NumberEnum {
1084 mangled: crate::mangle::package_symbol(SYNTH, "Color"),
1085 package: Package(SYNTH.to_string()),
1086 name: "Color".to_string(),
1087 member: None,
1088 },
1089 ),
1090 );
1091 defs.values.insert(
1092 "makeTree".to_string(),
1093 synth_ref(
1094 "makeTree",
1095 Type::Alias {
1096 mangled: crate::mangle::package_symbol(SYNTH, "Tree"),
1097 package: Package(SYNTH.to_string()),
1098 name: "Tree".to_string(),
1099 args: Vec::new(),
1100 ty: Box::new(tree_body()),
1101 },
1102 ),
1103 );
1104 defs
1105 }
1106
1107 fn acme_package() -> PackageDeclaration {
1108 let mut defs = PackageDeclaration::with_package(ACME);
1109 defs.values.insert(
1110 "greet".to_string(),
1111 ValueSymbol {
1112 name: "greet".to_string(),
1113 mangled_name: package_symbol(ACME, "greet"),
1114 declaration_span: Span::at(crate::FileId(0)),
1115 kind: ValueKind::Function {
1116 generics: Vec::new(),
1117 params: Vec::new(),
1118 ret: Type::String,
1119 type_predicate: None,
1120 doc: None,
1121 },
1122 },
1123 );
1124 defs
1125 }
1126
1127 fn errors(source: &str, packages: &[&PackageDeclaration]) -> Vec<String> {
1128 let parsed = super::parse_script(source, crate::FileId(0));
1129 let stdlib_defs = crate::runtime::stdlib_package_declarations();
1130 let (_ta, diags, _timings) =
1131 super::front_end(source, &parsed, &stdlib_defs, packages).expect("front end");
1132 diags
1133 .into_iter()
1134 .filter(|d| d.severity == crate::Severity::Error)
1135 .map(|d| d.message)
1136 .collect()
1137 }
1138
1139 fn compile_errors(source: &str, packages: &[&PackageDeclaration]) -> Vec<String> {
1140 super::compile_script(source, "main.subm", crate::FileId(0), packages, &[])
1141 .expect_err("expected compile error")
1142 .into_iter()
1143 .filter(|d| d.severity == crate::Severity::Error)
1144 .map(|d| d.message)
1145 .collect()
1146 }
1147
1148 #[test]
1149 fn package_declarations_passed_to_compile_are_importable() {
1150 let acme = acme_package();
1151 let source = r#"
1152 import { greet } from "@acme/util";
1153 function main(): void {}
1154 "#;
1155 super::compile_script(source, "main.subm", crate::FileId(0), &[&acme], &[])
1156 .expect("package compiles");
1157 }
1158
1159 #[test]
1160 fn package_not_passed_to_compile_uses_not_found_diagnostic() {
1161 let acme = acme_package();
1162 let source = r#"
1163 import { greet } from "@acme/util";
1164 function main(): void {}
1165 "#;
1166 let _package_not_supplied = acme;
1167 let errs = compile_errors(source, &[]);
1168 assert!(
1169 errs.iter()
1170 .any(|m| m.contains("package `@acme/util` not found")),
1171 "expected not-found diagnostic, got: {errs:?}"
1172 );
1173 }
1174
1175 #[test]
1176 fn unknown_package_still_uses_not_found_diagnostic() {
1177 let source = r#"
1178 import { greet } from "@acme/missing";
1179 function main(): void {}
1180 "#;
1181 let errs = compile_errors(source, &[]);
1182 assert!(
1183 errs.iter()
1184 .any(|m| m.contains("package `@acme/missing` not found")),
1185 "expected not-found diagnostic, got: {errs:?}"
1186 );
1187 }
1188
1189 #[test]
1190 fn stdlib_imports_do_not_require_blueprint_packages() {
1191 let source = r#"
1192 import { v4 } from "submilli:uuid";
1193 function main(): void {}
1194 "#;
1195 super::compile_script(source, "main.subm", crate::FileId(0), &[], &[])
1196 .expect("stdlib package compiles without package declarations");
1197 }
1198
1199 #[test]
1200 fn importless_interface_member_access() {
1201 let synth = synth_package();
1202 let source = r#"
1204 import { makeWidget } from "submilli:synth";
1205 function main(): void {
1206 const w = makeWidget();
1207 assert(w.label === "x", "property read");
1208 const n: number = w.size();
1209 assert(n >= 0, "method call");
1210 }
1211 "#;
1212 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1213 }
1214
1215 #[test]
1216 fn importless_enum_value_use() {
1217 let synth = synth_package();
1218 let source = r#"
1220 import { pickColor } from "submilli:synth";
1221 function main(): void {
1222 const a = pickColor();
1223 const b = pickColor();
1224 assert(a === b, "enum values compare");
1225 }
1226 "#;
1227 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1228 }
1229
1230 #[test]
1231 fn importless_recursive_alias_deep_member_access() {
1232 let synth = synth_package();
1237 let source = r#"
1238 import { makeTree } from "submilli:synth";
1239 function main(): void {
1240 const t = makeTree();
1241 const root: number = t.value;
1242 assert(root >= 0, "root value");
1243 const child: number = t.children[0].value;
1244 assert(child >= 0, "deep recursive-alias value");
1245 const grand: number = t.children[0].children[0].value;
1246 assert(grand >= 0, "two levels deep");
1247 }
1248 "#;
1249 assert_eq!(errors(source, &[&synth]), Vec::<String>::new());
1250 }
1251
1252 #[test]
1253 fn importless_recursive_alias_resolves_body_shape() {
1254 let synth = synth_package();
1259 let source = r#"
1260 import { makeTree } from "submilli:synth";
1261 function main(): void {
1262 const t = makeTree();
1263 const _x = t.children[0].nope;
1264 }
1265 "#;
1266 let errs = errors(source, &[&synth]);
1267 assert!(
1268 errs.iter().any(|m| m.contains("nope")),
1269 "expected a field-not-found error naming `nope`; got: {errs:?}"
1270 );
1271 }
1272
1273 #[test]
1274 fn unimported_type_name_still_unresolved_in_source() {
1275 let synth = synth_package();
1278 let source = r#"
1279 import { makeWidget } from "submilli:synth";
1280 function main(): void {
1281 const w: Widget = makeWidget();
1282 }
1283 "#;
1284 let errs = errors(source, &[&synth]);
1285 assert!(
1286 errs.iter().any(|m| m.contains("unknown type `Widget`")),
1287 "expected unknown-type error for unimported source annotation; got: {errs:?}"
1288 );
1289 }
1290}
1291
1292#[cfg(test)]
1293mod untyped_arena_failures {
1294 use super::*;
1295 use crate::arena::{ArenaKind, with_node_limit};
1296 use crate::compiler_error::{CompilerFailure, CompilerStage};
1297
1298 #[test]
1299 fn parser_arena_limit_is_fatal_and_keeps_prior_diagnostics() {
1300 let source = "let = 0; function main(): number { return 1; }";
1301 for arena in [ArenaKind::Expressions, ArenaKind::Statements] {
1302 let error = with_node_limit(arena, 0, || {
1303 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1304 })
1305 .unwrap_err();
1306 assert!(matches!(
1307 error.fatal,
1308 Some(CompilerFailure::Limit {
1309 stage: CompilerStage::Parse,
1310 span: None,
1311 ..
1312 })
1313 ));
1314 assert!(
1315 !error.diagnostics.is_empty(),
1316 "earlier syntax diagnostics must survive"
1317 );
1318 }
1319 assert!(
1320 !compile_script_checked(
1321 "function main(): number { return 1; }",
1322 "healthy.ts",
1323 FileId(0),
1324 &[],
1325 &[]
1326 )
1327 .unwrap()
1328 .wasm
1329 .is_empty()
1330 );
1331 }
1332
1333 #[test]
1334 fn lowering_arena_limit_stops_script_before_inference() {
1335 let source = "function main(): number { const [x] = [1]; return x; }";
1336 let mut lexer = crate::Asi::new(source, FileId(0));
1337 let mut tokens = Vec::new();
1338 loop {
1339 let token = lexer.next_token();
1340 let done = matches!(token.kind, crate::TokenKind::Eof);
1341 tokens.push(token);
1342 if done {
1343 break;
1344 }
1345 }
1346 let (ast, _) = crate::parser::parse_checked(source, tokens, FileId(0)).unwrap();
1347 for (arena, count) in [
1348 (ArenaKind::Expressions, ast.exprs_len()),
1349 (ArenaKind::Statements, ast.stmts_len()),
1350 ] {
1351 let error = with_node_limit(arena, u32::try_from(count).unwrap(), || {
1352 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1353 })
1354 .unwrap_err();
1355 assert!(matches!(
1356 error.fatal,
1357 Some(CompilerFailure::Limit {
1358 stage: CompilerStage::Infer,
1359 span: None,
1360 ..
1361 })
1362 ));
1363 }
1364 assert!(
1365 !compile_script_checked(source, "healthy.ts", FileId(0), &[], &[])
1366 .unwrap()
1367 .wasm
1368 .is_empty()
1369 );
1370 }
1371
1372 #[test]
1373 fn package_arena_limit_returns_no_artifact() {
1374 let modules = [PackageSourceModule {
1375 path: ModulePath::from("lib"),
1376 source: "export function value(): number { return 1; }",
1377 }];
1378 let error = with_node_limit(ArenaKind::Expressions, 0, || {
1379 compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1380 })
1381 .unwrap_err();
1382 assert!(matches!(
1383 error.fatal,
1384 Some(CompilerFailure::Limit {
1385 stage: CompilerStage::Parse,
1386 ..
1387 })
1388 ));
1389 compile_package_checked("test", ModulePath::from("lib"), &modules, &[]).unwrap();
1390 }
1391
1392 #[test]
1393 fn corrupt_parsed_imports_return_an_internal_failure() {
1394 let mut parsed = parse_script("function main(): number { return 1; }", FileId(0));
1395 parsed.ast.top_level.push(crate::StmtId(u32::MAX));
1396 let error = parsed.external_imports().unwrap_err();
1397 assert!(matches!(
1398 error.fatal,
1399 Some(CompilerFailure::Internal { span: None, .. })
1400 ));
1401 }
1402}
1403
1404#[cfg(test)]
1405mod typed_arena_failures {
1406 use super::*;
1407 use crate::arena::{ArenaKind, with_node_limit};
1408
1409 fn assert_limit(error: CompileError) {
1410 assert!(
1411 matches!(error.fatal, Some(CompilerFailure::Limit { span: None, .. })),
1412 "{error:?}"
1413 );
1414 }
1415
1416 #[test]
1417 fn typed_allocation_failure_stops_script_and_package_compilation() {
1418 let source = "function main(): number { return 1; }";
1419 let modules = [PackageSourceModule {
1420 path: ModulePath::from("lib"),
1421 source: "export function value(): number { return 1; }",
1422 }];
1423 for arena in [ArenaKind::TypedExpressions, ArenaKind::TypedStatements] {
1424 assert_limit(
1425 with_node_limit(arena, 0, || {
1426 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1427 })
1428 .unwrap_err(),
1429 );
1430 assert_limit(
1431 with_node_limit(arena, 0, || {
1432 compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1433 })
1434 .unwrap_err(),
1435 );
1436 }
1437 assert!(
1438 !compile_script_checked(source, "healthy.ts", FileId(0), &[], &[])
1439 .unwrap()
1440 .wasm
1441 .is_empty()
1442 );
1443 assert!(
1444 !compile_package_checked("test", ModulePath::from("lib"), &modules, &[])
1445 .unwrap()
1446 .wasm
1447 .is_empty()
1448 );
1449 }
1450
1451 #[test]
1452 fn typed_allocation_failure_keeps_prior_inference_diagnostics() {
1453 let source = "function main(): number { let value: number = \"wrong\"; return 2; }";
1454 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1455 assert!(!diagnostics.is_empty());
1456 for (arena, count) in [
1457 (ArenaKind::TypedExpressions, ast.exprs_len()),
1458 (ArenaKind::TypedStatements, ast.stmts_len()),
1459 ] {
1460 let error = with_node_limit(arena, u32::try_from(count - 1).unwrap(), || {
1461 compile_script_checked(source, "limit.ts", FileId(0), &[], &[])
1462 })
1463 .unwrap_err();
1464 assert!(!error.diagnostics.is_empty(), "{error:?}");
1465 assert_limit(error);
1466 }
1467 }
1468
1469 #[test]
1470 fn codegen_allocation_failure_returns_no_wasm() {
1471 let source = "function main(): number { let value: number | null = 1; if (value !== null) { return value; } return 0; }";
1472 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1473 assert!(diagnostics.is_empty());
1474 let ast = desugar(capture(ast).unwrap(), FileId(0)).unwrap();
1475 let (prelude, host, internal) = prelude::cached_runtime_package_declarations();
1476 let dependencies: Vec<_> = prelude.iter().chain(host).chain(internal).collect();
1477 let failure = with_node_limit(
1478 ArenaKind::TypedExpressions,
1479 u32::try_from(ast.exprs_len()).unwrap(),
1480 || crate::codegen::codegen(source, "limit.ts", FileId(0), &ast, &dependencies),
1481 )
1482 .unwrap_err();
1483 assert!(
1484 matches!(
1485 failure,
1486 CompilerFailure::Limit {
1487 stage: CompilerStage::Codegen,
1488 span: None,
1489 ..
1490 }
1491 ),
1492 "{failure}"
1493 );
1494 assert!(
1495 !crate::codegen::codegen(source, "healthy.ts", FileId(0), &ast, &dependencies)
1496 .unwrap()
1497 .is_empty()
1498 );
1499 }
1500
1501 #[test]
1502 fn desugaring_capacity_failure_does_not_return_a_transformed_tree() {
1503 let source = "function main(): void { do {} while (false); }";
1504 let (ast, diagnostics) = typecheck_to_typed_ast(source, FileId(0));
1505 assert!(diagnostics.is_empty());
1506 for (arena, count) in [
1507 (ArenaKind::TypedExpressions, ast.exprs_len()),
1508 (ArenaKind::TypedStatements, ast.stmts_len()),
1509 ] {
1510 let failure = with_node_limit(arena, u32::try_from(count).unwrap(), || {
1511 desugar(ast.clone(), FileId(0))
1512 })
1513 .unwrap_err();
1514 assert_limit(failure.into());
1515 }
1516 desugar(ast, FileId(0)).unwrap();
1517 }
1518}