use crate::{
compilers::{multi::MultiCompilerParsedSource, CompilerSettings, ParsedSource},
resolver::{parse::SolData, GraphEdges},
Sources,
};
use foundry_compilers_artifacts::output_selection::OutputSelection;
use std::{
collections::HashSet,
fmt,
path::{Path, PathBuf},
};
pub trait FileFilter: dyn_clone::DynClone + Send + Sync {
fn is_match(&self, file: &Path) -> bool;
}
dyn_clone::clone_trait_object!(FileFilter);
impl<F: Fn(&Path) -> bool + Clone + Send + Sync> FileFilter for F {
fn is_match(&self, file: &Path) -> bool {
(self)(file)
}
}
#[derive(Clone, Default)]
pub struct TestFileFilter {
_priv: (),
}
impl fmt::Debug for TestFileFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TestFileFilter").finish()
}
}
impl fmt::Display for TestFileFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TestFileFilter")
}
}
impl FileFilter for TestFileFilter {
fn is_match(&self, file: &Path) -> bool {
file.file_name().and_then(|s| s.to_str()).map(|s| s.ends_with(".t.sol")).unwrap_or_default()
}
}
pub trait MaybeSolData {
fn sol_data(&self) -> Option<&SolData>;
}
impl MaybeSolData for SolData {
fn sol_data(&self) -> Option<&SolData> {
Some(self)
}
}
impl MaybeSolData for MultiCompilerParsedSource {
fn sol_data(&self) -> Option<&SolData> {
match self {
Self::Solc(data) => Some(data),
_ => None,
}
}
}
#[derive(Default)]
pub enum SparseOutputFilter<'a> {
#[default]
Optimized,
Custom(&'a dyn FileFilter),
}
impl<'a> SparseOutputFilter<'a> {
pub fn new(filter: Option<&'a dyn FileFilter>) -> Self {
if let Some(f) = filter {
SparseOutputFilter::Custom(f)
} else {
SparseOutputFilter::Optimized
}
}
pub fn sparse_sources<D: ParsedSource, S: CompilerSettings>(
&self,
sources: &Sources,
settings: &mut S,
graph: &GraphEdges<D>,
) -> Vec<PathBuf> {
let mut full_compilation: HashSet<PathBuf> = sources
.dirty_files()
.flat_map(|file| {
if let Self::Custom(f) = self {
if !f.is_match(file) {
return vec![];
}
}
let mut required_sources = vec![file.clone()];
if let Some(data) = graph.get_parsed_source(file) {
let imports = graph.imports(file).into_iter().filter_map(|import| {
graph.get_parsed_source(import).map(|data| (import.as_path(), data))
});
for import in data.compilation_dependencies(imports) {
let import = import.to_path_buf();
#[cfg(windows)]
let import = {
use path_slash::PathBufExt;
PathBuf::from(import.to_slash_lossy().to_string())
};
required_sources.push(import);
}
}
required_sources
})
.collect();
full_compilation.retain(|file| sources.0.get(file).map_or(false, |s| s.is_dirty()));
settings.update_output_selection(|selection| {
trace!(
"optimizing output selection for {} sources",
sources.len() - full_compilation.len()
);
let default_selection = selection
.as_mut()
.remove("*")
.unwrap_or_else(OutputSelection::default_file_output_selection);
for file in sources.0.keys() {
let key = file.display().to_string();
let output = if full_compilation.contains(file) {
default_selection.clone()
} else {
OutputSelection::empty_file_output_select()
};
selection.as_mut().insert(key, output);
}
});
full_compilation.into_iter().collect()
}
}
impl<'a> fmt::Debug for SparseOutputFilter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SparseOutputFilter::Optimized => f.write_str("Optimized"),
SparseOutputFilter::Custom(_) => f.write_str("Custom"),
}
}
}