1use crate::{CompilerOptions, errors};
22
23use leo_ast::{
24 AleoProgram,
25 FunctionStub,
26 Identifier,
27 Library,
28 NetworkName,
29 NodeBuilder,
30 ProgramId,
31 Stub,
32 TypeInterner,
33};
34pub use leo_ast::{Ast, DiGraph, Program};
35use leo_errors::{Handler, Result};
36use leo_package::{
37 CompilationUnit,
38 Dependency,
39 Location,
40 MANIFEST_FILENAME,
41 Manifest,
42 PackageKind,
43 ProgramData,
44 bare_unit_name,
45 resolve_workspace_dependency,
46};
47use leo_passes::*;
48use leo_span::{
49 Span,
50 Symbol,
51 create_session_if_not_set_then,
52 file_source::{DiskFileSource, FileSource},
53 source_map::FileName,
54 with_session_globals,
55};
56
57use std::{
58 fs,
59 path::{Path, PathBuf},
60 rc::Rc,
61};
62
63use indexmap::{IndexMap, map::Entry};
64
65pub struct FrontendAnalysis<'a> {
67 pub ast: &'a Ast,
69 pub symbol_table: &'a SymbolTable,
71 pub type_table: &'a TypeTable,
73 pub types: &'a leo_ast::TypeInterner,
75}
76
77pub struct LoadedImportStubs {
79 pub stubs: IndexMap<Symbol, Stub>,
81 pub watch_paths: Vec<PathBuf>,
83}
84
85pub struct CompiledProgram {
87 pub name: String,
89 pub bytecode: String,
91 pub abi: leo_abi::Program,
93}
94
95pub struct Compiled {
97 pub primary: CompiledProgram,
99 pub imports: Vec<CompiledProgram>,
101 pub interfaces: Vec<leo_abi::interfaces::CompiledInterface>,
103}
104
105pub struct Compiler {
107 pub unit_name: Option<String>,
109 pub rename: Option<String>,
112 compiler_options: CompilerOptions,
114 state: CompilerState,
116 import_stubs: IndexMap<Symbol, Stub>,
118}
119
120impl Compiler {
121 pub fn network(&self) -> NetworkName {
123 self.state.network
124 }
125
126 pub fn parse_program(&mut self, source: &str, filename: FileName, modules: &[(&str, FileName)]) -> Result<()> {
136 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
138
139 let modules = modules
141 .iter()
142 .map(|(source, filename)| with_session_globals(|s| s.source_map.new_source(source, filename.clone())))
143 .collect::<Vec<_>>();
144
145 let mut program = leo_parser::parse_program(
147 self.state.handler.clone(),
148 &self.state.node_builder,
149 &self.state.types,
150 &source_file,
151 &modules,
152 self.state.network,
153 )?;
154
155 let (source_name, source_span) = {
159 let program_id = &program.program_scopes.values().next().unwrap().program_id;
160 (program_id.as_symbol().to_string(), program_id.span())
161 };
162
163 if let Some(rename) = self.rename.clone() {
164 let bare = bare_unit_name(&rename);
165 let program_scope = program.program_scopes.values_mut().next().unwrap();
166 program_scope.program_id.name.name = Symbol::intern(bare);
167 self.unit_name = Some(rename);
168 } else if let Some(unit_name) = &self.unit_name {
169 if unit_name != &source_name {
171 return Err(crate::errors::program_name_should_match_file_name(
172 Symbol::intern(&source_name),
173 if self.state.is_test {
175 format!(
176 "`{}` (the test file name)",
177 filename.to_string().split("/").last().expect("Could not get file name")
178 )
179 } else {
180 format!("`{unit_name}` (specified in `program.json`)")
181 },
182 source_span,
183 )
184 .into());
185 }
186 } else {
187 self.unit_name = Some(source_name);
188 }
189
190 self.state.ast = Ast::Program(program);
191
192 Ok(())
193 }
194
195 pub fn parse_and_return_program(
197 &mut self,
198 source: &str,
199 filename: FileName,
200 modules: &[(&str, FileName)],
201 ) -> Result<Program> {
202 self.parse_program(source, filename, modules)?;
204
205 match &self.state.ast {
206 Ast::Program(program) => Ok(program.clone()),
207 Ast::Library(_) => unreachable!("expected Program AST"),
208 }
209 }
210
211 pub fn parse_and_return_library(
213 &mut self,
214 library_name: &str,
215 source: &str,
216 filename: FileName,
217 modules: &[(&str, FileName)],
218 ) -> Result<Library> {
219 self.parse_library(Symbol::intern(library_name), source, filename, modules)?;
220
221 match &self.state.ast {
222 Ast::Program(_) => unreachable!("expected Library AST"),
223 Ast::Library(library) => Ok(library.clone()),
224 }
225 }
226
227 pub fn parse_library(
232 &mut self,
233 library_name: Symbol,
234 source: &str,
235 filename: FileName,
236 modules: &[(&str, FileName)],
237 ) -> Result<()> {
238 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
239
240 let module_files = modules
242 .iter()
243 .map(|(src, name)| with_session_globals(|s| s.source_map.new_source(src, name.clone())))
244 .collect::<Vec<_>>();
245
246 self.state.ast = Ast::Library(leo_parser::parse_library(
247 self.state.handler.clone(),
248 &self.state.node_builder,
249 &self.state.types,
250 library_name,
251 &source_file,
252 &module_files,
253 self.state.network,
254 )?);
255
256 if self.unit_name.is_none() {
260 self.unit_name = Some(library_name.to_string());
261 }
262
263 Ok(())
264 }
265
266 pub fn analyze_frontend_from_directory_with_file_source(
273 &mut self,
274 entry_file_path: impl AsRef<Path>,
275 source_directory: impl AsRef<Path>,
276 file_source: &impl FileSource,
277 ) -> Result<FrontendAnalysis<'_>> {
278 self.analyze_frontend_from_directory_with_file_source_and_check(
279 entry_file_path,
280 source_directory,
281 file_source,
282 || Ok(()),
283 )
284 }
285
286 pub fn analyze_frontend_from_directory_with_file_source_and_check<C>(
290 &mut self,
291 entry_file_path: impl AsRef<Path>,
292 source_directory: impl AsRef<Path>,
293 file_source: &impl FileSource,
294 mut should_continue: C,
295 ) -> Result<FrontendAnalysis<'_>>
296 where
297 C: FnMut() -> Result<()>,
298 {
299 should_continue()?;
300 let is_library = self.unit_name.as_deref().is_some_and(|name| !name.ends_with(".aleo"));
301
302 if is_library {
303 let library_name = Symbol::intern(self.unit_name.as_deref().expect("library analysis requires a name"));
304 self.parse_library_from_directory_with_file_source(
305 library_name,
306 &entry_file_path,
307 &source_directory,
308 file_source,
309 )?;
310 } else {
311 self.parse_program_from_directory_with_file_source(&entry_file_path, &source_directory, file_source)?;
312 self.add_import_stubs()?;
313 }
314
315 should_continue()?;
318 self.frontend_passes_with_check(&mut should_continue)?;
319
320 Ok(FrontendAnalysis {
321 ast: &self.state.ast,
322 symbol_table: &self.state.symbol_table,
323 type_table: &self.state.type_table,
324 types: &self.state.types,
325 })
326 }
327
328 #[allow(clippy::too_many_arguments)]
332 pub fn new(
333 expected_unit_name: Option<String>,
334 is_test: bool,
335 handler: Handler,
336 node_builder: Rc<NodeBuilder>,
337 compiler_options: Option<CompilerOptions>,
338 import_stubs: IndexMap<Symbol, Stub>,
339 network: NetworkName,
340 ) -> Self {
341 Self::new_with_types(
342 expected_unit_name,
343 is_test,
344 handler,
345 node_builder,
346 compiler_options,
347 import_stubs,
348 network,
349 Rc::new(TypeInterner::default()),
350 )
351 }
352
353 #[allow(clippy::too_many_arguments)]
356 pub fn new_with_types(
357 expected_unit_name: Option<String>,
358 is_test: bool,
359 handler: Handler,
360 node_builder: Rc<NodeBuilder>,
361 compiler_options: Option<CompilerOptions>,
362 import_stubs: IndexMap<Symbol, Stub>,
363 network: NetworkName,
364 types: Rc<TypeInterner>,
365 ) -> Self {
366 Self {
367 state: CompilerState {
368 handler,
369 node_builder: Rc::clone(&node_builder),
370 is_test,
371 network,
372 types,
373 ..Default::default()
374 },
375 unit_name: expected_unit_name,
376 rename: None,
377 compiler_options: compiler_options.unwrap_or_default(),
378 import_stubs,
379 }
380 }
381
382 pub fn do_pass<P: Pass>(&mut self, input: P::Input) -> Result<P::Output> {
384 self.do_pass_with_check::<P, _>(input, &mut || Ok(()))
385 }
386
387 fn do_pass_with_check<P: Pass, C>(&mut self, input: P::Input, should_continue: &mut C) -> Result<P::Output>
390 where
391 C: FnMut() -> Result<()>,
392 {
393 let output = P::do_pass(input, &mut self.state)?;
394
395 should_continue()?;
396 Ok(output)
397 }
398
399 pub fn frontend_passes(&mut self) -> Result<()> {
401 self.frontend_passes_with_check(|| Ok(()))
402 }
403
404 pub fn frontend_passes_with_check<C>(&mut self, mut should_continue: C) -> Result<()>
406 where
407 C: FnMut() -> Result<()>,
408 {
409 self.state.handler.last_err()?;
412
413 self.do_pass_with_check::<NameValidation, _>((), &mut should_continue)?;
414 self.do_pass_with_check::<GlobalVarsCollection, _>((), &mut should_continue)?;
415 self.do_pass_with_check::<PathResolution, _>((), &mut should_continue)?;
416 self.do_pass_with_check::<GlobalItemsCollection, _>((), &mut should_continue)?;
417 self.do_pass_with_check::<CheckInterfaces, _>((), &mut should_continue)?;
418 self.do_pass_with_check::<TypeChecking, _>(TypeCheckingInput::new(self.state.network), &mut should_continue)?;
419 self.do_pass_with_check::<Disambiguate, _>((), &mut should_continue)?;
420 self.do_pass_with_check::<CeiAnalyzing, _>((), &mut should_continue)?;
421 self.do_pass_with_check::<ProcessingAsync, _>(
422 TypeCheckingInput::new(self.state.network),
423 &mut should_continue,
424 )?;
425 self.do_pass_with_check::<StaticAnalyzing, _>((), &mut should_continue)?;
426 Ok(())
427 }
428
429 pub fn intermediate_passes(
435 &mut self,
436 ) -> Result<(leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>)>
437 {
438 let type_checking_config = TypeCheckingInput::new(self.state.network);
439
440 self.frontend_passes()?;
441
442 self.do_pass::<LibraryPruning>(())?;
444
445 self.do_pass::<ConstPropUnrollAndMorphing>(type_checking_config.clone())?;
446
447 let abis = self.generate_abi();
450
451 self.do_pass::<StorageLowering>(type_checking_config.clone())?;
452
453 self.do_pass::<OptionLowering>(type_checking_config)?;
454
455 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: true })?;
456
457 self.do_pass::<Destructuring>(())?;
458
459 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
460
461 self.do_pass::<WriteTransforming>(())?;
462
463 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
464
465 self.do_pass::<Flattening>(())?;
466
467 self.do_pass::<FunctionInlining>(())?;
468
469 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
471
472 self.do_pass::<SsaConstPropagation>(())?;
473
474 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
475
476 self.do_pass::<CommonSubexpressionEliminating>(())?;
477
478 self.do_pass::<DeadCodeEliminating>(())?;
479
480 Ok(abis)
481 }
482
483 fn generate_abi(
490 &self,
491 ) -> (leo_abi::Program, IndexMap<String, leo_abi::Program>, Vec<leo_abi::interfaces::CompiledInterface>) {
492 let program = match &self.state.ast {
493 Ast::Program(program) => program,
494 Ast::Library(_) => panic!("expected Program AST"),
495 };
496
497 let primary_abi = leo_abi::generate(program);
499
500 let interface_abis = leo_abi::interfaces::generate_program_interfaces(program);
502
503 let import_abis: IndexMap<String, leo_abi::Program> = program
505 .stubs
506 .iter()
507 .filter(|(_, stub)| !matches!(stub, Stub::FromLibrary { .. }))
508 .map(|(name, stub)| {
509 let abi = match stub {
510 Stub::FromLeo { program, .. } => leo_abi::generate(program),
511 Stub::FromAleo { program, .. } => leo_abi::aleo::generate(program),
512 Stub::FromLibrary { .. } => unreachable!("filtered out"),
513 };
514 (name.to_string(), abi)
515 })
516 .collect();
517
518 (primary_abi, import_abis, interface_abis)
519 }
520
521 pub fn generate_library_interface_abis(&self) -> Vec<leo_abi::interfaces::CompiledInterface> {
525 let Ast::Library(library) = &self.state.ast else {
526 panic!("expected Library AST");
527 };
528 leo_abi::interfaces::generate_library_interfaces(library)
529 }
530
531 pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result<Compiled> {
546 self.parse_program(source, filename, modules)?;
548 self.add_import_stubs()?;
550 let (primary_abi, import_abis, interfaces) = self.intermediate_passes()?;
552 let generated = self.do_pass::<CodeGenerating>(())?;
554 let bytecodes = self.do_pass::<PeepholeOptimizing>(generated)?;
556
557 let primary = CompiledProgram {
559 name: self.unit_name.clone().unwrap(),
560 bytecode: bytecodes.primary_bytecode,
561 abi: primary_abi,
562 };
563
564 let imports: Vec<CompiledProgram> = bytecodes
566 .import_bytecodes
567 .into_iter()
568 .map(|bc| {
569 let abi = import_abis.get(&bc.program_name).expect("ABI should exist for all imports").clone();
570 CompiledProgram { name: bc.program_name, bytecode: bc.bytecode, abi }
571 })
572 .collect();
573
574 Ok(Compiled { primary, imports, interfaces })
575 }
576
577 fn read_sources_and_modules(
593 file_source: &impl FileSource,
594 entry_file_path: impl AsRef<Path>,
595 source_directory: impl AsRef<Path>,
596 ) -> Result<(String, Vec<(String, FileName)>)> {
597 let entry_file_path = entry_file_path.as_ref();
598 let source_directory = source_directory.as_ref();
599
600 let source = file_source
602 .read_file(entry_file_path)
603 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
604
605 let files = file_source
606 .list_leo_files(source_directory, entry_file_path)
607 .map_err(|e| crate::errors::file_read_error(source_directory.display().to_string(), e))?;
608
609 let mut modules = Vec::with_capacity(files.len());
610 for path in files {
611 let module_source = file_source
612 .read_file(&path)
613 .map_err(|e| crate::errors::file_read_error(path.display().to_string(), e))?;
614 modules.push((module_source, FileName::Real(path)));
615 }
616
617 Ok((source, modules))
618 }
619
620 pub fn compile_from_directory(
622 &mut self,
623 entry_file_path: impl AsRef<Path>,
624 source_directory: impl AsRef<Path>,
625 ) -> Result<Compiled> {
626 self.compile_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
627 }
628
629 pub fn compile_from_directory_with_file_source(
631 &mut self,
632 entry_file_path: impl AsRef<Path>,
633 source_directory: impl AsRef<Path>,
634 file_source: &impl FileSource,
635 ) -> Result<Compiled> {
636 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
637
638 let module_refs: Vec<(&str, FileName)> =
640 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
641
642 self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
644 }
645
646 pub fn compile_from_file(&mut self, entry_file_path: impl AsRef<Path>) -> Result<Compiled> {
651 self.compile_from_file_with_file_source(entry_file_path, &DiskFileSource)
652 }
653
654 pub fn compile_from_file_with_file_source(
656 &mut self,
657 entry_file_path: impl AsRef<Path>,
658 file_source: &impl FileSource,
659 ) -> Result<Compiled> {
660 let entry_file_path = entry_file_path.as_ref();
661 let source = file_source
662 .read_file(entry_file_path)
663 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
664 self.compile(&source, FileName::Real(entry_file_path.into()), &Vec::new())
665 }
666
667 pub fn parse_program_from_directory(
669 &mut self,
670 entry_file_path: impl AsRef<Path>,
671 source_directory: impl AsRef<Path>,
672 ) -> Result<Program> {
673 self.parse_program_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
674 }
675
676 pub fn parse_program_from_directory_with_file_source(
678 &mut self,
679 entry_file_path: impl AsRef<Path>,
680 source_directory: impl AsRef<Path>,
681 file_source: &impl FileSource,
682 ) -> Result<Program> {
683 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
684
685 let module_refs: Vec<(&str, FileName)> =
687 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
688
689 self.parse_program(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
691
692 match &self.state.ast {
693 Ast::Program(program) => Ok(program.clone()),
694 Ast::Library(_) => unreachable!("expected Program AST"),
695 }
696 }
697
698 pub fn parse_program_from_file(&mut self, entry_file_path: impl AsRef<Path>) -> Result<Program> {
703 self.parse_program_from_file_with_file_source(entry_file_path, &DiskFileSource)
704 }
705
706 pub fn parse_program_from_file_with_file_source(
708 &mut self,
709 entry_file_path: impl AsRef<Path>,
710 file_source: &impl FileSource,
711 ) -> Result<Program> {
712 let entry_file_path = entry_file_path.as_ref();
713 let source = file_source
714 .read_file(entry_file_path)
715 .map_err(|e| crate::errors::file_read_error(entry_file_path.display().to_string(), e))?;
716 self.parse_program(&source, FileName::Real(entry_file_path.into()), &[])?;
717
718 match &self.state.ast {
719 Ast::Program(program) => Ok(program.clone()),
720 Ast::Library(_) => unreachable!("expected Program AST"),
721 }
722 }
723
724 pub fn parse_library_from_directory(
726 &mut self,
727 library_name: Symbol,
728 entry_file_path: impl AsRef<Path>,
729 source_directory: impl AsRef<Path>,
730 ) -> Result<Library> {
731 self.parse_library_from_directory_with_file_source(
732 library_name,
733 entry_file_path,
734 source_directory,
735 &DiskFileSource,
736 )
737 }
738
739 pub fn parse_library_from_directory_with_file_source(
741 &mut self,
742 library_name: Symbol,
743 entry_file_path: impl AsRef<Path>,
744 source_directory: impl AsRef<Path>,
745 file_source: &impl FileSource,
746 ) -> Result<Library> {
747 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
748
749 let module_refs: Vec<(&str, FileName)> =
750 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
751
752 self.parse_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
753
754 match &self.state.ast {
755 Ast::Library(library) => Ok(library.clone()),
756 Ast::Program(_) => unreachable!("expected Library AST"),
757 }
758 }
759
760 pub fn add_import_stubs(&mut self) -> Result<()> {
775 use indexmap::IndexSet;
776
777 self.inject_std_library()?;
779
780 let mut explored = IndexSet::<Symbol>::new();
782
783 let initial_imports: IndexMap<Symbol, Span> = match &self.state.ast {
785 Ast::Program(program) => {
786 let mut map: IndexMap<Symbol, Span> =
787 program.imports.iter().map(|(name, id)| (*name, id.span())).collect();
788 for (stub_name, stub) in &self.import_stubs {
790 if matches!(stub, Stub::FromLibrary { .. })
791 && stub.parents().contains(&Symbol::intern(self.unit_name.as_ref().unwrap()))
792 {
793 map.insert(
794 *stub_name,
795 Span::default(), );
797 }
798 }
799 map
800 }
801 Ast::Library(_) => {
802 let library_name = Symbol::intern(self.unit_name.as_ref().unwrap());
806 self.import_stubs
807 .iter()
808 .filter(|(_, stub)| stub.parents().contains(&library_name))
809 .map(|(name, _)| (*name, Span::default()))
810 .collect()
811 }
812 };
813
814 let mut to_explore: Vec<(Symbol, Span)> = initial_imports.iter().map(|(sym, span)| (*sym, *span)).collect();
816
817 if let Some(main_program_name) = self.unit_name.clone() {
819 let main_symbol = Symbol::intern(&main_program_name);
820 for import in initial_imports.keys() {
821 if let Some(child_stub) = self.import_stubs.get_mut(import) {
822 child_stub.add_parent(main_symbol);
823 }
824 }
825 }
826
827 while let Some((import_symbol, span)) = to_explore.pop() {
829 explored.insert(import_symbol);
831
832 let Some(stub) = self.import_stubs.get(&import_symbol) else {
834 return Err(crate::errors::imported_program_not_found(
835 self.unit_name.as_ref().unwrap(),
836 import_symbol,
837 span,
838 )
839 .into());
840 };
841
842 let mut combined_imports: IndexMap<Symbol, Span> = stub.explicit_imports().collect();
844 for (lib_name, lib_stub) in &self.import_stubs {
845 if matches!(lib_stub, Stub::FromLibrary { .. }) && lib_stub.parents().contains(&import_symbol) {
846 combined_imports.insert(
847 *lib_name,
848 Span::default(), );
850 }
851 }
852
853 for (child_symbol, child_span) in combined_imports {
854 if let Some(child_stub) = self.import_stubs.get_mut(&child_symbol) {
856 child_stub.add_parent(import_symbol);
857 }
858
859 if explored.insert(child_symbol) {
861 to_explore.push((child_symbol, child_span));
862 }
863 }
864 }
865
866 let reachable: IndexMap<Symbol, Stub> = self
868 .import_stubs
869 .iter()
870 .filter(|(symbol, _)| explored.contains(*symbol))
871 .map(|(symbol, stub)| (*symbol, stub.clone()))
872 .collect();
873 match &mut self.state.ast {
874 Ast::Program(program) => program.stubs = reachable,
875 Ast::Library(library) => library.stubs = reachable,
876 }
877
878 Ok(())
879 }
880
881 pub fn build_std_stub(
887 handler: Handler,
888 node_builder: Rc<NodeBuilder>,
889 network: NetworkName,
890 types: Rc<TypeInterner>,
891 ) -> Result<Stub> {
892 let std_name = Symbol::intern(leo_std::library_name());
893
894 let mut sub_compiler = Compiler::new_with_types(
895 Some(leo_std::library_name().to_string()),
896 false,
897 handler,
898 node_builder,
899 Some(CompilerOptions {
900 no_std: true,
902 }),
903 IndexMap::new(),
904 network,
905 types,
906 );
907
908 let module_refs: Vec<(&str, FileName)> =
909 leo_std::modules().iter().map(|(path, source)| (*source, FileName::Custom((*path).to_string()))).collect();
910
911 let library = sub_compiler.build_library_inner(
913 std_name,
914 leo_std::entry_source(),
915 FileName::Custom(format!("<{}>", leo_std::library_name())),
916 &module_refs,
917 false,
918 )?;
919
920 Ok(library.into())
921 }
922
923 fn inject_std_library(&mut self) -> Result<()> {
927 if self.compiler_options.no_std {
928 return Ok(());
929 }
930
931 let std_name = Symbol::intern(leo_std::library_name());
932 let unit_parent = Symbol::intern(self.unit_name.as_deref().expect("Cannot get unit name"));
933
934 let mut parents: Vec<Symbol> = self
935 .import_stubs
936 .iter()
937 .filter_map(|(name, _)| if *name == std_name { None } else { Some(*name) })
938 .collect();
939 parents.push(unit_parent);
940
941 if let Some(existing) = self.import_stubs.get_mut(&std_name) {
942 for parent in parents {
943 existing.add_parent(parent);
944 }
945 return Ok(());
946 }
947
948 let mut stub = Self::build_std_stub(
949 self.state.handler.clone(),
950 Rc::clone(&self.state.node_builder),
951 self.state.network,
952 Rc::clone(&self.state.types),
953 )?;
954 for parent in parents {
955 stub.add_parent(parent);
956 }
957 self.import_stubs.insert(std_name, stub);
958 Ok(())
959 }
960
961 pub fn build_library(
967 &mut self,
968 library_name: Symbol,
969 source: &str,
970 filename: FileName,
971 modules: &[(&str, FileName)],
972 ) -> Result<Library> {
973 self.build_library_inner(library_name, source, filename, modules, true)
974 }
975
976 fn build_library_inner(
981 &mut self,
982 library_name: Symbol,
983 source: &str,
984 filename: FileName,
985 modules: &[(&str, FileName)],
986 run_frontend: bool,
987 ) -> Result<Library> {
988 self.parse_library(library_name, source, filename, modules)?;
989 self.add_import_stubs()?;
990 if run_frontend {
991 self.frontend_passes()?;
992 }
993
994 match &self.state.ast {
995 Ast::Library(library) => Ok(library.clone()),
996 Ast::Program(_) => unreachable!("expected Library AST"),
997 }
998 }
999
1000 pub fn build_library_from_directory(
1002 &mut self,
1003 library_name: Symbol,
1004 entry_file_path: impl AsRef<Path>,
1005 source_directory: impl AsRef<Path>,
1006 ) -> Result<Library> {
1007 self.build_library_from_directory_with_file_source(
1008 library_name,
1009 entry_file_path,
1010 source_directory,
1011 &DiskFileSource,
1012 )
1013 }
1014
1015 pub fn build_library_from_directory_with_file_source(
1017 &mut self,
1018 library_name: Symbol,
1019 entry_file_path: impl AsRef<Path>,
1020 source_directory: impl AsRef<Path>,
1021 file_source: &impl FileSource,
1022 ) -> Result<Library> {
1023 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
1024
1025 let module_refs: Vec<(&str, FileName)> =
1026 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
1027
1028 self.build_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
1029 }
1030}
1031
1032pub fn load_import_stubs_for_package(
1042 package_root: &Path,
1043 network: NetworkName,
1044 types: &Rc<TypeInterner>,
1045) -> Result<LoadedImportStubs> {
1046 load_import_stubs_for_package_with_file_source(package_root, network, &DiskFileSource, types)
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 types: &Rc<TypeInterner>,
1060) -> Result<LoadedImportStubs> {
1061 create_session_if_not_set_then(|_| {
1062 let package_root =
1063 package_root.canonicalize().map_err(|error| crate::errors::failed_path(package_root.display(), error))?;
1064 let declared_dependencies = collect_local_declared_dependencies(&package_root)?;
1065 let mut import_stubs = IndexMap::new();
1066 let mut watch_paths = vec![package_root.join(MANIFEST_FILENAME)];
1067
1068 for (name, dependency) in &declared_dependencies {
1069 let Some(path) = dependency.path.as_ref() else {
1070 continue;
1071 };
1072
1073 let unit = if path.extension().is_some_and(|extension| extension == "aleo") && path.is_file() {
1074 watch_paths.push(path.clone());
1075 CompilationUnit::from_aleo_path(*name, path, &declared_dependencies)?
1076 } else {
1077 let unit = CompilationUnit::from_package_path(*name, path)?;
1078 watch_paths.extend(unit_watch_paths(&unit, file_source)?);
1079 unit
1080 };
1081
1082 let stub = match &unit.data {
1083 ProgramData::Bytecode(bytecode) => disassemble_dependency_bytecode(unit.name, bytecode, network)?,
1084 ProgramData::SourcePath { directory, source } => load_source_dependency_stub(
1085 &unit,
1086 source,
1087 dependency_source_directory(directory, source),
1088 network,
1089 file_source,
1090 types,
1091 )?,
1092 };
1093 import_stubs.insert(unit.name, stub);
1094 }
1095
1096 watch_paths.sort();
1097 watch_paths.dedup();
1098
1099 Ok(LoadedImportStubs { stubs: import_stubs, watch_paths })
1100 })
1101}
1102
1103fn dependency_source_directory(directory: &Path, source: &Path) -> PathBuf {
1105 let source_root = directory.join("src");
1106 if source.starts_with(&source_root) { source_root } else { directory.to_path_buf() }
1107}
1108
1109fn collect_local_declared_dependencies(package_root: &Path) -> Result<IndexMap<Symbol, Dependency>> {
1114 let manifest = Manifest::read_from_file(package_root.join(MANIFEST_FILENAME))?;
1115 let mut declared = IndexMap::new();
1116 collect_local_declared_dependencies_recursive(package_root, &manifest, &mut declared)?;
1117 Ok(declared)
1118}
1119
1120fn collect_local_declared_dependencies_recursive(
1122 base_path: &Path,
1123 manifest: &Manifest,
1124 declared: &mut IndexMap<Symbol, Dependency>,
1125) -> Result<()> {
1126 for dependency in manifest.dependencies.iter().flatten() {
1127 let dependency = normalize_local_dependency(base_path, dependency.clone())?;
1128 let dependency = if dependency.location == Location::Workspace {
1130 resolve_workspace_dependency(base_path, dependency)?
1131 } else {
1132 dependency
1133 };
1134 if dependency.location != Location::Local {
1135 continue;
1136 }
1137
1138 let Some(path) = dependency.path.as_ref() else {
1139 continue;
1140 };
1141 let symbol = Symbol::intern(&dependency.name);
1142
1143 match declared.entry(symbol) {
1144 Entry::Occupied(_) => continue,
1145 Entry::Vacant(entry) => {
1146 entry.insert(dependency.clone());
1147 let manifest_path = path.join(MANIFEST_FILENAME);
1148 if path.is_dir() && manifest_path.is_file() {
1149 let child = Manifest::read_from_file(manifest_path)?;
1150 collect_local_declared_dependencies_recursive(path, &child, declared)?;
1151 }
1152 }
1153 }
1154 }
1155
1156 Ok(())
1157}
1158
1159fn normalize_local_dependency(base_path: &Path, mut dependency: Dependency) -> Result<Dependency> {
1161 if let Some(path) = dependency.path.as_mut()
1162 && !path.is_absolute()
1163 {
1164 let joined = base_path.join(&*path);
1165 *path = joined.canonicalize().map_err(|error| crate::errors::failed_path(joined.display(), error))?;
1166 }
1167
1168 Ok(dependency)
1169}
1170
1171fn unit_watch_paths(unit: &CompilationUnit, file_source: &impl FileSource) -> Result<Vec<PathBuf>> {
1173 let ProgramData::SourcePath { directory, source } = &unit.data else {
1174 return Ok(Vec::new());
1175 };
1176
1177 let source_directory = dependency_source_directory(directory, source);
1178 let mut watch_paths = vec![directory.join(MANIFEST_FILENAME), source_directory.clone(), source.clone()];
1179 if source_directory.is_dir() {
1180 collect_source_directories(&source_directory, &mut watch_paths)?;
1181 let mut modules = file_source
1182 .list_leo_files(&source_directory, source)
1183 .map_err(|error| crate::errors::file_read_error(source_directory.display().to_string(), error))?;
1184 watch_paths.append(&mut modules);
1185 }
1186
1187 Ok(watch_paths)
1188}
1189
1190fn collect_source_directories(dir: &Path, watch_paths: &mut Vec<PathBuf>) -> Result<()> {
1192 for entry in fs::read_dir(dir).map_err(|error| errors::file_read_error(dir.display().to_string(), error))? {
1193 let entry = entry.map_err(|error| errors::file_read_error(dir.display().to_string(), error))?;
1194 let path = entry.path();
1195 if path.is_dir() {
1196 watch_paths.push(path.clone());
1200 collect_source_directories(&path, watch_paths)?;
1201 }
1202 }
1203 Ok(())
1204}
1205
1206fn load_source_dependency_stub(
1209 unit: &CompilationUnit,
1210 source: &Path,
1211 source_directory: PathBuf,
1212 network: NetworkName,
1213 file_source: &impl FileSource,
1214 types: &Rc<TypeInterner>,
1215) -> Result<Stub> {
1216 let handler = Handler::default();
1217 let node_builder = Rc::new(NodeBuilder::default());
1218 let mut compiler = Compiler::new_with_types(
1219 Some(unit.name.to_string()),
1220 false,
1221 handler,
1222 node_builder,
1223 Some(CompilerOptions::default()),
1224 IndexMap::new(),
1225 network,
1226 Rc::clone(types),
1227 );
1228
1229 match unit.kind {
1230 PackageKind::Library => {
1231 let library_name = Symbol::intern(&unit.name.to_string());
1232 let library = compiler.parse_library_from_directory_with_file_source(
1233 library_name,
1234 source,
1235 &source_directory,
1236 file_source,
1237 )?;
1238 Ok(library.into())
1239 }
1240 PackageKind::Program | PackageKind::Test => {
1241 let program =
1242 compiler.parse_program_from_directory_with_file_source(source, &source_directory, file_source)?;
1243 Ok(extract_program_interface_stub(unit.name, &program))
1244 }
1245 }
1246}
1247
1248fn extract_program_interface_stub(_program_name: Symbol, program: &Program) -> Stub {
1250 let scope = program.program_scopes.values().next().expect("program AST should contain one program scope");
1251
1252 let functions = scope
1257 .functions
1258 .iter()
1259 .map(|(sym, func)| {
1260 (*sym, FunctionStub {
1261 annotations: func.annotations.clone(),
1262 variant: func.variant,
1263 identifier: func.identifier,
1264 input: func.input.clone(),
1265 output: func.output.clone(),
1266 output_type: func.output_type.clone(),
1267 span: func.span,
1268 id: func.id,
1269 })
1270 })
1271 .collect();
1272
1273 let imports = program
1274 .imports
1275 .keys()
1276 .map(|sym| {
1277 let sym_str = sym.to_string();
1278 let name_only = sym_str.strip_suffix(".aleo").unwrap_or(&sym_str);
1282 ProgramId {
1283 name: Identifier { name: Symbol::intern(name_only), span: Default::default(), id: Default::default() },
1284 network: Identifier { name: Symbol::intern("aleo"), span: Default::default(), id: Default::default() },
1285 }
1286 })
1287 .collect();
1288
1289 AleoProgram {
1290 imports,
1291 stub_id: scope.program_id,
1292 consts: scope.consts.clone(),
1293 composites: scope.composites.clone(),
1294 mappings: scope.mappings.clone(),
1295 functions,
1296 span: scope.span,
1297 }
1298 .into()
1299}
1300
1301fn disassemble_dependency_bytecode(program_name: Symbol, bytecode: &str, network: NetworkName) -> Result<Stub> {
1305 let disassembled = match network {
1306 NetworkName::MainnetV0 => {
1307 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::MainnetV0>(program_name, bytecode)
1308 }
1309 NetworkName::TestnetV0 => {
1310 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::TestnetV0>(program_name, bytecode)
1311 }
1312 NetworkName::CanaryV0 => {
1313 leo_disassembler::disassemble_from_str_unchecked::<snarkvm::prelude::CanaryV0>(program_name, bytecode)
1314 }
1315 };
1316
1317 disassembled
1318 .map(Into::into)
1319 .map_err(|err| crate::errors::file_read_error(format!("dependency bytecode for `{program_name}`"), err).into())
1320}
1321
1322#[cfg(test)]
1323mod tests {
1324 use super::Compiler;
1325
1326 use leo_ast::{NetworkName, NodeBuilder};
1327 use leo_errors::{BufferEmitter, Handler};
1328 use leo_span::{Symbol, create_session_if_not_set_then, file_source::InMemoryFileSource, source_map::FileName};
1329
1330 use std::{path::PathBuf, rc::Rc};
1331
1332 use indexmap::IndexMap;
1333
1334 #[test]
1336 fn parse_library_from_directory_in_memory() {
1337 create_session_if_not_set_then(|_| {
1338 let mut source = InMemoryFileSource::new();
1339 source.set(
1340 PathBuf::from("/mylib/src/lib.leo"),
1341 concat!("const SCALE: u32 = 10u32;\n", "const OFFSET: u32 = SCALE + 1u32;\n",).into(),
1342 );
1343
1344 let handler = Handler::default();
1345 let node_builder = Rc::new(NodeBuilder::default());
1346 let mut compiler =
1347 Compiler::new(None, false, handler, node_builder, None, IndexMap::new(), NetworkName::TestnetV0);
1348
1349 let library = compiler
1350 .parse_library_from_directory_with_file_source(
1351 Symbol::intern("mylib"),
1352 "/mylib/src/lib.leo",
1353 "/mylib/src",
1354 &source,
1355 )
1356 .unwrap_or_else(|err| panic!("parsing library from in-memory file source failed: {err}"));
1357
1358 assert_eq!(library.name, Symbol::intern("mylib"));
1359 assert_eq!(library.consts.len(), 2, "expected 2 consts, got {}", library.consts.len());
1360 assert!(
1361 library.consts.iter().any(|(name, _)| *name == Symbol::intern("SCALE")),
1362 "expected const `SCALE` in library"
1363 );
1364 assert!(
1365 library.consts.iter().any(|(name, _)| *name == Symbol::intern("OFFSET")),
1366 "expected const `OFFSET` in library"
1367 );
1368 });
1369 }
1370
1371 #[test]
1373 fn build_library_from_directory_in_memory_rejects_type_error() {
1374 create_session_if_not_set_then(|_| {
1375 let mut source = InMemoryFileSource::new();
1376 source
1378 .set(PathBuf::from("/badlib/src/lib.leo"), "fn broken() -> u32 {\n return true + 1u32;\n}\n".into());
1379
1380 let emitter = BufferEmitter::new();
1382 let handler = Handler::new(emitter.clone());
1383 let node_builder = Rc::new(NodeBuilder::default());
1384 let mut compiler = Compiler::new(
1385 Some("badlib".into()),
1386 false,
1387 handler,
1388 node_builder,
1389 None,
1390 IndexMap::new(),
1391 NetworkName::TestnetV0,
1392 );
1393
1394 let result = compiler.build_library_from_directory_with_file_source(
1395 Symbol::intern("badlib"),
1396 "/badlib/src/lib.leo",
1397 "/badlib/src",
1398 &source,
1399 );
1400
1401 assert!(result.is_err(), "expected build_library to fail on a library with a type error");
1402
1403 let errors = emitter.extract_errs().to_string();
1404 assert!(errors.contains("ETYC"), "expected a type-checking error (prefix `ETYC`) but captured:\n{errors}");
1405 });
1406 }
1407
1408 #[test]
1410 fn parse_program_from_directory_in_memory_with_module() {
1411 create_session_if_not_set_then(|_| {
1412 let mut source = InMemoryFileSource::new();
1413 source.set(
1414 PathBuf::from("/project/src/main.leo"),
1415 concat!(
1416 "program test.aleo {\n",
1417 " fn main() -> u32 {\n",
1418 " return utils::helper();\n",
1419 " }\n",
1420 "}\n",
1421 )
1422 .into(),
1423 );
1424 source.set(PathBuf::from("/project/src/utils.leo"), "fn helper() -> u32 {\n return 42u32;\n}\n".into());
1425
1426 let handler = Handler::default();
1427 let node_builder = Rc::new(NodeBuilder::default());
1428 let mut compiler = Compiler::new(
1429 Some("test.aleo".into()),
1430 false,
1431 handler,
1432 node_builder,
1433 None,
1434 IndexMap::new(),
1435 NetworkName::TestnetV0,
1436 );
1437
1438 let ast = compiler
1439 .parse_program_from_directory_with_file_source("/project/src/main.leo", "/project/src", &source)
1440 .unwrap_or_else(|err| panic!("parsing from in-memory file source failed: {err}"));
1441 let utils_key = vec![Symbol::intern("utils")];
1442
1443 assert!(
1444 ast.modules.contains_key(&utils_key),
1445 "module `utils` should be loaded from the in-memory file source; found keys: {:?}",
1446 ast.modules.keys().collect::<Vec<_>>()
1447 );
1448 });
1449 }
1450
1451 #[test]
1455 fn rename_override_recompiles_under_new_name() {
1456 create_session_if_not_set_then(|_| {
1457 let handler = Handler::default();
1458 let node_builder = Rc::new(NodeBuilder::default());
1459 let mut compiler = Compiler::new(
1460 Some("foo.aleo".into()),
1461 false,
1462 handler,
1463 node_builder,
1464 None,
1465 IndexMap::new(),
1466 NetworkName::TestnetV0,
1467 );
1468 compiler.rename = Some("bar.aleo".into());
1470
1471 let source = concat!(
1472 "program foo.aleo {\n",
1473 " record R {\n",
1474 " owner: address,\n",
1475 " x: bool,\n",
1476 " }\n",
1477 " @noupgrade\n",
1478 " constructor() {}\n",
1479 " fn foo() -> R {\n",
1480 " return R { owner: std::ctx::signer(), x: true };\n",
1481 " }\n",
1482 "}\n",
1483 );
1484 let filename = FileName::Custom("main.leo".into());
1485 let modules: Vec<(&str, FileName)> = Vec::new();
1486 let compiled = compiler
1487 .compile(source, filename, &modules)
1488 .unwrap_or_else(|err| panic!("compiling with rename failed: {err}"));
1489
1490 assert_eq!(compiler.unit_name.as_deref(), Some("bar.aleo"), "unit name should adopt the rename");
1491 let bytecode = &compiled.primary.bytecode;
1492 assert!(bytecode.contains("program bar.aleo;"), "expected renamed header, got:\n{bytecode}");
1493 assert!(!bytecode.contains("program foo.aleo;"), "old name leaked into bytecode:\n{bytecode}");
1494 });
1495 }
1496
1497 #[test]
1502 fn std_library_passes_frontend() {
1503 create_session_if_not_set_then(|_| {
1504 let emitter = BufferEmitter::new();
1505 let handler = Handler::new(emitter.clone());
1506 let node_builder = Rc::new(NodeBuilder::default());
1507 let mut compiler = Compiler::new(
1508 Some(leo_std::library_name().to_string()),
1509 false,
1510 handler,
1511 node_builder,
1512 Some(crate::CompilerOptions { no_std: true }),
1514 IndexMap::new(),
1515 NetworkName::TestnetV0,
1516 );
1517
1518 let module_refs: Vec<(&str, FileName)> = leo_std::modules()
1519 .iter()
1520 .map(|(path, source)| (*source, FileName::Custom((*path).to_string())))
1521 .collect();
1522
1523 let result = compiler.build_library(
1524 Symbol::intern(leo_std::library_name()),
1525 leo_std::entry_source(),
1526 FileName::Custom(format!("<{}>", leo_std::library_name())),
1527 &module_refs,
1528 );
1529
1530 assert!(result.is_ok(), "std failed to build: {:?}", result.err());
1531 let errors = emitter.extract_errs().to_string();
1532 assert!(errors.is_empty(), "std produced diagnostics:\n{errors}");
1533 });
1534 }
1535}