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,
};
use std::{
collections::HashMap,
collections::{BTreeSet, HashMap},
fs::{self, File},
io::BufReader,
path::{Path, PathBuf},
@ -185,8 +185,6 @@ where
destination: Option<PathBuf>,
include_dependencies: bool,
) -> Result<(), Vec<Error>> {
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<Error>> {
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<Error>> {
fn with_dependencies(&mut self, parsed_packages: &mut ParsedModules) -> Result<(), Vec<Error>> {
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::<HashMap<_, _>>::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<Error>> {
let our_modules: BTreeSet<String> = 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(
for name in modules.sequence(&our_modules)? {
if let Some(module) = modules.remove(&name) {
let (checked_module, warnings) = module.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,
})?;
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);
}
}

View File

@ -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<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>);
@ -47,7 +121,7 @@ impl ParsedModules {
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
.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::<String, ()>::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<W>(
origin: NodeIndex,
parent: NodeIndex,
graph: &petgraph::Graph<(), ()>,
graph: &petgraph::Graph<W, ()>,
path: &mut Vec<NodeIndex>,
seen: &mut HashSet<NodeIndex>,
seen: &mut BTreeSet<NodeIndex>,
) -> bool {
seen.insert(parent);