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