1use crate::{AstSnapshots, CompilerOptions, errors};
22
23use leo_ast::{AleoProgram, FunctionStub, Identifier, Library, NetworkName, NodeBuilder, ProgramId, Stub};
24pub use leo_ast::{Ast, DiGraph, Program};
25use leo_errors::{Handler, Result};
26use leo_package::{
27 CompilationUnit,
28 Dependency,
29 Location,
30 MANIFEST_FILENAME,
31 Manifest,
32 PackageKind,
33 ProgramData,
34 bare_unit_name,
35 resolve_workspace_dependency,
36};
37use leo_passes::*;
38use leo_span::{
39 Span,
40 Symbol,
41 create_session_if_not_set_then,
42 file_source::{DiskFileSource, FileSource},
43 source_map::FileName,
44 with_session_globals,
45};
46
47use std::{
48 fs,
49 path::{Path, PathBuf},
50 rc::Rc,
51};
52
53use indexmap::{IndexMap, map::Entry};
54
55pub struct FrontendAnalysis<'a> {
57 pub ast: &'a Ast,
59 pub symbol_table: &'a SymbolTable,
61 pub type_table: &'a TypeTable,
63}
64
65pub struct LoadedImportStubs {
67 pub stubs: IndexMap<Symbol, Stub>,
69 pub watch_paths: Vec<PathBuf>,
71}
72
73pub struct CompiledProgram {
75 pub name: String,
77 pub bytecode: String,
79 pub abi: leo_abi::Program,
81}
82
83pub struct Compiled {
85 pub primary: CompiledProgram,
87 pub imports: Vec<CompiledProgram>,
89 pub interfaces: Vec<leo_abi::interfaces::CompiledInterface>,
91}
92
93pub struct Compiler {
95 output_directory: PathBuf,
97 pub unit_name: Option<String>,
99 pub rename: Option<String>,
102 compiler_options: CompilerOptions,
104 state: CompilerState,
106 import_stubs: IndexMap<Symbol, Stub>,
108}
109
110impl Compiler {
111 pub fn network(&self) -> NetworkName {
113 self.state.network
114 }
115
116 pub fn parse_program(&mut self, source: &str, filename: FileName, modules: &[(&str, FileName)]) -> Result<()> {
126 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
128
129 let modules = modules
131 .iter()
132 .map(|(source, filename)| with_session_globals(|s| s.source_map.new_source(source, filename.clone())))
133 .collect::<Vec<_>>();
134
135 let mut program = leo_parser::parse_program(
137 self.state.handler.clone(),
138 &self.state.node_builder,
139 &source_file,
140 &modules,
141 self.state.network,
142 )?;
143
144 let (source_name, source_span) = {
148 let program_id = &program.program_scopes.values().next().unwrap().program_id;
149 (program_id.as_symbol().to_string(), program_id.span())
150 };
151
152 if let Some(rename) = self.rename.clone() {
153 let bare = bare_unit_name(&rename);
154 let program_scope = program.program_scopes.values_mut().next().unwrap();
155 program_scope.program_id.name.name = Symbol::intern(bare);
156 self.unit_name = Some(rename);
157 } else if let Some(unit_name) = &self.unit_name {
158 if unit_name != &source_name {
160 return Err(crate::errors::program_name_should_match_file_name(
161 Symbol::intern(&source_name),
162 if self.state.is_test {
164 format!(
165 "`{}` (the test file name)",
166 filename.to_string().split("/").last().expect("Could not get file name")
167 )
168 } else {
169 format!("`{unit_name}` (specified in `program.json`)")
170 },
171 source_span,
172 )
173 .into());
174 }
175 } else {
176 self.unit_name = Some(source_name);
177 }
178
179 self.state.ast = Ast::Program(program);
180
181 if self.compiler_options.initial_ast {
182 self.write_ast_to_json("initial.json")?;
183 self.write_ast("initial.ast")?;
184 }
185
186 Ok(())
187 }
188
189 pub fn parse_and_return_program(
191 &mut self,
192 source: &str,
193 filename: FileName,
194 modules: &[(&str, FileName)],
195 ) -> Result<Program> {
196 self.parse_program(source, filename, modules)?;
198
199 match &self.state.ast {
200 Ast::Program(program) => Ok(program.clone()),
201 Ast::Library(_) => unreachable!("expected Program AST"),
202 }
203 }
204
205 pub fn parse_and_return_library(
207 &mut self,
208 library_name: &str,
209 source: &str,
210 filename: FileName,
211 modules: &[(&str, FileName)],
212 ) -> Result<Library> {
213 self.parse_library(Symbol::intern(library_name), source, filename, modules)?;
214
215 match &self.state.ast {
216 Ast::Program(_) => unreachable!("expected Library AST"),
217 Ast::Library(library) => Ok(library.clone()),
218 }
219 }
220
221 pub fn parse_library(
226 &mut self,
227 library_name: Symbol,
228 source: &str,
229 filename: FileName,
230 modules: &[(&str, FileName)],
231 ) -> Result<()> {
232 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
233
234 let module_files = modules
236 .iter()
237 .map(|(src, name)| with_session_globals(|s| s.source_map.new_source(src, name.clone())))
238 .collect::<Vec<_>>();
239
240 self.state.ast = Ast::Library(leo_parser::parse_library(
241 self.state.handler.clone(),
242 &self.state.node_builder,
243 library_name,
244 &source_file,
245 &module_files,
246 self.state.network,
247 )?);
248
249 if self.unit_name.is_none() {
253 self.unit_name = Some(library_name.to_string());
254 }
255
256 if self.compiler_options.initial_ast {
257 self.write_ast_to_json("initial.json")?;
258 self.write_ast("initial.ast")?;
259 }
260
261 Ok(())
262 }
263
264 pub fn analyze_frontend_from_directory_with_file_source(
271 &mut self,
272 entry_file_path: impl AsRef<Path>,
273 source_directory: impl AsRef<Path>,
274 file_source: &impl FileSource,
275 ) -> Result<FrontendAnalysis<'_>> {
276 self.analyze_frontend_from_directory_with_file_source_and_check(
277 entry_file_path,
278 source_directory,
279 file_source,
280 || Ok(()),
281 )
282 }
283
284 pub fn analyze_frontend_from_directory_with_file_source_and_check<C>(
288 &mut self,
289 entry_file_path: impl AsRef<Path>,
290 source_directory: impl AsRef<Path>,
291 file_source: &impl FileSource,
292 mut should_continue: C,
293 ) -> Result<FrontendAnalysis<'_>>
294 where
295 C: FnMut() -> Result<()>,
296 {
297 should_continue()?;
298 let is_library = self.unit_name.as_deref().is_some_and(|name| !name.ends_with(".aleo"));
299
300 if is_library {
301 let library_name = Symbol::intern(self.unit_name.as_deref().expect("library analysis requires a name"));
302 self.parse_library_from_directory_with_file_source(
303 library_name,
304 &entry_file_path,
305 &source_directory,
306 file_source,
307 )?;
308 } else {
309 self.parse_program_from_directory_with_file_source(&entry_file_path, &source_directory, file_source)?;
310 self.add_import_stubs()?;
311 }
312
313 should_continue()?;
316 self.frontend_passes_with_check(&mut should_continue)?;
317
318 Ok(FrontendAnalysis {
319 ast: &self.state.ast,
320 symbol_table: &self.state.symbol_table,
321 type_table: &self.state.type_table,
322 })
323 }
324
325 #[allow(clippy::too_many_arguments)]
327 pub fn new(
328 expected_unit_name: Option<String>,
329 is_test: bool,
330 handler: Handler,
331 node_builder: Rc<NodeBuilder>,
332 output_directory: PathBuf,
333 compiler_options: Option<CompilerOptions>,
334 import_stubs: IndexMap<Symbol, Stub>,
335 network: NetworkName,
336 ) -> Self {
337 Self {
338 state: CompilerState {
339 handler,
340 node_builder: Rc::clone(&node_builder),
341 is_test,
342 network,
343 ..Default::default()
344 },
345 output_directory,
346 unit_name: expected_unit_name,
347 rename: None,
348 compiler_options: compiler_options.unwrap_or_default(),
349 import_stubs,
350 }
351 }
352
353 pub fn do_pass<P: Pass>(&mut self, input: P::Input) -> Result<P::Output> {
355 self.do_pass_with_check::<P, _>(input, &mut || Ok(()))
356 }
357
358 fn do_pass_with_check<P: Pass, C>(&mut self, input: P::Input, should_continue: &mut C) -> Result<P::Output>
361 where
362 C: FnMut() -> Result<()>,
363 {
364 let output = P::do_pass(input, &mut self.state)?;
365
366 let write = match &self.compiler_options.ast_snapshots {
367 AstSnapshots::All => true,
368 AstSnapshots::Some(passes) => passes.contains(P::NAME),
369 };
370
371 if write {
372 self.write_ast_to_json(&format!("{}.json", P::NAME))?;
373 self.write_ast(&format!("{}.ast", P::NAME))?;
374 }
375
376 should_continue()?;
377 Ok(output)
378 }
379
380 pub fn frontend_passes(&mut self) -> Result<()> {
382 self.frontend_passes_with_check(|| Ok(()))
383 }
384
385 pub fn frontend_passes_with_check<C>(&mut self, mut should_continue: C) -> Result<()>
387 where
388 C: FnMut() -> Result<()>,
389 {
390 self.state.handler.last_err()?;
393
394 self.do_pass_with_check::<NameValidation, _>((), &mut should_continue)?;
395 self.do_pass_with_check::<GlobalVarsCollection, _>((), &mut should_continue)?;
396 self.do_pass_with_check::<PathResolution, _>((), &mut should_continue)?;
397 self.do_pass_with_check::<GlobalItemsCollection, _>((), &mut should_continue)?;
398 self.do_pass_with_check::<CheckInterfaces, _>((), &mut should_continue)?;
399 self.do_pass_with_check::<TypeChecking, _>(TypeCheckingInput::new(self.state.network), &mut should_continue)?;
400 self.do_pass_with_check::<Disambiguate, _>((), &mut should_continue)?;
401 self.do_pass_with_check::<CeiAnalyzing, _>((), &mut should_continue)?;
402 self.do_pass_with_check::<ProcessingAsync, _>(
403 TypeCheckingInput::new(self.state.network),
404 &mut should_continue,
405 )?;
406 self.do_pass_with_check::<StaticAnalyzing, _>((), &mut should_continue)?;
407 Ok(())
408 }
409
410 pub fn intermediate_passes(
416 &mut self,
417 ) -> Result<(leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>)>
418 {
419 let type_checking_config = TypeCheckingInput::new(self.state.network);
420
421 self.frontend_passes()?;
422
423 self.do_pass::<LibraryPruning>(())?;
425
426 self.do_pass::<ConstPropUnrollAndMorphing>(type_checking_config.clone())?;
427
428 let abis = self.generate_abi();
431
432 self.do_pass::<StorageLowering>(type_checking_config.clone())?;
433
434 self.do_pass::<OptionLowering>(type_checking_config)?;
435
436 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: true })?;
437
438 self.do_pass::<Destructuring>(())?;
439
440 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
441
442 self.do_pass::<WriteTransforming>(())?;
443
444 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
445
446 self.do_pass::<Flattening>(())?;
447
448 self.do_pass::<FunctionInlining>(())?;
449
450 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
452
453 self.do_pass::<SsaConstPropagation>(())?;
454
455 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
456
457 self.do_pass::<CommonSubexpressionEliminating>(())?;
458
459 self.do_pass::<DeadCodeEliminating>(())?;
460
461 Ok(abis)
462 }
463
464 fn generate_abi(
471 &self,
472 ) -> (leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>) {
473 let program = match &self.state.ast {
474 Ast::Program(program) => program,
475 Ast::Library(_) => panic!("expected Program AST"),
476 };
477
478 let primary_abi = leo_abi::generate(program);
480
481 let interface_abis = leo_abi::interfaces::generate_program_interfaces(program);
483
484 let import_abis: IndexMap<String, leo_abi::Program> = program
486 .stubs
487 .iter()
488 .filter(|(_, stub)| !matches!(stub, Stub::FromLibrary { .. }))
489 .map(|(name, stub)| {
490 let abi = match stub {
491 Stub::FromLeo { program, .. } => leo_abi::generate(program),
492 Stub::FromAleo { program, .. } => leo_abi::aleo::generate(program),
493 Stub::FromLibrary { .. } => unreachable!("filtered out"),
494 };
495 (name.to_string(), abi)
496 })
497 .collect();
498
499 (primary_abi, import_abis, interface_abis)
500 }
501
502 pub fn generate_library_interface_abis(&self) -> Vec<leo_abi::interfaces::CompiledInterface> {
506 let Ast::Library(library) = &self.state.ast else {
507 panic!("expected Library AST");
508 };
509 leo_abi::interfaces::generate_library_interfaces(library)
510 }
511
512 pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result<Compiled> {
527 self.parse_program(source, filename, modules)?;
529 self.add_import_stubs()?;
531 let (primary_abi, import_abis, interfaces) = self.intermediate_passes()?;
533 let generated = self.do_pass::<CodeGenerating>(())?;
535 let bytecodes = self.do_pass::<PeepholeOptimizing>(generated)?;
537
538 let primary = CompiledProgram {
540 name: self.unit_name.clone().unwrap(),
541 bytecode: bytecodes.primary_bytecode,
542 abi: primary_abi,
543 };
544
545 let imports: Vec<CompiledProgram> = bytecodes
547 .import_bytecodes
548 .into_iter()
549 .map(|bc| {
550 let abi = import_abis.get(&bc.program_name).expect("ABI should exist for all imports").clone();
551 CompiledProgram { name: bc.program_name, bytecode: bc.bytecode, abi }
552 })
553 .collect();
554
555 Ok(Compiled { primary, imports, interfaces })
556 }
557
558 fn read_sources_and_modules(
574 file_source: &impl FileSource,
575 entry_file_path: impl AsRef<Path>,
576 source_directory: impl AsRef<Path>,
577 ) -> Result<(String, Vec<(String, FileName)>)> {
578 let entry_file_path = entry_file_path.as_ref();
579 let source_directory = source_directory.as_ref();
580
581 let source = file_source
583 .read_file(entry_file_path)
584 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
585
586 let files = file_source
587 .list_leo_files(source_directory, entry_file_path)
588 .map_err(|e| crate::errors::file_read_error(source_directory.display().to_string(), e))?;
589
590 let mut modules = Vec::with_capacity(files.len());
591 for path in files {
592 let module_source = file_source
593 .read_file(&path)
594 .map_err(|e| crate::errors::file_read_error(path.display().to_string(), e))?;
595 modules.push((module_source, FileName::Real(path)));
596 }
597
598 Ok((source, modules))
599 }
600
601 pub fn compile_from_directory(
603 &mut self,
604 entry_file_path: impl AsRef<Path>,
605 source_directory: impl AsRef<Path>,
606 ) -> Result<Compiled> {
607 self.compile_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
608 }
609
610 pub fn compile_from_directory_with_file_source(
612 &mut self,
613 entry_file_path: impl AsRef<Path>,
614 source_directory: impl AsRef<Path>,
615 file_source: &impl FileSource,
616 ) -> Result<Compiled> {
617 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
618
619 let module_refs: Vec<(&str, FileName)> =
621 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
622
623 self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
625 }
626
627 pub fn compile_from_file(&mut self, entry_file_path: impl AsRef<Path>) -> Result<Compiled> {
632 self.compile_from_file_with_file_source(entry_file_path, &DiskFileSource)
633 }
634
635 pub fn compile_from_file_with_file_source(
637 &mut self,
638 entry_file_path: impl AsRef<Path>,
639 file_source: &impl FileSource,
640 ) -> Result<Compiled> {
641 let entry_file_path = entry_file_path.as_ref();
642 let source = file_source
643 .read_file(entry_file_path)
644 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
645 self.compile(&source, FileName::Real(entry_file_path.into()), &Vec::new())
646 }
647
648 pub fn parse_program_from_directory(
650 &mut self,
651 entry_file_path: impl AsRef<Path>,
652 source_directory: impl AsRef<Path>,
653 ) -> Result<Program> {
654 self.parse_program_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
655 }
656
657 pub fn parse_program_from_directory_with_file_source(
659 &mut self,
660 entry_file_path: impl AsRef<Path>,
661 source_directory: impl AsRef<Path>,
662 file_source: &impl FileSource,
663 ) -> Result<Program> {
664 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
665
666 let module_refs: Vec<(&str, FileName)> =
668 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
669
670 self.parse_program(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
672
673 match &self.state.ast {
674 Ast::Program(program) => Ok(program.clone()),
675 Ast::Library(_) => unreachable!("expected Program AST"),
676 }
677 }
678
679 pub fn parse_program_from_file(&mut self, entry_file_path: impl AsRef<Path>) -> Result<Program> {
684 self.parse_program_from_file_with_file_source(entry_file_path, &DiskFileSource)
685 }
686
687 pub fn parse_program_from_file_with_file_source(
689 &mut self,
690 entry_file_path: impl AsRef<Path>,
691 file_source: &impl FileSource,
692 ) -> Result<Program> {
693 let entry_file_path = entry_file_path.as_ref();
694 let source = file_source
695 .read_file(entry_file_path)
696 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
697 self.parse_program(&source, FileName::Real(entry_file_path.into()), &[])?;
698
699 match &self.state.ast {
700 Ast::Program(program) => Ok(program.clone()),
701 Ast::Library(_) => unreachable!("expected Program AST"),
702 }
703 }
704
705 pub fn parse_library_from_directory(
707 &mut self,
708 library_name: Symbol,
709 entry_file_path: impl AsRef<Path>,
710 source_directory: impl AsRef<Path>,
711 ) -> Result<Library> {
712 self.parse_library_from_directory_with_file_source(
713 library_name,
714 entry_file_path,
715 source_directory,
716 &DiskFileSource,
717 )
718 }
719
720 pub fn parse_library_from_directory_with_file_source(
722 &mut self,
723 library_name: Symbol,
724 entry_file_path: impl AsRef<Path>,
725 source_directory: impl AsRef<Path>,
726 file_source: &impl FileSource,
727 ) -> Result<Library> {
728 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
729
730 let module_refs: Vec<(&str, FileName)> =
731 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
732
733 self.parse_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
734
735 match &self.state.ast {
736 Ast::Library(library) => Ok(library.clone()),
737 Ast::Program(_) => unreachable!("expected Library AST"),
738 }
739 }
740
741 fn write_ast_to_json(&self, filename: &str) -> Result<()> {
743 if self.output_directory.as_os_str().is_empty() {
745 return Ok(());
746 }
747 fs::create_dir_all(&self.output_directory)
749 .map_err(|e| crate::errors::failed_ast_file(self.output_directory.display(), e))?;
750 let dir = self.output_directory.clone();
751 if self.compiler_options.ast_spans_enabled {
752 match &self.state.ast {
753 Ast::Program(program) => leo_ast::write_ast_json(program, dir, filename)?,
754 Ast::Library(library) => leo_ast::write_ast_json(library, dir, filename)?,
755 }
756 } else {
757 match &self.state.ast {
758 Ast::Program(program) => leo_ast::write_ast_json_filtered(program, dir, filename, &["_span", "span"])?,
759 Ast::Library(library) => leo_ast::write_ast_json_filtered(library, dir, filename, &["_span", "span"])?,
760 }
761 }
762 Ok(())
763 }
764
765 fn write_ast(&self, filename: &str) -> Result<()> {
767 if self.output_directory.as_os_str().is_empty() {
769 return Ok(());
770 }
771 fs::create_dir_all(&self.output_directory)
773 .map_err(|e| crate::errors::failed_ast_file(self.output_directory.display(), e))?;
774 let full_filename = self.output_directory.join(filename);
775
776 let contents = self.state.ast.to_string();
777
778 fs::write(&full_filename, contents).map_err(|e| crate::errors::failed_ast_file(full_filename.display(), e))?;
779
780 Ok(())
781 }
782
783 pub fn add_import_stubs(&mut self) -> Result<()> {
798 use indexmap::IndexSet;
799
800 self.inject_std_library()?;
802
803 let mut explored = IndexSet::<Symbol>::new();
805
806 let initial_imports: IndexMap<Symbol, Span> = match &self.state.ast {
808 Ast::Program(program) => {
809 let mut map: IndexMap<Symbol, Span> =
810 program.imports.iter().map(|(name, id)| (*name, id.span())).collect();
811 for (stub_name, stub) in &self.import_stubs {
813 if matches!(stub, Stub::FromLibrary { .. })
814 && stub.parents().contains(&Symbol::intern(self.unit_name.as_ref().unwrap()))
815 {
816 map.insert(
817 *stub_name,
818 Span::default(), );
820 }
821 }
822 map
823 }
824 Ast::Library(_) => {
825 let library_name = Symbol::intern(self.unit_name.as_ref().unwrap());
829 self.import_stubs
830 .iter()
831 .filter(|(_, stub)| stub.parents().contains(&library_name))
832 .map(|(name, _)| (*name, Span::default()))
833 .collect()
834 }
835 };
836
837 let mut to_explore: Vec<(Symbol, Span)> = initial_imports.iter().map(|(sym, span)| (*sym, *span)).collect();
839
840 if let Some(main_program_name) = self.unit_name.clone() {
842 let main_symbol = Symbol::intern(&main_program_name);
843 for import in initial_imports.keys() {
844 if let Some(child_stub) = self.import_stubs.get_mut(import) {
845 child_stub.add_parent(main_symbol);
846 }
847 }
848 }
849
850 while let Some((import_symbol, span)) = to_explore.pop() {
852 explored.insert(import_symbol);
854
855 let Some(stub) = self.import_stubs.get(&import_symbol) else {
857 return Err(crate::errors::imported_program_not_found(
858 self.unit_name.as_ref().unwrap(),
859 import_symbol,
860 span,
861 )
862 .into());
863 };
864
865 let mut combined_imports: IndexMap<Symbol, Span> = stub.explicit_imports().collect();
867 for (lib_name, lib_stub) in &self.import_stubs {
868 if matches!(lib_stub, Stub::FromLibrary { .. }) && lib_stub.parents().contains(&import_symbol) {
869 combined_imports.insert(
870 *lib_name,
871 Span::default(), );
873 }
874 }
875
876 for (child_symbol, child_span) in combined_imports {
877 if let Some(child_stub) = self.import_stubs.get_mut(&child_symbol) {
879 child_stub.add_parent(import_symbol);
880 }
881
882 if explored.insert(child_symbol) {
884 to_explore.push((child_symbol, child_span));
885 }
886 }
887 }
888
889 let reachable: IndexMap<Symbol, Stub> = self
891 .import_stubs
892 .iter()
893 .filter(|(symbol, _)| explored.contains(*symbol))
894 .map(|(symbol, stub)| (*symbol, stub.clone()))
895 .collect();
896 match &mut self.state.ast {
897 Ast::Program(program) => program.stubs = reachable,
898 Ast::Library(library) => library.stubs = reachable,
899 }
900
901 Ok(())
902 }
903
904 pub fn build_std_stub(handler: Handler, node_builder: Rc<NodeBuilder>, network: NetworkName) -> Result<Stub> {
910 let std_name = Symbol::intern(leo_std::library_name());
911
912 let mut sub_compiler = Compiler::new(
913 Some(leo_std::library_name().to_string()),
914 false,
915 handler,
916 node_builder,
917 PathBuf::new(),
918 Some(CompilerOptions {
919 no_std: true,
921 ..CompilerOptions::default()
922 }),
923 IndexMap::new(),
924 network,
925 );
926
927 let module_refs: Vec<(&str, FileName)> =
928 leo_std::modules().iter().map(|(path, source)| (*source, FileName::Custom((*path).to_string()))).collect();
929
930 let library = sub_compiler.build_library_inner(
932 std_name,
933 leo_std::entry_source(),
934 FileName::Custom(format!("<{}>", leo_std::library_name())),
935 &module_refs,
936 false,
937 )?;
938
939 Ok(library.into())
940 }
941
942 fn inject_std_library(&mut self) -> Result<()> {
946 if self.compiler_options.no_std {
947 return Ok(());
948 }
949
950 let std_name = Symbol::intern(leo_std::library_name());
951 let parent = Symbol::intern(self.unit_name.as_deref().expect("Cannot get unit name"));
952
953 if let Some(existing) = self.import_stubs.get_mut(&std_name) {
954 existing.add_parent(parent);
955 return Ok(());
956 }
957
958 let mut stub =
959 Self::build_std_stub(self.state.handler.clone(), Rc::clone(&self.state.node_builder), self.state.network)?;
960 stub.add_parent(parent);
961 self.import_stubs.insert(std_name, stub);
962 Ok(())
963 }
964
965 pub fn build_library(
971 &mut self,
972 library_name: Symbol,
973 source: &str,
974 filename: FileName,
975 modules: &[(&str, FileName)],
976 ) -> Result<Library> {
977 self.build_library_inner(library_name, source, filename, modules, true)
978 }
979
980 fn build_library_inner(
985 &mut self,
986 library_name: Symbol,
987 source: &str,
988 filename: FileName,
989 modules: &[(&str, FileName)],
990 run_frontend: bool,
991 ) -> Result<Library> {
992 self.parse_library(library_name, source, filename, modules)?;
993 self.add_import_stubs()?;
994 if run_frontend {
995 self.frontend_passes()?;
996 }
997
998 match &self.state.ast {
999 Ast::Library(library) => Ok(library.clone()),
1000 Ast::Program(_) => unreachable!("expected Library AST"),
1001 }
1002 }
1003
1004 pub fn build_library_from_directory(
1006 &mut self,
1007 library_name: Symbol,
1008 entry_file_path: impl AsRef<Path>,
1009 source_directory: impl AsRef<Path>,
1010 ) -> Result<Library> {
1011 self.build_library_from_directory_with_file_source(
1012 library_name,
1013 entry_file_path,
1014 source_directory,
1015 &DiskFileSource,
1016 )
1017 }
1018
1019 pub fn build_library_from_directory_with_file_source(
1021 &mut self,
1022 library_name: Symbol,
1023 entry_file_path: impl AsRef<Path>,
1024 source_directory: impl AsRef<Path>,
1025 file_source: &impl FileSource,
1026 ) -> Result<Library> {
1027 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
1028
1029 let module_refs: Vec<(&str, FileName)> =
1030 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
1031
1032 self.build_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
1033 }
1034}
1035
1036pub fn load_import_stubs_for_package(package_root: &Path, network: NetworkName) -> Result<LoadedImportStubs> {
1046 load_import_stubs_for_package_with_file_source(package_root, network, &DiskFileSource)
1047}
1048
1049pub fn load_import_stubs_for_package_with_file_source(
1056 package_root: &Path,
1057 network: NetworkName,
1058 file_source: &impl FileSource,
1059) -> Result<LoadedImportStubs> {
1060 create_session_if_not_set_then(|_| {
1061 let package_root =
1062 package_root.canonicalize().map_err(|error| crate::errors::failed_path(package_root.display(), error))?;
1063 let declared_dependencies = collect_local_declared_dependencies(&package_root)?;
1064 let mut import_stubs = IndexMap::new();
1065 let mut watch_paths = vec![package_root.join(MANIFEST_FILENAME)];
1066
1067 for (name, dependency) in &declared_dependencies {
1068 let Some(path) = dependency.path.as_ref() else {
1069 continue;
1070 };
1071
1072 let unit = if path.extension().is_some_and(|extension| extension == "aleo") && path.is_file() {
1073 watch_paths.push(path.clone());
1074 CompilationUnit::from_aleo_path(*name, path, &declared_dependencies)?
1075 } else {
1076 let unit = CompilationUnit::from_package_path(*name, path)?;
1077 watch_paths.extend(unit_watch_paths(&unit, file_source)?);
1078 unit
1079 };
1080
1081 let stub = match &unit.data {
1082 ProgramData::Bytecode(bytecode) => disassemble_dependency_bytecode(unit.name, bytecode, network)?,
1083 ProgramData::SourcePath { directory, source } => load_source_dependency_stub(
1084 &unit,
1085 source,
1086 dependency_source_directory(directory, source),
1087 network,
1088 file_source,
1089 )?,
1090 };
1091 import_stubs.insert(unit.name, stub);
1092 }
1093
1094 watch_paths.sort();
1095 watch_paths.dedup();
1096
1097 Ok(LoadedImportStubs { stubs: import_stubs, watch_paths })
1098 })
1099}
1100
1101fn dependency_source_directory(directory: &Path, source: &Path) -> PathBuf {
1103 let source_root = directory.join("src");
1104 if source.starts_with(&source_root) { source_root } else { directory.to_path_buf() }
1105}
1106
1107fn collect_local_declared_dependencies(package_root: &Path) -> Result<IndexMap<Symbol, Dependency>> {
1112 let manifest = Manifest::read_from_file(package_root.join(MANIFEST_FILENAME))?;
1113 let mut declared = IndexMap::new();
1114 collect_local_declared_dependencies_recursive(package_root, &manifest, &mut declared)?;
1115 Ok(declared)
1116}
1117
1118fn collect_local_declared_dependencies_recursive(
1120 base_path: &Path,
1121 manifest: &Manifest,
1122 declared: &mut IndexMap<Symbol, Dependency>,
1123) -> Result<()> {
1124 for dependency in manifest.dependencies.iter().flatten() {
1125 let dependency = normalize_local_dependency(base_path, dependency.clone())?;
1126 let dependency = if dependency.location == Location::Workspace {
1128 resolve_workspace_dependency(base_path, dependency)?
1129 } else {
1130 dependency
1131 };
1132 if dependency.location != Location::Local {
1133 continue;
1134 }
1135
1136 let Some(path) = dependency.path.as_ref() else {
1137 continue;
1138 };
1139 let symbol = Symbol::intern(&dependency.name);
1140
1141 match declared.entry(symbol) {
1142 Entry::Occupied(_) => continue,
1143 Entry::Vacant(entry) => {
1144 entry.insert(dependency.clone());
1145 let manifest_path = path.join(MANIFEST_FILENAME);
1146 if path.is_dir() && manifest_path.is_file() {
1147 let child = Manifest::read_from_file(manifest_path)?;
1148 collect_local_declared_dependencies_recursive(path, &child, declared)?;
1149 }
1150 }
1151 }
1152 }
1153
1154 Ok(())
1155}
1156
1157fn normalize_local_dependency(base_path: &Path, mut dependency: Dependency) -> Result<Dependency> {
1159 if let Some(path) = dependency.path.as_mut()
1160 && !path.is_absolute()
1161 {
1162 let joined = base_path.join(&*path);
1163 *path = joined.canonicalize().map_err(|error| crate::errors::failed_path(joined.display(), error))?;
1164 }
1165
1166 Ok(dependency)
1167}
1168
1169fn unit_watch_paths(unit: &CompilationUnit, file_source: &impl FileSource) -> Result<Vec<PathBuf>> {
1171 let ProgramData::SourcePath { directory, source } = &unit.data else {
1172 return Ok(Vec::new());
1173 };
1174
1175 let source_directory = dependency_source_directory(directory, source);
1176 let mut watch_paths = vec![directory.join(MANIFEST_FILENAME), source_directory.clone(), source.clone()];
1177 if source_directory.is_dir() {
1178 collect_source_directories(&source_directory, &mut watch_paths)?;
1179 let mut modules = file_source
1180 .list_leo_files(&source_directory, source)
1181 .map_err(|error| crate::errors::file_read_error(source_directory.display().to_string(), error))?;
1182 watch_paths.append(&mut modules);
1183 }
1184
1185 Ok(watch_paths)
1186}
1187
1188fn collect_source_directories(dir: &Path, watch_paths: &mut Vec<PathBuf>) -> Result<()> {
1190 for entry in fs::read_dir(dir).map_err(|error| errors::file_read_error(dir.display().to_string(), error))? {
1191 let entry = entry.map_err(|error| errors::file_read_error(dir.display().to_string(), error))?;
1192 let path = entry.path();
1193 if path.is_dir() {
1194 watch_paths.push(path.clone());
1198 collect_source_directories(&path, watch_paths)?;
1199 }
1200 }
1201 Ok(())
1202}
1203
1204fn load_source_dependency_stub(
1207 unit: &CompilationUnit,
1208 source: &Path,
1209 source_directory: PathBuf,
1210 network: NetworkName,
1211 file_source: &impl FileSource,
1212) -> Result<Stub> {
1213 let handler = Handler::default();
1214 let node_builder = Rc::new(NodeBuilder::default());
1215 let mut compiler = Compiler::new(
1216 Some(unit.name.to_string()),
1217 false,
1218 handler,
1219 node_builder,
1220 PathBuf::default(),
1221 Some(CompilerOptions::default()),
1222 IndexMap::new(),
1223 network,
1224 );
1225
1226 match unit.kind {
1227 PackageKind::Library => {
1228 let library_name = Symbol::intern(&unit.name.to_string());
1229 let library = compiler.parse_library_from_directory_with_file_source(
1230 library_name,
1231 source,
1232 &source_directory,
1233 file_source,
1234 )?;
1235 Ok(library.into())
1236 }
1237 PackageKind::Program | PackageKind::Test => {
1238 let program =
1239 compiler.parse_program_from_directory_with_file_source(source, &source_directory, file_source)?;
1240 Ok(extract_program_interface_stub(unit.name, &program))
1241 }
1242 }
1243}
1244
1245fn extract_program_interface_stub(_program_name: Symbol, program: &Program) -> Stub {
1247 let scope = program.program_scopes.values().next().expect("program AST should contain one program scope");
1248
1249 let functions = scope
1254 .functions
1255 .iter()
1256 .map(|(sym, func)| {
1257 (*sym, FunctionStub {
1258 annotations: func.annotations.clone(),
1259 variant: func.variant,
1260 identifier: func.identifier,
1261 input: func.input.clone(),
1262 output: func.output.clone(),
1263 output_type: func.output_type.clone(),
1264 span: func.span,
1265 id: func.id,
1266 })
1267 })
1268 .collect();
1269
1270 let imports = program
1271 .imports
1272 .keys()
1273 .map(|sym| {
1274 let sym_str = sym.to_string();
1275 let name_only = sym_str.strip_suffix(".aleo").unwrap_or(&sym_str);
1279 ProgramId {
1280 name: Identifier { name: Symbol::intern(name_only), span: Default::default(), id: Default::default() },
1281 network: Identifier { name: Symbol::intern("aleo"), span: Default::default(), id: Default::default() },
1282 }
1283 })
1284 .collect();
1285
1286 AleoProgram {
1287 imports,
1288 stub_id: scope.program_id,
1289 consts: scope.consts.clone(),
1290 composites: scope.composites.clone(),
1291 mappings: scope.mappings.clone(),
1292 functions,
1293 span: scope.span,
1294 }
1295 .into()
1296}
1297
1298fn disassemble_dependency_bytecode(program_name: Symbol, bytecode: &str, network: NetworkName) -> Result<Stub> {
1302 let disassembled = match network {
1303 NetworkName::MainnetV0 => {
1304 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::MainnetV0>(program_name, bytecode)
1305 }
1306 NetworkName::TestnetV0 => {
1307 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::TestnetV0>(program_name, bytecode)
1308 }
1309 NetworkName::CanaryV0 => {
1310 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::CanaryV0>(program_name, bytecode)
1311 }
1312 };
1313
1314 disassembled
1315 .map(Into::into)
1316 .map_err(|err| crate::errors::file_read_error(format!("dependency bytecode for `{program_name}`"), err).into())
1317}
1318
1319#[cfg(test)]
1320mod tests {
1321 use super::Compiler;
1322
1323 use leo_ast::{NetworkName, NodeBuilder};
1324 use leo_errors::{BufferEmitter, Handler};
1325 use leo_span::{Symbol, create_session_if_not_set_then, file_source::InMemoryFileSource, source_map::FileName};
1326
1327 use std::{path::PathBuf, rc::Rc};
1328
1329 use indexmap::IndexMap;
1330
1331 #[test]
1333 fn parse_library_from_directory_in_memory() {
1334 create_session_if_not_set_then(|_| {
1335 let mut source = InMemoryFileSource::new();
1336 source.set(
1337 PathBuf::from("/mylib/src/lib.leo"),
1338 concat!("const SCALE: u32 = 10u32;\n", "const OFFSET: u32 = SCALE + 1u32;\n",).into(),
1339 );
1340
1341 let handler = Handler::default();
1342 let node_builder = Rc::new(NodeBuilder::default());
1343 let mut compiler = Compiler::new(
1344 None,
1345 false,
1346 handler,
1347 node_builder,
1348 PathBuf::from("/unused"),
1349 None,
1350 IndexMap::new(),
1351 NetworkName::TestnetV0,
1352 );
1353
1354 let library = compiler
1355 .parse_library_from_directory_with_file_source(
1356 Symbol::intern("mylib"),
1357 "/mylib/src/lib.leo",
1358 "/mylib/src",
1359 &source,
1360 )
1361 .unwrap_or_else(|err| panic!("parsing library from in-memory file source failed: {err}"));
1362
1363 assert_eq!(library.name, Symbol::intern("mylib"));
1364 assert_eq!(library.consts.len(), 2, "expected 2 consts, got {}", library.consts.len());
1365 assert!(
1366 library.consts.iter().any(|(name, _)| *name == Symbol::intern("SCALE")),
1367 "expected const `SCALE` in library"
1368 );
1369 assert!(
1370 library.consts.iter().any(|(name, _)| *name == Symbol::intern("OFFSET")),
1371 "expected const `OFFSET` in library"
1372 );
1373 });
1374 }
1375
1376 #[test]
1378 fn build_library_from_directory_in_memory_rejects_type_error() {
1379 create_session_if_not_set_then(|_| {
1380 let mut source = InMemoryFileSource::new();
1381 source
1383 .set(PathBuf::from("/badlib/src/lib.leo"), "fn broken() -> u32 {\n return true + 1u32;\n}\n".into());
1384
1385 let emitter = BufferEmitter::new();
1387 let handler = Handler::new(emitter.clone());
1388 let node_builder = Rc::new(NodeBuilder::default());
1389 let mut compiler = Compiler::new(
1390 Some("badlib".into()),
1391 false,
1392 handler,
1393 node_builder,
1394 PathBuf::from("/unused"),
1395 None,
1396 IndexMap::new(),
1397 NetworkName::TestnetV0,
1398 );
1399
1400 let result = compiler.build_library_from_directory_with_file_source(
1401 Symbol::intern("badlib"),
1402 "/badlib/src/lib.leo",
1403 "/badlib/src",
1404 &source,
1405 );
1406
1407 assert!(result.is_err(), "expected build_library to fail on a library with a type error");
1408
1409 let errors = emitter.extract_errs().to_string();
1410 assert!(errors.contains("ETYC"), "expected a type-checking error (prefix `ETYC`) but captured:\n{errors}");
1411 });
1412 }
1413
1414 #[test]
1416 fn parse_program_from_directory_in_memory_with_module() {
1417 create_session_if_not_set_then(|_| {
1418 let mut source = InMemoryFileSource::new();
1419 source.set(
1420 PathBuf::from("/project/src/main.leo"),
1421 concat!(
1422 "program test.aleo {\n",
1423 " fn main() -> u32 {\n",
1424 " return utils::helper();\n",
1425 " }\n",
1426 "}\n",
1427 )
1428 .into(),
1429 );
1430 source.set(PathBuf::from("/project/src/utils.leo"), "fn helper() -> u32 {\n return 42u32;\n}\n".into());
1431
1432 let handler = Handler::default();
1433 let node_builder = Rc::new(NodeBuilder::default());
1434 let mut compiler = Compiler::new(
1435 Some("test.aleo".into()),
1436 false,
1437 handler,
1438 node_builder,
1439 PathBuf::from("/unused"),
1440 None,
1441 IndexMap::new(),
1442 NetworkName::TestnetV0,
1443 );
1444
1445 let ast = compiler
1446 .parse_program_from_directory_with_file_source("/project/src/main.leo", "/project/src", &source)
1447 .unwrap_or_else(|err| panic!("parsing from in-memory file source failed: {err}"));
1448 let utils_key = vec![Symbol::intern("utils")];
1449
1450 assert!(
1451 ast.modules.contains_key(&utils_key),
1452 "module `utils` should be loaded from the in-memory file source; found keys: {:?}",
1453 ast.modules.keys().collect::<Vec<_>>()
1454 );
1455 });
1456 }
1457
1458 #[test]
1462 fn rename_override_recompiles_under_new_name() {
1463 create_session_if_not_set_then(|_| {
1464 let handler = Handler::default();
1465 let node_builder = Rc::new(NodeBuilder::default());
1466 let mut compiler = Compiler::new(
1467 Some("foo.aleo".into()),
1468 false,
1469 handler,
1470 node_builder,
1471 PathBuf::from("/unused"),
1472 None,
1473 IndexMap::new(),
1474 NetworkName::TestnetV0,
1475 );
1476 compiler.rename = Some("bar.aleo".into());
1478
1479 let source = concat!(
1480 "program foo.aleo {\n",
1481 " record R {\n",
1482 " owner: address,\n",
1483 " x: bool,\n",
1484 " }\n",
1485 " @noupgrade\n",
1486 " constructor() {}\n",
1487 " fn foo() -> R {\n",
1488 " return R { owner: self.signer, x: true };\n",
1489 " }\n",
1490 "}\n",
1491 );
1492 let filename = FileName::Custom("main.leo".into());
1493 let modules: Vec<(&str, FileName)> = Vec::new();
1494 let compiled = compiler
1495 .compile(source, filename, &modules)
1496 .unwrap_or_else(|err| panic!("compiling with rename failed: {err}"));
1497
1498 assert_eq!(compiler.unit_name.as_deref(), Some("bar.aleo"), "unit name should adopt the rename");
1499 let bytecode = &compiled.primary.bytecode;
1500 assert!(bytecode.contains("program bar.aleo;"), "expected renamed header, got:\n{bytecode}");
1501 assert!(!bytecode.contains("program foo.aleo;"), "old name leaked into bytecode:\n{bytecode}");
1502 });
1503 }
1504
1505 #[test]
1510 fn std_library_passes_frontend() {
1511 create_session_if_not_set_then(|_| {
1512 let emitter = BufferEmitter::new();
1513 let handler = Handler::new(emitter.clone());
1514 let node_builder = Rc::new(NodeBuilder::default());
1515 let mut compiler = Compiler::new(
1516 Some(leo_std::library_name().to_string()),
1517 false,
1518 handler,
1519 node_builder,
1520 PathBuf::new(),
1521 Some(crate::CompilerOptions { no_std: true, ..Default::default() }),
1523 IndexMap::new(),
1524 NetworkName::TestnetV0,
1525 );
1526
1527 let module_refs: Vec<(&str, FileName)> = leo_std::modules()
1528 .iter()
1529 .map(|(path, source)| (*source, FileName::Custom((*path).to_string())))
1530 .collect();
1531
1532 let result = compiler.build_library(
1533 Symbol::intern(leo_std::library_name()),
1534 leo_std::entry_source(),
1535 FileName::Custom(format!("<{}>", leo_std::library_name())),
1536 &module_refs,
1537 );
1538
1539 assert!(result.is_ok(), "std failed to build: {:?}", result.err());
1540 let errors = emitter.extract_errs().to_string();
1541 assert!(errors.is_empty(), "std produced diagnostics:\n{errors}");
1542 });
1543 }
1544}