diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index a8fba735..e0b8f657 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -51,7 +51,7 @@ use pallas::ledger::{ traverse::ComputeHash, }; use std::{ - collections::HashMap, + collections::{BTreeSet, HashMap}, fs::{self, File}, io::BufReader, path::{Path, PathBuf}, @@ -185,8 +185,6 @@ where destination: Option, include_dependencies: bool, ) -> Result<(), Vec> { - self.compile_deps()?; - self.event_listener .handle_event(Event::BuildingDocumentation { root: self.root.clone(), @@ -196,12 +194,12 @@ where self.read_source_files()?; + let mut modules = self.parse_sources(self.config.name.clone())?; + + self.type_check(&mut modules, Tracing::silent(), false)?; + let destination = destination.unwrap_or_else(|| self.root.join("docs")); - let parsed_modules = self.parse_sources(self.config.name.clone())?; - - self.type_check(parsed_modules, Tracing::silent(), false, false)?; - self.event_listener.handle_event(Event::GeneratingDocFiles { output_path: destination.clone(), }); @@ -283,8 +281,6 @@ where } pub fn compile(&mut self, options: Options) -> Result<(), Vec> { - self.compile_deps()?; - self.event_listener .handle_event(Event::StartingCompilation { root: self.root.clone(), @@ -294,9 +290,9 @@ where self.read_source_files()?; - let parsed_modules = self.parse_sources(self.config.name.clone())?; + let mut modules = self.parse_sources(self.config.name.clone())?; - self.type_check(parsed_modules, options.tracing, true, false)?; + self.type_check(&mut modules, options.tracing, true)?; match options.code_gen_mode { CodeGenMode::Build(uplc_dump) => { @@ -537,7 +533,7 @@ where Ok(blueprint) } - fn compile_deps(&mut self) -> Result<(), Vec> { + fn with_dependencies(&mut self, parsed_packages: &mut ParsedModules) -> Result<(), Vec> { let manifest = deps::download(&self.event_listener, &self.root, &self.config)?; for package in manifest.packages { @@ -565,7 +561,7 @@ where .retain(|def| !matches!(def, Definition::Test { .. })) }); - self.type_check(parsed_modules, Tracing::silent(), true, true)?; + parsed_packages.extend(Into::>::into(parsed_modules)); } Ok(()) @@ -680,78 +676,33 @@ where fn type_check( &mut self, - mut parsed_modules: ParsedModules, + modules: &mut ParsedModules, tracing: Tracing, validate_module_name: bool, - is_dependency: bool, - ) -> Result<(), Error> { - let processing_sequence = parsed_modules.sequence()?; + ) -> Result<(), Vec> { + let our_modules: BTreeSet = modules.keys().cloned().collect(); - for name in processing_sequence { - if let Some(ParsedModule { - name, - path, - code, - kind, - extra, - package, - ast, - }) = parsed_modules.remove(&name) - { - let mut type_warnings = Vec::new(); + self.with_dependencies(modules)?; - let ast = ast - .infer( - &self.id_gen, - kind, - &self.config.name.to_string(), - &self.module_types, - tracing, - &mut type_warnings, - ) - .map_err(|error| Error::Type { - path: path.clone(), - src: code.clone(), - named: NamedSource::new(path.display().to_string(), code.clone()), - error, - })?; + for name in modules.sequence(&our_modules)? { + if let Some(module) = modules.remove(&name) { + let (checked_module, warnings) = module.infer( + &self.id_gen, + &self.config.name.to_string(), + tracing, + validate_module_name, + &mut self.module_sources, + &mut self.module_types, + &mut self.functions, + &mut self.data_types, + )?; - if validate_module_name { - ast.validate_module_name()?; + if our_modules.contains(checked_module.name.as_str()) { + self.warnings.extend(warnings); } - // Register any warnings emitted as type warnings - let type_warnings = type_warnings - .into_iter() - .map(|w| Warning::from_type_warning(w, path.clone(), code.clone())); - - if !is_dependency { - self.warnings.extend(type_warnings); - } - - // Register module sources for an easier access later. - self.module_sources - .insert(name.clone(), (code.clone(), LineNumbers::new(&code))); - - // Register the types from this module so they can be - // imported into other modules. - self.module_types - .insert(name.clone(), ast.type_info.clone()); - - // Register function definitions & data-types for easier access later. - ast.register_definitions(&mut self.functions, &mut self.data_types); - - let checked_module = CheckedModule { - kind, - extra, - name: name.clone(), - code, - ast, - package, - input_path: path, - }; - - self.checked_modules.insert(name, checked_module); + self.checked_modules + .insert(checked_module.name.clone(), checked_module); } } diff --git a/crates/aiken-project/src/module.rs b/crates/aiken-project/src/module.rs index 2d90dbd1..ff70e38a 100644 --- a/crates/aiken-project/src/module.rs +++ b/crates/aiken-project/src/module.rs @@ -1,14 +1,20 @@ -use crate::error::Error; +use crate::{Error, Warning}; use aiken_lang::{ ast::{ - DataType, Definition, Function, Located, ModuleKind, TypedModule, TypedValidator, - UntypedModule, Validator, + DataType, DataTypeKey, Definition, Function, FunctionAccessKey, Located, ModuleKind, + Tracing, TypedDataType, TypedFunction, TypedModule, TypedValidator, UntypedModule, + Validator, }, + line_numbers::LineNumbers, parser::extra::{comments_before, Comment, ModuleExtra}, + tipo::TypeInfo, + IdGenerator, }; +use indexmap::IndexMap; +use miette::NamedSource; use petgraph::{algo, graph::NodeIndex, Direction, Graph}; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeSet, HashMap}, io, ops::{Deref, DerefMut}, path::PathBuf, @@ -38,6 +44,74 @@ impl ParsedModule { (name, deps) } + + #[allow(clippy::too_many_arguments)] + pub fn infer( + self, + id_gen: &IdGenerator, + package: &str, + tracing: Tracing, + validate_module_name: bool, + module_sources: &mut HashMap, + module_types: &mut HashMap, + functions: &mut IndexMap, + data_types: &mut IndexMap, + ) -> Result<(CheckedModule, Vec), Error> { + let mut warnings = Vec::new(); + + let ast = self + .ast + .infer( + id_gen, + self.kind, + package, + module_types, + tracing, + &mut warnings, + ) + .map_err(|error| Error::Type { + path: self.path.clone(), + src: self.code.clone(), + named: NamedSource::new(self.path.display().to_string(), self.code.clone()), + error, + })?; + + let warnings = warnings + .into_iter() + .map(|w| Warning::from_type_warning(w, self.path.clone(), self.code.clone())) + .collect::>(); + + // Unless we're compiling prelude documentation, prevent keywords in module name + if validate_module_name { + ast.validate_module_name()?; + } + + // Register module sources for an easier access later. + module_sources.insert( + self.name.clone(), + (self.code.clone(), LineNumbers::new(&self.code)), + ); + + // Register the types from this module so they can be + // imported into other modules. + module_types.insert(self.name.clone(), ast.type_info.clone()); + + // Register function definitions & data-types for easier access later. + ast.register_definitions(functions, data_types); + + Ok(( + CheckedModule { + ast, + kind: self.kind, + extra: self.extra, + name: self.name, + code: self.code, + package: self.package, + input_path: self.path, + }, + warnings, + )) + } } pub struct ParsedModules(HashMap); @@ -47,7 +121,7 @@ impl ParsedModules { Self(HashMap::new()) } - pub fn sequence(&self) -> Result, Error> { + pub fn sequence(&self, our_modules: &BTreeSet) -> Result, Error> { let inputs = self .0 .values() @@ -56,18 +130,18 @@ impl ParsedModules { let capacity = inputs.len(); - let mut graph = Graph::<(), ()>::with_capacity(capacity, capacity * 5); + let mut graph = Graph::::with_capacity(capacity, capacity * 5); - // TODO: maybe use a bimap? let mut indices = HashMap::with_capacity(capacity); - let mut values = HashMap::with_capacity(capacity); + + let mut our_indices = BTreeSet::new(); for (value, _) in &inputs { - let index = graph.add_node(()); - + let index = graph.add_node(value.to_string()); indices.insert(value.clone(), index); - - values.insert(index, value.clone()); + if our_modules.contains(value) { + our_indices.insert(index); + } } for (value, deps) in inputs { @@ -80,12 +154,42 @@ impl ParsedModules { } } + let mut messed_up_indices = false; + + // Prune the dependency graph to only keep nodes that have a path to one of our (i.e. the + // current project) module. This effectively prunes dependencies that are unused from the + // graph to ensure that we only compile the modules we actually depend on. + graph.retain_nodes(|graph, ix| { + // When discarding a node, indices in the graph end up being rewritten. Yet, we need to + // know starting indices for our search, so when we remove a dependency, we need find + // back what those indices are. + if messed_up_indices { + our_indices = BTreeSet::new(); + for j in graph.node_indices() { + if our_modules.contains(graph[j].as_str()) { + our_indices.insert(j); + } + } + } + + for start in our_indices.iter() { + if algo::astar(&*graph, *start, |end| end == ix, |_| 1, |_| 0).is_some() { + messed_up_indices = false; + return true; + } + } + + messed_up_indices = true; + false + }); + match algo::toposort(&graph, None) { Ok(sequence) => { let sequence = sequence .iter() - .filter_map(|i| values.remove(i)) + .filter_map(|i| graph.node_weight(*i)) .rev() + .cloned() .collect(); Ok(sequence) @@ -95,11 +199,12 @@ impl ParsedModules { let mut path = vec![]; - find_cycle(origin, origin, &graph, &mut path, &mut HashSet::new()); + find_cycle(origin, origin, &graph, &mut path, &mut BTreeSet::new()); let modules = path .iter() - .filter_map(|index| values.remove(index)) + .filter_map(|i| graph.node_weight(*i)) + .cloned() .collect(); Err(Error::ImportCycle { modules }) @@ -140,12 +245,12 @@ impl DerefMut for ParsedModules { } } -fn find_cycle( +fn find_cycle( origin: NodeIndex, parent: NodeIndex, - graph: &petgraph::Graph<(), ()>, + graph: &petgraph::Graph, path: &mut Vec, - seen: &mut HashSet, + seen: &mut BTreeSet, ) -> bool { seen.insert(parent);