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