1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! Types to apply filter to input types

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},
};

/// A predicate property that determines whether a file satisfies a certain condition
pub trait FileFilter: dyn_clone::DynClone + Send + Sync {
    /// The predicate function that should return if the given `file` should be included.
    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)
    }
}

/// An [FileFilter] that matches all solidity files that end with `.t.sol`
#[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,
        }
    }
}

/// A type that can apply a filter to a set of preprocessed [Sources] in order to set sparse
/// output for specific files
#[derive(Default)]
pub enum SparseOutputFilter<'a> {
    /// Sets the configured [OutputSelection] for dirty files only.
    ///
    /// In other words, we request the output of solc only for files that have been detected as
    /// _dirty_.
    #[default]
    Optimized,
    /// Apply an additional filter to [Sources] to
    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
        }
    }

    /// While solc needs all the files to compile the actual _dirty_ files, we can tell solc to
    /// output everything for those dirty files as currently configured in the settings, but output
    /// nothing for the other files that are _not_ dirty.
    ///
    /// This will modify the [OutputSelection] of the [CompilerSettings] so that we explicitly
    /// select the files' output based on their state.
    ///
    /// This also takes the project's graph as input, this allows us to check if the files the
    /// filter matches depend on libraries that need to be linked
    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 we have a custom filter and file does not match, we skip it.
                if let Self::Custom(f) = self {
                    if !f.is_match(file) {
                        return vec![];
                    }
                }

                // Collect compilation dependencies for sources needing compilation.
                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();

        // Remove clean sources, those will be read from cache.
        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);

            // set output selections
            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"),
        }
    }
}