Merge pull request #877 from aiken-lang/dependencies-pruning

Only compile modules the project depends on
This commit is contained in:
Matthias Benkort 2024-03-15 00:25:00 +01:00 committed by GitHub
commit b09e0316fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 152 additions and 96 deletions

View File

@ -51,7 +51,7 @@ use pallas::ledger::{
traverse::ComputeHash, traverse::ComputeHash,
}; };
use std::{ use std::{
collections::HashMap, collections::{BTreeSet, HashMap},
fs::{self, File}, fs::{self, File},
io::BufReader, io::BufReader,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -185,8 +185,6 @@ where
destination: Option<PathBuf>, destination: Option<PathBuf>,
include_dependencies: bool, include_dependencies: bool,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
self.compile_deps()?;
self.event_listener self.event_listener
.handle_event(Event::BuildingDocumentation { .handle_event(Event::BuildingDocumentation {
root: self.root.clone(), root: self.root.clone(),
@ -196,12 +194,12 @@ where
self.read_source_files()?; 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 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 { self.event_listener.handle_event(Event::GeneratingDocFiles {
output_path: destination.clone(), output_path: destination.clone(),
}); });
@ -283,8 +281,6 @@ where
} }
pub fn compile(&mut self, options: Options) -> Result<(), Vec<Error>> { pub fn compile(&mut self, options: Options) -> Result<(), Vec<Error>> {
self.compile_deps()?;
self.event_listener self.event_listener
.handle_event(Event::StartingCompilation { .handle_event(Event::StartingCompilation {
root: self.root.clone(), root: self.root.clone(),
@ -294,9 +290,9 @@ where
self.read_source_files()?; 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 { match options.code_gen_mode {
CodeGenMode::Build(uplc_dump) => { CodeGenMode::Build(uplc_dump) => {
@ -537,7 +533,7 @@ where
Ok(blueprint) Ok(blueprint)
} }
fn compile_deps(&mut self) -> Result<(), Vec<Error>> { fn with_dependencies(&mut self, parsed_packages: &mut ParsedModules) -> Result<(), Vec<Error>> {
let manifest = deps::download(&self.event_listener, &self.root, &self.config)?; let manifest = deps::download(&self.event_listener, &self.root, &self.config)?;
for package in manifest.packages { for package in manifest.packages {
@ -565,7 +561,7 @@ where
.retain(|def| !matches!(def, Definition::Test { .. })) .retain(|def| !matches!(def, Definition::Test { .. }))
}); });
self.type_check(parsed_modules, Tracing::silent(), true, true)?; parsed_packages.extend(Into::<HashMap<_, _>>::into(parsed_modules));
} }
Ok(()) Ok(())
@ -680,78 +676,33 @@ where
fn type_check( fn type_check(
&mut self, &mut self,
mut parsed_modules: ParsedModules, modules: &mut ParsedModules,
tracing: Tracing, tracing: Tracing,
validate_module_name: bool, validate_module_name: bool,
is_dependency: bool, ) -> Result<(), Vec<Error>> {
) -> Result<(), Error> { let our_modules: BTreeSet<String> = modules.keys().cloned().collect();
let processing_sequence = parsed_modules.sequence()?;
for name in processing_sequence { self.with_dependencies(modules)?;
if let Some(ParsedModule {
name,
path,
code,
kind,
extra,
package,
ast,
}) = parsed_modules.remove(&name)
{
let mut type_warnings = Vec::new();
let ast = ast for name in modules.sequence(&our_modules)? {
.infer( if let Some(module) = modules.remove(&name) {
let (checked_module, warnings) = module.infer(
&self.id_gen, &self.id_gen,
kind,
&self.config.name.to_string(), &self.config.name.to_string(),
&self.module_types,
tracing, tracing,
&mut type_warnings, validate_module_name,
) &mut self.module_sources,
.map_err(|error| Error::Type { &mut self.module_types,
path: path.clone(), &mut self.functions,
src: code.clone(), &mut self.data_types,
named: NamedSource::new(path.display().to_string(), code.clone()), )?;
error,
})?;
if validate_module_name { if our_modules.contains(checked_module.name.as_str()) {
ast.validate_module_name()?; self.warnings.extend(warnings);
} }
// Register any warnings emitted as type warnings self.checked_modules
let type_warnings = type_warnings .insert(checked_module.name.clone(), checked_module);
.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);
} }
} }

View File

@ -1,14 +1,20 @@
use crate::error::Error; use crate::{Error, Warning};
use aiken_lang::{ use aiken_lang::{
ast::{ ast::{
DataType, Definition, Function, Located, ModuleKind, TypedModule, TypedValidator, DataType, DataTypeKey, Definition, Function, FunctionAccessKey, Located, ModuleKind,
UntypedModule, Validator, Tracing, TypedDataType, TypedFunction, TypedModule, TypedValidator, UntypedModule,
Validator,
}, },
line_numbers::LineNumbers,
parser::extra::{comments_before, Comment, ModuleExtra}, parser::extra::{comments_before, Comment, ModuleExtra},
tipo::TypeInfo,
IdGenerator,
}; };
use indexmap::IndexMap;
use miette::NamedSource;
use petgraph::{algo, graph::NodeIndex, Direction, Graph}; use petgraph::{algo, graph::NodeIndex, Direction, Graph};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeSet, HashMap},
io, io,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::PathBuf, path::PathBuf,
@ -38,6 +44,74 @@ impl ParsedModule {
(name, deps) (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<String, (String, LineNumbers)>,
module_types: &mut HashMap<String, TypeInfo>,
functions: &mut IndexMap<FunctionAccessKey, TypedFunction>,
data_types: &mut IndexMap<DataTypeKey, TypedDataType>,
) -> Result<(CheckedModule, Vec<Warning>), 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::<Vec<_>>();
// 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<String, ParsedModule>); pub struct ParsedModules(HashMap<String, ParsedModule>);
@ -47,7 +121,7 @@ impl ParsedModules {
Self(HashMap::new()) Self(HashMap::new())
} }
pub fn sequence(&self) -> Result<Vec<String>, Error> { pub fn sequence(&self, our_modules: &BTreeSet<String>) -> Result<Vec<String>, Error> {
let inputs = self let inputs = self
.0 .0
.values() .values()
@ -56,18 +130,18 @@ impl ParsedModules {
let capacity = inputs.len(); let capacity = inputs.len();
let mut graph = Graph::<(), ()>::with_capacity(capacity, capacity * 5); let mut graph = Graph::<String, ()>::with_capacity(capacity, capacity * 5);
// TODO: maybe use a bimap?
let mut indices = HashMap::with_capacity(capacity); let mut indices = HashMap::with_capacity(capacity);
let mut values = HashMap::with_capacity(capacity);
let mut our_indices = BTreeSet::new();
for (value, _) in &inputs { for (value, _) in &inputs {
let index = graph.add_node(()); let index = graph.add_node(value.to_string());
indices.insert(value.clone(), index); indices.insert(value.clone(), index);
if our_modules.contains(value) {
values.insert(index, value.clone()); our_indices.insert(index);
}
} }
for (value, deps) in inputs { 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) { match algo::toposort(&graph, None) {
Ok(sequence) => { Ok(sequence) => {
let sequence = sequence let sequence = sequence
.iter() .iter()
.filter_map(|i| values.remove(i)) .filter_map(|i| graph.node_weight(*i))
.rev() .rev()
.cloned()
.collect(); .collect();
Ok(sequence) Ok(sequence)
@ -95,11 +199,12 @@ impl ParsedModules {
let mut path = vec![]; 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 let modules = path
.iter() .iter()
.filter_map(|index| values.remove(index)) .filter_map(|i| graph.node_weight(*i))
.cloned()
.collect(); .collect();
Err(Error::ImportCycle { modules }) Err(Error::ImportCycle { modules })
@ -140,12 +245,12 @@ impl DerefMut for ParsedModules {
} }
} }
fn find_cycle( fn find_cycle<W>(
origin: NodeIndex, origin: NodeIndex,
parent: NodeIndex, parent: NodeIndex,
graph: &petgraph::Graph<(), ()>, graph: &petgraph::Graph<W, ()>,
path: &mut Vec<NodeIndex>, path: &mut Vec<NodeIndex>,
seen: &mut HashSet<NodeIndex>, seen: &mut BTreeSet<NodeIndex>,
) -> bool { ) -> bool {
seen.insert(parent); seen.insert(parent);