use crate::{
artifacts::output_selection::OutputSelection,
compilers::CompilerSettings,
resolver::{parse::SolData, GraphEdges},
Source, Sources,
};
use std::{
collections::{BTreeMap, HashSet},
fmt::{self, Formatter},
path::{Path, PathBuf},
};
pub trait FileFilter {
fn is_match(&self, file: &Path) -> bool;
}
impl<F: Fn(&Path) -> bool> FileFilter for F {
fn is_match(&self, file: &Path) -> bool {
(self)(file)
}
}
#[derive(Default)]
pub struct TestFileFilter {
_priv: (),
}
impl fmt::Debug for TestFileFilter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TestFileFilter").finish()
}
}
impl fmt::Display for TestFileFilter {
fn fmt(&self, f: &mut 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 struct SolcSparseFileFilter<T> {
file_filter: T,
}
impl<T> SolcSparseFileFilter<T> {
pub fn new(file_filter: T) -> Self {
Self { file_filter }
}
}
impl<T: FileFilter> FileFilter for SolcSparseFileFilter<T> {
fn is_match(&self, file: &Path) -> bool {
self.file_filter.is_match(file)
}
}
impl<T: FileFilter> SparseOutputFileFilter<SolData> for SolcSparseFileFilter<T> {
fn sparse_sources(&self, file: &Path, graph: &GraphEdges<SolData>) -> Vec<PathBuf> {
if !self.file_filter.is_match(file) {
return vec![];
}
let mut sources_to_compile = vec![file.to_path_buf()];
for import in graph.imports(file) {
if let Some(parsed) = graph.get_parsed_source(import) {
if !parsed.libraries.is_empty() {
sources_to_compile.push(import.to_path_buf());
}
}
}
sources_to_compile
}
}
pub trait SparseOutputFileFilter<D>: FileFilter {
fn sparse_sources(&self, file: &Path, graph: &GraphEdges<D>) -> Vec<PathBuf>;
}
#[derive(Default)]
pub enum SparseOutputFilter<D> {
#[default]
Optimized,
Custom(Box<dyn SparseOutputFileFilter<D>>),
}
impl<D> SparseOutputFilter<D> {
pub fn sparse_sources<S: CompilerSettings>(
&self,
sources: FilteredSources,
settings: &mut S,
graph: &GraphEdges<D>,
) -> Sources {
match self {
SparseOutputFilter::Optimized => {
if !sources.all_dirty() {
Self::optimize(&sources, settings)
}
}
SparseOutputFilter::Custom(f) => {
Self::apply_custom_filter(&sources, settings, graph, &**f)
}
};
sources.into()
}
fn apply_custom_filter<S: CompilerSettings>(
sources: &FilteredSources,
settings: &mut S,
graph: &GraphEdges<D>,
f: &dyn SparseOutputFileFilter<D>,
) {
trace!("optimizing output selection with custom filter");
let selection = settings
.output_selection_mut()
.as_mut()
.remove("*")
.unwrap_or_else(OutputSelection::default_file_output_selection);
let mut full_compilation = HashSet::new();
for (file, source) in sources.0.iter() {
if source.is_dirty() {
for source in f.sparse_sources(file, graph) {
full_compilation.insert(source);
}
}
}
for file in sources.0.keys() {
let key = format!("{}", file.display());
if full_compilation.contains(file) {
settings.output_selection_mut().as_mut().insert(key, selection.clone());
} else {
settings
.output_selection_mut()
.as_mut()
.insert(key, OutputSelection::empty_file_output_select());
}
}
}
fn optimize<S: CompilerSettings>(sources: &FilteredSources, settings: &mut S) {
trace!(
"optimizing output selection for {}/{} sources",
sources.clean().count(),
sources.len()
);
let selection = settings
.output_selection_mut()
.as_mut()
.remove("*")
.unwrap_or_else(OutputSelection::default_file_output_selection);
for (file, kind) in sources.0.iter() {
match kind {
SourceCompilationKind::Complete(_) => {
settings
.output_selection_mut()
.as_mut()
.insert(format!("{}", file.display()), selection.clone());
}
SourceCompilationKind::Optimized(_) => {
trace!("using pruned output selection for {}", file.display());
settings.output_selection_mut().as_mut().insert(
format!("{}", file.display()),
OutputSelection::empty_file_output_select(),
);
}
}
}
}
}
impl<D> From<Box<dyn SparseOutputFileFilter<D>>> for SparseOutputFilter<D> {
fn from(f: Box<dyn SparseOutputFileFilter<D>>) -> Self {
SparseOutputFilter::Custom(f)
}
}
impl<D> fmt::Debug for SparseOutputFilter<D> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SparseOutputFilter::Optimized => f.write_str("Optimized"),
SparseOutputFilter::Custom(_) => f.write_str("Custom"),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FilteredSources(pub BTreeMap<PathBuf, SourceCompilationKind>);
impl FilteredSources {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn all_dirty(&self) -> bool {
self.0.values().all(|s| s.is_dirty())
}
pub fn dirty(&self) -> impl Iterator<Item = (&PathBuf, &SourceCompilationKind)> + '_ {
self.0.iter().filter(|(_, s)| s.is_dirty())
}
pub fn clean(&self) -> impl Iterator<Item = (&PathBuf, &SourceCompilationKind)> + '_ {
self.0.iter().filter(|(_, s)| !s.is_dirty())
}
pub fn dirty_files(&self) -> impl Iterator<Item = &PathBuf> + fmt::Debug + '_ {
self.0.iter().filter_map(|(k, s)| s.is_dirty().then_some(k))
}
}
impl From<FilteredSources> for Sources {
fn from(sources: FilteredSources) -> Self {
sources.0.into_iter().map(|(k, v)| (k, v.into_source())).collect()
}
}
impl From<Sources> for FilteredSources {
fn from(s: Sources) -> Self {
FilteredSources(
s.into_iter().map(|(key, val)| (key, SourceCompilationKind::Complete(val))).collect(),
)
}
}
impl From<BTreeMap<PathBuf, SourceCompilationKind>> for FilteredSources {
fn from(s: BTreeMap<PathBuf, SourceCompilationKind>) -> Self {
FilteredSources(s)
}
}
impl AsRef<BTreeMap<PathBuf, SourceCompilationKind>> for FilteredSources {
fn as_ref(&self) -> &BTreeMap<PathBuf, SourceCompilationKind> {
&self.0
}
}
impl AsMut<BTreeMap<PathBuf, SourceCompilationKind>> for FilteredSources {
fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, SourceCompilationKind> {
&mut self.0
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SourceCompilationKind {
Complete(Source),
Optimized(Source),
}
impl SourceCompilationKind {
pub fn source(&self) -> &Source {
match self {
SourceCompilationKind::Complete(s) => s,
SourceCompilationKind::Optimized(s) => s,
}
}
pub fn into_source(self) -> Source {
match self {
SourceCompilationKind::Complete(s) => s,
SourceCompilationKind::Optimized(s) => s,
}
}
pub fn is_dirty(&self) -> bool {
matches!(self, SourceCompilationKind::Complete(_))
}
}