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 resolve_workspace_dependency,
35};
36use leo_passes::*;
37use leo_span::{
38 Span,
39 Symbol,
40 create_session_if_not_set_then,
41 file_source::{DiskFileSource, FileSource},
42 source_map::FileName,
43 with_session_globals,
44};
45
46use std::{
47 fs,
48 path::{Path, PathBuf},
49 rc::Rc,
50};
51
52use indexmap::{IndexMap, map::Entry};
53
54pub struct FrontendAnalysis<'a> {
56 pub ast: &'a Ast,
58 pub symbol_table: &'a SymbolTable,
60 pub type_table: &'a TypeTable,
62}
63
64pub struct LoadedImportStubs {
66 pub stubs: IndexMap<Symbol, Stub>,
68 pub watch_paths: Vec<PathBuf>,
70}
71
72pub struct CompiledProgram {
74 pub name: String,
76 pub bytecode: String,
78 pub abi: leo_abi::Program,
80}
81
82pub struct Compiled {
84 pub primary: CompiledProgram,
86 pub imports: Vec<CompiledProgram>,
88 pub interfaces: Vec<leo_abi::interfaces::CompiledInterface>,
90}
91
92pub struct Compiler {
94 output_directory: PathBuf,
96 pub unit_name: Option<String>,
98 compiler_options: CompilerOptions,
100 state: CompilerState,
102 import_stubs: IndexMap<Symbol, Stub>,
104 pub statements_before_dce: u32,
106 pub statements_after_dce: u32,
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 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 program_scope = program.program_scopes.values().next().unwrap();
147 if let Some(unit_name) = &self.unit_name {
148 if unit_name != &program_scope.program_id.as_symbol().to_string() {
149 return Err(crate::errors::program_name_should_match_file_name(
150 program_scope.program_id.as_symbol(),
151 if self.state.is_test {
153 format!(
154 "`{}` (the test file name)",
155 filename.to_string().split("/").last().expect("Could not get file name")
156 )
157 } else {
158 format!("`{unit_name}` (specified in `program.json`)")
159 },
160 program_scope.program_id.span(),
161 )
162 .into());
163 }
164 } else {
165 self.unit_name = Some(program_scope.program_id.as_symbol().to_string());
166 }
167
168 self.state.ast = Ast::Program(program);
169
170 if self.compiler_options.initial_ast {
171 self.write_ast_to_json("initial.json")?;
172 self.write_ast("initial.ast")?;
173 }
174
175 Ok(())
176 }
177
178 pub fn parse_and_return_program(
180 &mut self,
181 source: &str,
182 filename: FileName,
183 modules: &[(&str, FileName)],
184 ) -> Result<Program> {
185 self.parse_program(source, filename, modules)?;
187
188 match &self.state.ast {
189 Ast::Program(program) => Ok(program.clone()),
190 Ast::Library(_) => unreachable!("expected Program AST"),
191 }
192 }
193
194 pub fn parse_and_return_library(
196 &mut self,
197 library_name: &str,
198 source: &str,
199 filename: FileName,
200 modules: &[(&str, FileName)],
201 ) -> Result<Library> {
202 self.parse_library(Symbol::intern(library_name), source, filename, modules)?;
203
204 match &self.state.ast {
205 Ast::Program(_) => unreachable!("expected Library AST"),
206 Ast::Library(library) => Ok(library.clone()),
207 }
208 }
209
210 pub fn parse_library(
215 &mut self,
216 library_name: Symbol,
217 source: &str,
218 filename: FileName,
219 modules: &[(&str, FileName)],
220 ) -> Result<()> {
221 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
222
223 let module_files = modules
225 .iter()
226 .map(|(src, name)| with_session_globals(|s| s.source_map.new_source(src, name.clone())))
227 .collect::<Vec<_>>();
228
229 self.state.ast = Ast::Library(leo_parser::parse_library(
230 self.state.handler.clone(),
231 &self.state.node_builder,
232 library_name,
233 &source_file,
234 &module_files,
235 self.state.network,
236 )?);
237
238 if self.unit_name.is_none() {
242 self.unit_name = Some(library_name.to_string());
243 }
244
245 Ok(())
246 }
247
248 pub fn analyze_frontend_from_directory_with_file_source(
255 &mut self,
256 entry_file_path: impl AsRef<Path>,
257 source_directory: impl AsRef<Path>,
258 file_source: &impl FileSource,
259 ) -> Result<FrontendAnalysis<'_>> {
260 self.analyze_frontend_from_directory_with_file_source_and_check(
261 entry_file_path,
262 source_directory,
263 file_source,
264 || Ok(()),
265 )
266 }
267
268 pub fn analyze_frontend_from_directory_with_file_source_and_check<C>(
272 &mut self,
273 entry_file_path: impl AsRef<Path>,
274 source_directory: impl AsRef<Path>,
275 file_source: &impl FileSource,
276 mut should_continue: C,
277 ) -> Result<FrontendAnalysis<'_>>
278 where
279 C: FnMut() -> Result<()>,
280 {
281 should_continue()?;
282 let is_library = self.unit_name.as_deref().is_some_and(|name| !name.ends_with(".aleo"));
283
284 if is_library {
285 let library_name = Symbol::intern(self.unit_name.as_deref().expect("library analysis requires a name"));
286 self.parse_library_from_directory_with_file_source(
287 library_name,
288 &entry_file_path,
289 &source_directory,
290 file_source,
291 )?;
292 } else {
293 self.parse_program_from_directory_with_file_source(&entry_file_path, &source_directory, file_source)?;
294 self.add_import_stubs()?;
295 }
296
297 should_continue()?;
300 self.frontend_passes_with_check(&mut should_continue)?;
301
302 Ok(FrontendAnalysis {
303 ast: &self.state.ast,
304 symbol_table: &self.state.symbol_table,
305 type_table: &self.state.type_table,
306 })
307 }
308
309 #[allow(clippy::too_many_arguments)]
311 pub fn new(
312 expected_unit_name: Option<String>,
313 is_test: bool,
314 handler: Handler,
315 node_builder: Rc<NodeBuilder>,
316 output_directory: PathBuf,
317 compiler_options: Option<CompilerOptions>,
318 import_stubs: IndexMap<Symbol, Stub>,
319 network: NetworkName,
320 ) -> Self {
321 Self {
322 state: CompilerState {
323 handler,
324 node_builder: Rc::clone(&node_builder),
325 is_test,
326 network,
327 ..Default::default()
328 },
329 output_directory,
330 unit_name: expected_unit_name,
331 compiler_options: compiler_options.unwrap_or_default(),
332 import_stubs,
333 statements_before_dce: 0,
334 statements_after_dce: 0,
335 }
336 }
337
338 pub fn do_pass<P: Pass>(&mut self, input: P::Input) -> Result<P::Output> {
340 self.do_pass_with_check::<P, _>(input, &mut || Ok(()))
341 }
342
343 fn do_pass_with_check<P: Pass, C>(&mut self, input: P::Input, should_continue: &mut C) -> Result<P::Output>
346 where
347 C: FnMut() -> Result<()>,
348 {
349 let output = P::do_pass(input, &mut self.state)?;
350
351 let write = match &self.compiler_options.ast_snapshots {
352 AstSnapshots::All => true,
353 AstSnapshots::Some(passes) => passes.contains(P::NAME),
354 };
355
356 if write {
357 self.write_ast_to_json(&format!("{}.json", P::NAME))?;
358 self.write_ast(&format!("{}.ast", P::NAME))?;
359 }
360
361 should_continue()?;
362 Ok(output)
363 }
364
365 pub fn frontend_passes(&mut self) -> Result<()> {
367 self.frontend_passes_with_check(|| Ok(()))
368 }
369
370 pub fn frontend_passes_with_check<C>(&mut self, mut should_continue: C) -> Result<()>
372 where
373 C: FnMut() -> Result<()>,
374 {
375 self.state.handler.last_err()?;
378
379 self.do_pass_with_check::<NameValidation, _>((), &mut should_continue)?;
380 self.do_pass_with_check::<GlobalVarsCollection, _>((), &mut should_continue)?;
381 self.do_pass_with_check::<PathResolution, _>((), &mut should_continue)?;
382 self.do_pass_with_check::<GlobalItemsCollection, _>((), &mut should_continue)?;
383 self.do_pass_with_check::<CheckInterfaces, _>((), &mut should_continue)?;
384 self.do_pass_with_check::<TypeChecking, _>(TypeCheckingInput::new(self.state.network), &mut should_continue)?;
385 self.do_pass_with_check::<Disambiguate, _>((), &mut should_continue)?;
386 self.do_pass_with_check::<CeiAnalyzing, _>((), &mut should_continue)?;
387 self.do_pass_with_check::<ProcessingAsync, _>(
388 TypeCheckingInput::new(self.state.network),
389 &mut should_continue,
390 )?;
391 self.do_pass_with_check::<StaticAnalyzing, _>((), &mut should_continue)?;
392 Ok(())
393 }
394
395 pub fn intermediate_passes(
401 &mut self,
402 ) -> Result<(leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>)>
403 {
404 let type_checking_config = TypeCheckingInput::new(self.state.network);
405
406 self.frontend_passes()?;
407
408 self.do_pass::<ConstPropUnrollAndMorphing>(type_checking_config.clone())?;
409
410 let abis = self.generate_abi();
413
414 self.do_pass::<StorageLowering>(type_checking_config.clone())?;
415
416 self.do_pass::<OptionLowering>(type_checking_config)?;
417
418 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: true })?;
419
420 self.do_pass::<Destructuring>(())?;
421
422 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
423
424 self.do_pass::<WriteTransforming>(())?;
425
426 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
427
428 self.do_pass::<Flattening>(())?;
429
430 self.do_pass::<FunctionInlining>(())?;
431
432 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
434
435 self.do_pass::<SsaConstPropagation>(())?;
436
437 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
438
439 self.do_pass::<CommonSubexpressionEliminating>(())?;
440
441 let output = self.do_pass::<DeadCodeEliminating>(())?;
442 self.statements_before_dce = output.statements_before;
443 self.statements_after_dce = output.statements_after;
444
445 Ok(abis)
446 }
447
448 fn generate_abi(
455 &self,
456 ) -> (leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>) {
457 let program = match &self.state.ast {
458 Ast::Program(program) => program,
459 Ast::Library(_) => panic!("expected Program AST"),
460 };
461
462 let primary_abi = leo_abi::generate(program);
464
465 let interface_abis = leo_abi::interfaces::generate_program_interfaces(program);
467
468 let import_abis: IndexMap<String, leo_abi::Program> = program
470 .stubs
471 .iter()
472 .filter(|(_, stub)| !matches!(stub, Stub::FromLibrary { .. }))
473 .map(|(name, stub)| {
474 let abi = match stub {
475 Stub::FromLeo { program, .. } => leo_abi::generate(program),
476 Stub::FromAleo { program, .. } => leo_abi::aleo::generate(program),
477 Stub::FromLibrary { .. } => unreachable!("filtered out"),
478 };
479 (name.to_string(), abi)
480 })
481 .collect();
482
483 (primary_abi, import_abis, interface_abis)
484 }
485
486 pub fn generate_library_interface_abis(&self) -> Vec<leo_abi::interfaces::CompiledInterface> {
490 let Ast::Library(library) = &self.state.ast else {
491 panic!("expected Library AST");
492 };
493 leo_abi::interfaces::generate_library_interfaces(library)
494 }
495
496 pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result<Compiled> {
511 self.parse_program(source, filename, modules)?;
513 self.add_import_stubs()?;
515 let (primary_abi, import_abis, interfaces) = self.intermediate_passes()?;
517 let generated = self.do_pass::<CodeGenerating>(())?;
519 let bytecodes = self.do_pass::<PeepholeOptimizing>(generated)?;
521
522 let primary = CompiledProgram {
524 name: self.unit_name.clone().unwrap(),
525 bytecode: bytecodes.primary_bytecode,
526 abi: primary_abi,
527 };
528
529 let imports: Vec<CompiledProgram> = bytecodes
531 .import_bytecodes
532 .into_iter()
533 .map(|bc| {
534 let abi = import_abis.get(&bc.program_name).expect("ABI should exist for all imports").clone();
535 CompiledProgram { name: bc.program_name, bytecode: bc.bytecode, abi }
536 })
537 .collect();
538
539 Ok(Compiled { primary, imports, interfaces })
540 }
541
542 fn read_sources_and_modules(
558 file_source: &impl FileSource,
559 entry_file_path: impl AsRef<Path>,
560 source_directory: impl AsRef<Path>,
561 ) -> Result<(String, Vec<(String, FileName)>)> {
562 let entry_file_path = entry_file_path.as_ref();
563 let source_directory = source_directory.as_ref();
564
565 let source = file_source
567 .read_file(entry_file_path)
568 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
569
570 let files = file_source
571 .list_leo_files(source_directory, entry_file_path)
572 .map_err(|e| crate::errors::file_read_error(source_directory.display().to_string(), e))?;
573
574 let mut modules = Vec::with_capacity(files.len());
575 for path in files {
576 let module_source = file_source
577 .read_file(&path)
578 .map_err(|e| crate::errors::file_read_error(path.display().to_string(), e))?;
579 modules.push((module_source, FileName::Real(path)));
580 }
581
582 Ok((source, modules))
583 }
584
585 pub fn compile_from_directory(
587 &mut self,
588 entry_file_path: impl AsRef<Path>,
589 source_directory: impl AsRef<Path>,
590 ) -> Result<Compiled> {
591 self.compile_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
592 }
593
594 pub fn compile_from_directory_with_file_source(
596 &mut self,
597 entry_file_path: impl AsRef<Path>,
598 source_directory: impl AsRef<Path>,
599 file_source: &impl FileSource,
600 ) -> Result<Compiled> {
601 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
602
603 let module_refs: Vec<(&str, FileName)> =
605 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
606
607 self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
609 }
610
611 pub fn parse_program_from_directory(
613 &mut self,
614 entry_file_path: impl AsRef<Path>,
615 source_directory: impl AsRef<Path>,
616 ) -> Result<Program> {
617 self.parse_program_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
618 }
619
620 pub fn parse_program_from_directory_with_file_source(
622 &mut self,
623 entry_file_path: impl AsRef<Path>,
624 source_directory: impl AsRef<Path>,
625 file_source: &impl FileSource,
626 ) -> Result<Program> {
627 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
628
629 let module_refs: Vec<(&str, FileName)> =
631 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
632
633 self.parse_program(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
635
636 match &self.state.ast {
637 Ast::Program(program) => Ok(program.clone()),
638 Ast::Library(_) => unreachable!("expected Program AST"),
639 }
640 }
641
642 pub fn parse_library_from_directory(
644 &mut self,
645 library_name: Symbol,
646 entry_file_path: impl AsRef<Path>,
647 source_directory: impl AsRef<Path>,
648 ) -> Result<Library> {
649 self.parse_library_from_directory_with_file_source(
650 library_name,
651 entry_file_path,
652 source_directory,
653 &DiskFileSource,
654 )
655 }
656
657 pub fn parse_library_from_directory_with_file_source(
659 &mut self,
660 library_name: Symbol,
661 entry_file_path: impl AsRef<Path>,
662 source_directory: impl AsRef<Path>,
663 file_source: &impl FileSource,
664 ) -> Result<Library> {
665 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
666
667 let module_refs: Vec<(&str, FileName)> =
668 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
669
670 self.parse_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
671
672 match &self.state.ast {
673 Ast::Library(library) => Ok(library.clone()),
674 Ast::Program(_) => unreachable!("expected Library AST"),
675 }
676 }
677
678 fn write_ast_to_json(&self, filename: &str) -> Result<()> {
680 match &self.state.ast {
681 Ast::Program(program) => {
682 fs::create_dir_all(&self.output_directory)
684 .map_err(|e| crate::errors::failed_ast_file(self.output_directory.display(), e))?;
685 if self.compiler_options.ast_spans_enabled {
687 program.to_json_file(self.output_directory.clone(), filename)?;
688 } else {
689 program.to_json_file_without_keys(self.output_directory.clone(), filename, &["_span", "span"])?;
690 }
691 }
692 Ast::Library(_) => {
693 }
695 }
696 Ok(())
697 }
698
699 fn write_ast(&self, filename: &str) -> Result<()> {
701 fs::create_dir_all(&self.output_directory)
703 .map_err(|e| crate::errors::failed_ast_file(self.output_directory.display(), e))?;
704 let full_filename = self.output_directory.join(filename);
705
706 let contents = match &self.state.ast {
707 Ast::Program(program) => program.to_string(),
708 Ast::Library(_) => String::new(), };
710
711 fs::write(&full_filename, contents).map_err(|e| crate::errors::failed_ast_file(full_filename.display(), e))?;
712
713 Ok(())
714 }
715
716 pub fn add_import_stubs(&mut self) -> Result<()> {
731 use indexmap::IndexSet;
732
733 let mut explored = IndexSet::<Symbol>::new();
735
736 let initial_imports: IndexMap<Symbol, Span> = match &self.state.ast {
738 Ast::Program(program) => {
739 let mut map: IndexMap<Symbol, Span> =
740 program.imports.iter().map(|(name, id)| (*name, id.span())).collect();
741 for (stub_name, stub) in &self.import_stubs {
743 if matches!(stub, Stub::FromLibrary { .. })
744 && stub.parents().contains(&Symbol::intern(self.unit_name.as_ref().unwrap()))
745 {
746 map.insert(
747 *stub_name,
748 Span::default(), );
750 }
751 }
752 map
753 }
754 Ast::Library(_) => {
755 let library_name = Symbol::intern(self.unit_name.as_ref().unwrap());
759 self.import_stubs
760 .iter()
761 .filter(|(_, stub)| stub.parents().contains(&library_name))
762 .map(|(name, _)| (*name, Span::default()))
763 .collect()
764 }
765 };
766
767 let mut to_explore: Vec<(Symbol, Span)> = initial_imports.iter().map(|(sym, span)| (*sym, *span)).collect();
769
770 if let Some(main_program_name) = self.unit_name.clone() {
772 let main_symbol = Symbol::intern(&main_program_name);
773 for import in initial_imports.keys() {
774 if let Some(child_stub) = self.import_stubs.get_mut(import) {
775 child_stub.add_parent(main_symbol);
776 }
777 }
778 }
779
780 while let Some((import_symbol, span)) = to_explore.pop() {
782 explored.insert(import_symbol);
784
785 let Some(stub) = self.import_stubs.get(&import_symbol) else {
787 return Err(crate::errors::imported_program_not_found(
788 self.unit_name.as_ref().unwrap(),
789 import_symbol,
790 span,
791 )
792 .into());
793 };
794
795 let mut combined_imports: IndexMap<Symbol, Span> = stub.explicit_imports().collect();
797 for (lib_name, lib_stub) in &self.import_stubs {
798 if matches!(lib_stub, Stub::FromLibrary { .. }) && lib_stub.parents().contains(&import_symbol) {
799 combined_imports.insert(
800 *lib_name,
801 Span::default(), );
803 }
804 }
805
806 for (child_symbol, child_span) in combined_imports {
807 if let Some(child_stub) = self.import_stubs.get_mut(&child_symbol) {
809 child_stub.add_parent(import_symbol);
810 }
811
812 if explored.insert(child_symbol) {
814 to_explore.push((child_symbol, child_span));
815 }
816 }
817 }
818
819 let reachable: IndexMap<Symbol, Stub> = self
821 .import_stubs
822 .iter()
823 .filter(|(symbol, _)| explored.contains(*symbol))
824 .map(|(symbol, stub)| (*symbol, stub.clone()))
825 .collect();
826 match &mut self.state.ast {
827 Ast::Program(program) => program.stubs = reachable,
828 Ast::Library(library) => library.stubs = reachable,
829 }
830
831 Ok(())
832 }
833
834 pub fn build_library(
840 &mut self,
841 library_name: Symbol,
842 source: &str,
843 filename: FileName,
844 modules: &[(&str, FileName)],
845 ) -> Result<Library> {
846 self.parse_library(library_name, source, filename, modules)?;
847 self.add_import_stubs()?;
848 self.frontend_passes()?;
849
850 match &self.state.ast {
851 Ast::Library(library) => Ok(library.clone()),
852 Ast::Program(_) => unreachable!("expected Library AST"),
853 }
854 }
855
856 pub fn build_library_from_directory(
858 &mut self,
859 library_name: Symbol,
860 entry_file_path: impl AsRef<Path>,
861 source_directory: impl AsRef<Path>,
862 ) -> Result<Library> {
863 self.build_library_from_directory_with_file_source(
864 library_name,
865 entry_file_path,
866 source_directory,
867 &DiskFileSource,
868 )
869 }
870
871 pub fn build_library_from_directory_with_file_source(
873 &mut self,
874 library_name: Symbol,
875 entry_file_path: impl AsRef<Path>,
876 source_directory: impl AsRef<Path>,
877 file_source: &impl FileSource,
878 ) -> Result<Library> {
879 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
880
881 let module_refs: Vec<(&str, FileName)> =
882 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
883
884 self.build_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
885 }
886}
887
888pub fn load_import_stubs_for_package(package_root: &Path, network: NetworkName) -> Result<LoadedImportStubs> {
898 load_import_stubs_for_package_with_file_source(package_root, network, &DiskFileSource)
899}
900
901pub fn load_import_stubs_for_package_with_file_source(
908 package_root: &Path,
909 network: NetworkName,
910 file_source: &impl FileSource,
911) -> Result<LoadedImportStubs> {
912 create_session_if_not_set_then(|_| {
913 let package_root =
914 package_root.canonicalize().map_err(|error| crate::errors::failed_path(package_root.display(), error))?;
915 let declared_dependencies = collect_local_declared_dependencies(&package_root)?;
916 let mut import_stubs = IndexMap::new();
917 let mut watch_paths = vec![package_root.join(MANIFEST_FILENAME)];
918
919 for (name, dependency) in &declared_dependencies {
920 let Some(path) = dependency.path.as_ref() else {
921 continue;
922 };
923
924 let unit = if path.extension().is_some_and(|extension| extension == "aleo") && path.is_file() {
925 watch_paths.push(path.clone());
926 CompilationUnit::from_aleo_path(*name, path, &declared_dependencies)?
927 } else {
928 let unit = CompilationUnit::from_package_path(*name, path)?;
929 watch_paths.extend(unit_watch_paths(&unit, file_source)?);
930 unit
931 };
932
933 let stub = match &unit.data {
934 ProgramData::Bytecode(bytecode) => disassemble_dependency_bytecode(unit.name, bytecode, network)?,
935 ProgramData::SourcePath { directory, source } => load_source_dependency_stub(
936 &unit,
937 source,
938 dependency_source_directory(directory, source),
939 network,
940 file_source,
941 )?,
942 };
943 import_stubs.insert(unit.name, stub);
944 }
945
946 watch_paths.sort();
947 watch_paths.dedup();
948
949 Ok(LoadedImportStubs { stubs: import_stubs, watch_paths })
950 })
951}
952
953fn dependency_source_directory(directory: &Path, source: &Path) -> PathBuf {
955 let source_root = directory.join("src");
956 if source.starts_with(&source_root) { source_root } else { directory.to_path_buf() }
957}
958
959fn collect_local_declared_dependencies(package_root: &Path) -> Result<IndexMap<Symbol, Dependency>> {
964 let manifest = Manifest::read_from_file(package_root.join(MANIFEST_FILENAME))?;
965 let mut declared = IndexMap::new();
966 collect_local_declared_dependencies_recursive(package_root, &manifest, &mut declared)?;
967 Ok(declared)
968}
969
970fn collect_local_declared_dependencies_recursive(
972 base_path: &Path,
973 manifest: &Manifest,
974 declared: &mut IndexMap<Symbol, Dependency>,
975) -> Result<()> {
976 for dependency in manifest.dependencies.iter().flatten() {
977 let dependency = normalize_local_dependency(base_path, dependency.clone())?;
978 let dependency = if dependency.location == Location::Workspace {
980 resolve_workspace_dependency(base_path, dependency)?
981 } else {
982 dependency
983 };
984 if dependency.location != Location::Local {
985 continue;
986 }
987
988 let Some(path) = dependency.path.as_ref() else {
989 continue;
990 };
991 let symbol = Symbol::intern(&dependency.name);
992
993 match declared.entry(symbol) {
994 Entry::Occupied(_) => continue,
995 Entry::Vacant(entry) => {
996 entry.insert(dependency.clone());
997 let manifest_path = path.join(MANIFEST_FILENAME);
998 if path.is_dir() && manifest_path.is_file() {
999 let child = Manifest::read_from_file(manifest_path)?;
1000 collect_local_declared_dependencies_recursive(path, &child, declared)?;
1001 }
1002 }
1003 }
1004 }
1005
1006 Ok(())
1007}
1008
1009fn normalize_local_dependency(base_path: &Path, mut dependency: Dependency) -> Result<Dependency> {
1011 if let Some(path) = dependency.path.as_mut()
1012 && !path.is_absolute()
1013 {
1014 let joined = base_path.join(&*path);
1015 *path = joined.canonicalize().map_err(|error| crate::errors::failed_path(joined.display(), error))?;
1016 }
1017
1018 Ok(dependency)
1019}
1020
1021fn unit_watch_paths(unit: &CompilationUnit, file_source: &impl FileSource) -> Result<Vec<PathBuf>> {
1023 let ProgramData::SourcePath { directory, source } = &unit.data else {
1024 return Ok(Vec::new());
1025 };
1026
1027 let source_directory = dependency_source_directory(directory, source);
1028 let mut watch_paths = vec![directory.join(MANIFEST_FILENAME), source_directory.clone(), source.clone()];
1029 if source_directory.is_dir() {
1030 collect_source_directories(&source_directory, &mut watch_paths)?;
1031 let mut modules = file_source
1032 .list_leo_files(&source_directory, source)
1033 .map_err(|error| crate::errors::file_read_error(source_directory.display().to_string(), error))?;
1034 watch_paths.append(&mut modules);
1035 }
1036
1037 Ok(watch_paths)
1038}
1039
1040fn collect_source_directories(dir: &Path, watch_paths: &mut Vec<PathBuf>) -> Result<()> {
1042 for entry in fs::read_dir(dir).map_err(|error| errors::file_read_error(dir.display().to_string(), error))? {
1043 let entry = entry.map_err(|error| errors::file_read_error(dir.display().to_string(), error))?;
1044 let path = entry.path();
1045 if path.is_dir() {
1046 watch_paths.push(path.clone());
1050 collect_source_directories(&path, watch_paths)?;
1051 }
1052 }
1053 Ok(())
1054}
1055
1056fn load_source_dependency_stub(
1059 unit: &CompilationUnit,
1060 source: &Path,
1061 source_directory: PathBuf,
1062 network: NetworkName,
1063 file_source: &impl FileSource,
1064) -> Result<Stub> {
1065 let handler = Handler::default();
1066 let node_builder = Rc::new(NodeBuilder::default());
1067 let mut compiler = Compiler::new(
1068 Some(unit.name.to_string()),
1069 false,
1070 handler,
1071 node_builder,
1072 PathBuf::default(),
1073 Some(CompilerOptions::default()),
1074 IndexMap::new(),
1075 network,
1076 );
1077
1078 match unit.kind {
1079 PackageKind::Library => {
1080 let library_name = Symbol::intern(&unit.name.to_string());
1081 let library = compiler.parse_library_from_directory_with_file_source(
1082 library_name,
1083 source,
1084 &source_directory,
1085 file_source,
1086 )?;
1087 Ok(library.into())
1088 }
1089 PackageKind::Program | PackageKind::Test => {
1090 let program =
1091 compiler.parse_program_from_directory_with_file_source(source, &source_directory, file_source)?;
1092 Ok(extract_program_interface_stub(unit.name, &program))
1093 }
1094 }
1095}
1096
1097fn extract_program_interface_stub(_program_name: Symbol, program: &Program) -> Stub {
1099 let scope = program.program_scopes.values().next().expect("program AST should contain one program scope");
1100
1101 let functions = scope
1106 .functions
1107 .iter()
1108 .map(|(sym, func)| {
1109 (*sym, FunctionStub {
1110 annotations: func.annotations.clone(),
1111 variant: func.variant,
1112 identifier: func.identifier,
1113 input: func.input.clone(),
1114 output: func.output.clone(),
1115 output_type: func.output_type.clone(),
1116 span: func.span,
1117 id: func.id,
1118 })
1119 })
1120 .collect();
1121
1122 let imports = program
1123 .imports
1124 .keys()
1125 .map(|sym| {
1126 let sym_str = sym.to_string();
1127 let name_only = sym_str.strip_suffix(".aleo").unwrap_or(&sym_str);
1131 ProgramId {
1132 name: Identifier { name: Symbol::intern(name_only), span: Default::default(), id: Default::default() },
1133 network: Identifier { name: Symbol::intern("aleo"), span: Default::default(), id: Default::default() },
1134 }
1135 })
1136 .collect();
1137
1138 AleoProgram {
1139 imports,
1140 stub_id: scope.program_id,
1141 consts: scope.consts.clone(),
1142 composites: scope.composites.clone(),
1143 mappings: scope.mappings.clone(),
1144 functions,
1145 span: scope.span,
1146 }
1147 .into()
1148}
1149
1150fn disassemble_dependency_bytecode(program_name: Symbol, bytecode: &str, network: NetworkName) -> Result<Stub> {
1154 let disassembled = match network {
1155 NetworkName::MainnetV0 => {
1156 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::MainnetV0>(program_name, bytecode)
1157 }
1158 NetworkName::TestnetV0 => {
1159 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::TestnetV0>(program_name, bytecode)
1160 }
1161 NetworkName::CanaryV0 => {
1162 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::CanaryV0>(program_name, bytecode)
1163 }
1164 };
1165
1166 disassembled
1167 .map(Into::into)
1168 .map_err(|err| crate::errors::file_read_error(format!("dependency bytecode for `{program_name}`"), err).into())
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173 use super::Compiler;
1174
1175 use leo_ast::{NetworkName, NodeBuilder};
1176 use leo_errors::{BufferEmitter, Handler};
1177 use leo_span::{Symbol, create_session_if_not_set_then, file_source::InMemoryFileSource};
1178
1179 use std::{path::PathBuf, rc::Rc};
1180
1181 use indexmap::IndexMap;
1182
1183 #[test]
1185 fn parse_library_from_directory_in_memory() {
1186 create_session_if_not_set_then(|_| {
1187 let mut source = InMemoryFileSource::new();
1188 source.set(
1189 PathBuf::from("/mylib/src/lib.leo"),
1190 concat!("const SCALE: u32 = 10u32;\n", "const OFFSET: u32 = SCALE + 1u32;\n",).into(),
1191 );
1192
1193 let handler = Handler::default();
1194 let node_builder = Rc::new(NodeBuilder::default());
1195 let mut compiler = Compiler::new(
1196 None,
1197 false,
1198 handler,
1199 node_builder,
1200 PathBuf::from("/unused"),
1201 None,
1202 IndexMap::new(),
1203 NetworkName::TestnetV0,
1204 );
1205
1206 let library = compiler
1207 .parse_library_from_directory_with_file_source(
1208 Symbol::intern("mylib"),
1209 "/mylib/src/lib.leo",
1210 "/mylib/src",
1211 &source,
1212 )
1213 .unwrap_or_else(|err| panic!("parsing library from in-memory file source failed: {err}"));
1214
1215 assert_eq!(library.name, Symbol::intern("mylib"));
1216 assert_eq!(library.consts.len(), 2, "expected 2 consts, got {}", library.consts.len());
1217 assert!(
1218 library.consts.iter().any(|(name, _)| *name == Symbol::intern("SCALE")),
1219 "expected const `SCALE` in library"
1220 );
1221 assert!(
1222 library.consts.iter().any(|(name, _)| *name == Symbol::intern("OFFSET")),
1223 "expected const `OFFSET` in library"
1224 );
1225 });
1226 }
1227
1228 #[test]
1230 fn build_library_from_directory_in_memory_rejects_type_error() {
1231 create_session_if_not_set_then(|_| {
1232 let mut source = InMemoryFileSource::new();
1233 source
1235 .set(PathBuf::from("/badlib/src/lib.leo"), "fn broken() -> u32 {\n return true + 1u32;\n}\n".into());
1236
1237 let emitter = BufferEmitter::new();
1239 let handler = Handler::new(emitter.clone());
1240 let node_builder = Rc::new(NodeBuilder::default());
1241 let mut compiler = Compiler::new(
1242 Some("badlib".into()),
1243 false,
1244 handler,
1245 node_builder,
1246 PathBuf::from("/unused"),
1247 None,
1248 IndexMap::new(),
1249 NetworkName::TestnetV0,
1250 );
1251
1252 let result = compiler.build_library_from_directory_with_file_source(
1253 Symbol::intern("badlib"),
1254 "/badlib/src/lib.leo",
1255 "/badlib/src",
1256 &source,
1257 );
1258
1259 assert!(result.is_err(), "expected build_library to fail on a library with a type error");
1260
1261 let errors = emitter.extract_errs().to_string();
1262 assert!(errors.contains("ETYC"), "expected a type-checking error (prefix `ETYC`) but captured:\n{errors}");
1263 });
1264 }
1265
1266 #[test]
1268 fn parse_program_from_directory_in_memory_with_module() {
1269 create_session_if_not_set_then(|_| {
1270 let mut source = InMemoryFileSource::new();
1271 source.set(
1272 PathBuf::from("/project/src/main.leo"),
1273 concat!(
1274 "program test.aleo {\n",
1275 " fn main() -> u32 {\n",
1276 " return utils::helper();\n",
1277 " }\n",
1278 "}\n",
1279 )
1280 .into(),
1281 );
1282 source.set(PathBuf::from("/project/src/utils.leo"), "fn helper() -> u32 {\n return 42u32;\n}\n".into());
1283
1284 let handler = Handler::default();
1285 let node_builder = Rc::new(NodeBuilder::default());
1286 let mut compiler = Compiler::new(
1287 Some("test.aleo".into()),
1288 false,
1289 handler,
1290 node_builder,
1291 PathBuf::from("/unused"),
1292 None,
1293 IndexMap::new(),
1294 NetworkName::TestnetV0,
1295 );
1296
1297 let ast = compiler
1298 .parse_program_from_directory_with_file_source("/project/src/main.leo", "/project/src", &source)
1299 .unwrap_or_else(|err| panic!("parsing from in-memory file source failed: {err}"));
1300 let utils_key = vec![Symbol::intern("utils")];
1301
1302 assert!(
1303 ast.modules.contains_key(&utils_key),
1304 "module `utils` should be loaded from the in-memory file source; found keys: {:?}",
1305 ast.modules.keys().collect::<Vec<_>>()
1306 );
1307 });
1308 }
1309}