foundry_compilers/
filter.rs

1//! Types to apply filter to input types
2
3use crate::{
4    compilers::{multi::MultiCompilerParsedSource, CompilerSettings, ParsedSource},
5    resolver::{parse::SolData, GraphEdges},
6    Sources,
7};
8use foundry_compilers_artifacts::output_selection::OutputSelection;
9use std::{
10    collections::HashSet,
11    fmt,
12    path::{Path, PathBuf},
13};
14
15/// A predicate property that determines whether a file satisfies a certain condition
16pub trait FileFilter: dyn_clone::DynClone + Send + Sync {
17    /// The predicate function that should return if the given `file` should be included.
18    fn is_match(&self, file: &Path) -> bool;
19}
20
21dyn_clone::clone_trait_object!(FileFilter);
22
23impl<F: Fn(&Path) -> bool + Clone + Send + Sync> FileFilter for F {
24    fn is_match(&self, file: &Path) -> bool {
25        (self)(file)
26    }
27}
28
29/// An [FileFilter] that matches all solidity files that end with `.t.sol`
30#[derive(Clone, Default)]
31pub struct TestFileFilter {
32    _priv: (),
33}
34
35impl fmt::Debug for TestFileFilter {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("TestFileFilter").finish()
38    }
39}
40
41impl fmt::Display for TestFileFilter {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.write_str("TestFileFilter")
44    }
45}
46
47impl FileFilter for TestFileFilter {
48    fn is_match(&self, file: &Path) -> bool {
49        file.file_name().and_then(|s| s.to_str()).map(|s| s.ends_with(".t.sol")).unwrap_or_default()
50    }
51}
52
53pub trait MaybeSolData {
54    fn sol_data(&self) -> Option<&SolData>;
55}
56
57impl MaybeSolData for SolData {
58    fn sol_data(&self) -> Option<&SolData> {
59        Some(self)
60    }
61}
62
63impl MaybeSolData for MultiCompilerParsedSource {
64    fn sol_data(&self) -> Option<&SolData> {
65        match self {
66            Self::Solc(data) => Some(data),
67            _ => None,
68        }
69    }
70}
71
72/// A type that can apply a filter to a set of preprocessed [Sources] in order to set sparse
73/// output for specific files
74#[derive(Default)]
75pub enum SparseOutputFilter<'a> {
76    /// Sets the configured [OutputSelection] for dirty files only.
77    ///
78    /// In other words, we request the output of solc only for files that have been detected as
79    /// _dirty_.
80    #[default]
81    Optimized,
82    /// Apply an additional filter to [Sources] to
83    Custom(&'a dyn FileFilter),
84}
85
86impl<'a> SparseOutputFilter<'a> {
87    pub fn new(filter: Option<&'a dyn FileFilter>) -> Self {
88        if let Some(f) = filter {
89            SparseOutputFilter::Custom(f)
90        } else {
91            SparseOutputFilter::Optimized
92        }
93    }
94
95    /// While solc needs all the files to compile the actual _dirty_ files, we can tell solc to
96    /// output everything for those dirty files as currently configured in the settings, but output
97    /// nothing for the other files that are _not_ dirty.
98    ///
99    /// This will modify the [OutputSelection] of the [CompilerSettings] so that we explicitly
100    /// select the files' output based on their state.
101    ///
102    /// This also takes the project's graph as input, this allows us to check if the files the
103    /// filter matches depend on libraries that need to be linked
104    pub fn sparse_sources<D: ParsedSource, S: CompilerSettings>(
105        &self,
106        sources: &Sources,
107        settings: &mut S,
108        graph: &GraphEdges<D>,
109    ) -> Vec<PathBuf> {
110        let mut full_compilation: HashSet<PathBuf> = sources
111            .dirty_files()
112            .flat_map(|file| {
113                // If we have a custom filter and file does not match, we skip it.
114                if let Self::Custom(f) = self {
115                    if !f.is_match(file) {
116                        return vec![];
117                    }
118                }
119
120                // Collect compilation dependencies for sources needing compilation.
121                let mut required_sources = vec![file.clone()];
122                if let Some(data) = graph.get_parsed_source(file) {
123                    let imports = graph.imports(file).into_iter().filter_map(|import| {
124                        graph.get_parsed_source(import).map(|data| (import, data))
125                    });
126                    for import in data.compilation_dependencies(imports) {
127                        let import = import.to_path_buf();
128
129                        #[cfg(windows)]
130                        let import = {
131                            use path_slash::PathBufExt;
132
133                            PathBuf::from(import.to_slash_lossy().to_string())
134                        };
135
136                        required_sources.push(import);
137                    }
138                }
139
140                required_sources
141            })
142            .collect();
143
144        // Remove clean sources, those will be read from cache.
145        full_compilation.retain(|file| sources.0.get(file).is_some_and(|s| s.is_dirty()));
146
147        settings.update_output_selection(|selection| {
148            trace!(
149                "optimizing output selection for {} sources",
150                sources.len() - full_compilation.len()
151            );
152            let default_selection = selection
153                .as_mut()
154                .remove("*")
155                .unwrap_or_else(OutputSelection::default_file_output_selection);
156
157            // set output selections
158            for file in sources.0.keys() {
159                let key = file.display().to_string();
160                let output = if full_compilation.contains(file) {
161                    default_selection.clone()
162                } else {
163                    OutputSelection::empty_file_output_select()
164                };
165                selection.as_mut().insert(key, output);
166            }
167        });
168
169        full_compilation.into_iter().collect()
170    }
171}
172
173impl fmt::Debug for SparseOutputFilter<'_> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        match self {
176            SparseOutputFilter::Optimized => f.write_str("Optimized"),
177            SparseOutputFilter::Custom(_) => f.write_str("Custom"),
178        }
179    }
180}