1use crate::{
4 artifacts::{output_selection::OutputSelection, Settings},
5 resolver::GraphEdges,
6 Source, Sources,
7};
8use std::{
9 collections::BTreeMap,
10 fmt,
11 fmt::Formatter,
12 path::{Path, PathBuf},
13};
14
15pub trait FileFilter {
17 fn is_match(&self, file: &Path) -> bool;
19}
20
21impl<F> FileFilter for F
22where
23 F: Fn(&Path) -> bool,
24{
25 fn is_match(&self, file: &Path) -> bool {
26 (self)(file)
27 }
28}
29
30#[derive(Default)]
32pub struct TestFileFilter {
33 _priv: (),
34}
35
36impl fmt::Debug for TestFileFilter {
37 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
38 f.debug_struct("TestFileFilter").finish()
39 }
40}
41
42impl fmt::Display for TestFileFilter {
43 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
44 f.write_str("TestFileFilter")
45 }
46}
47
48impl FileFilter for TestFileFilter {
49 fn is_match(&self, file: &Path) -> bool {
50 file.file_name().and_then(|s| s.to_str()).map(|s| s.ends_with(".t.sol")).unwrap_or_default()
51 }
52}
53
54#[derive(Default)]
57pub enum SparseOutputFilter {
58 #[default]
63 AllDirty,
64 Custom(Box<dyn FileFilter>),
66}
67
68impl SparseOutputFilter {
69 pub fn sparse_sources(
79 &self,
80 sources: FilteredSources,
81 settings: &mut Settings,
82 graph: &GraphEdges,
83 ) -> Sources {
84 match self {
85 SparseOutputFilter::AllDirty => {
86 if !sources.all_dirty() {
87 Self::all_dirty(&sources, settings)
88 }
89 }
90 SparseOutputFilter::Custom(f) => {
91 Self::apply_custom_filter(&sources, settings, graph, f)
92 }
93 };
94 sources.into()
95 }
96
97 #[allow(clippy::borrowed_box)]
106 fn apply_custom_filter(
107 sources: &FilteredSources,
108 settings: &mut Settings,
109 graph: &GraphEdges,
110 f: &Box<dyn FileFilter>,
111 ) {
112 tracing::trace!("optimizing output selection with custom filter",);
113 let selection = settings
114 .output_selection
115 .as_mut()
116 .remove("*")
117 .unwrap_or_else(OutputSelection::default_file_output_selection);
118
119 for (file, source) in sources.0.iter() {
120 let key = format!("{}", file.display());
121 if source.is_dirty() && f.is_match(file) {
122 settings.output_selection.as_mut().insert(key, selection.clone());
123
124 for link in graph.get_link_references(file) {
128 settings
129 .output_selection
130 .as_mut()
131 .insert(format!("{}", link.display()), selection.clone());
132 }
133 } else if !settings.output_selection.as_ref().contains_key(&key) {
134 tracing::trace!("using pruned output selection for {}", file.display());
135 settings
136 .output_selection
137 .as_mut()
138 .insert(key, OutputSelection::empty_file_output_select());
139 }
140 }
141 }
142
143 fn all_dirty(sources: &FilteredSources, settings: &mut Settings) {
145 tracing::trace!(
147 "optimizing output selection for {}/{} sources",
148 sources.clean().count(),
149 sources.len()
150 );
151
152 let selection = settings
153 .output_selection
154 .as_mut()
155 .remove("*")
156 .unwrap_or_else(OutputSelection::default_file_output_selection);
157
158 for (file, source) in sources.0.iter() {
159 if source.is_dirty() {
160 settings
161 .output_selection
162 .as_mut()
163 .insert(format!("{}", file.display()), selection.clone());
164 } else {
165 tracing::trace!("using pruned output selection for {}", file.display());
166 settings.output_selection.as_mut().insert(
167 format!("{}", file.display()),
168 OutputSelection::empty_file_output_select(),
169 );
170 }
171 }
172 }
173}
174
175impl From<Box<dyn FileFilter>> for SparseOutputFilter {
176 fn from(f: Box<dyn FileFilter>) -> Self {
177 SparseOutputFilter::Custom(f)
178 }
179}
180
181impl fmt::Debug for SparseOutputFilter {
182 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
183 match self {
184 SparseOutputFilter::AllDirty => f.write_str("AllDirty"),
185 SparseOutputFilter::Custom(_) => f.write_str("Custom"),
186 }
187 }
188}
189
190#[derive(Debug, Clone, Eq, PartialEq)]
192pub struct FilteredSources(pub BTreeMap<PathBuf, FilteredSource>);
193
194impl FilteredSources {
195 pub fn is_empty(&self) -> bool {
196 self.0.is_empty()
197 }
198
199 pub fn len(&self) -> usize {
200 self.0.len()
201 }
202
203 pub fn all_dirty(&self) -> bool {
205 self.0.values().all(|s| s.is_dirty())
206 }
207
208 pub fn dirty(&self) -> impl Iterator<Item = (&PathBuf, &FilteredSource)> + '_ {
210 self.0.iter().filter(|(_, s)| s.is_dirty())
211 }
212
213 pub fn clean(&self) -> impl Iterator<Item = (&PathBuf, &FilteredSource)> + '_ {
215 self.0.iter().filter(|(_, s)| !s.is_dirty())
216 }
217
218 pub fn dirty_files(&self) -> impl Iterator<Item = &PathBuf> + fmt::Debug + '_ {
220 self.0.iter().filter_map(|(k, s)| s.is_dirty().then_some(k))
221 }
222}
223
224impl From<FilteredSources> for Sources {
225 fn from(sources: FilteredSources) -> Self {
226 sources.0.into_iter().map(|(k, v)| (k, v.into_source())).collect()
227 }
228}
229
230impl From<Sources> for FilteredSources {
231 fn from(s: Sources) -> Self {
232 FilteredSources(s.into_iter().map(|(key, val)| (key, FilteredSource::Dirty(val))).collect())
233 }
234}
235
236impl From<BTreeMap<PathBuf, FilteredSource>> for FilteredSources {
237 fn from(s: BTreeMap<PathBuf, FilteredSource>) -> Self {
238 FilteredSources(s)
239 }
240}
241
242impl AsRef<BTreeMap<PathBuf, FilteredSource>> for FilteredSources {
243 fn as_ref(&self) -> &BTreeMap<PathBuf, FilteredSource> {
244 &self.0
245 }
246}
247
248impl AsMut<BTreeMap<PathBuf, FilteredSource>> for FilteredSources {
249 fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, FilteredSource> {
250 &mut self.0
251 }
252}
253
254#[derive(Debug, Clone, Eq, PartialEq)]
256pub enum FilteredSource {
257 Dirty(Source),
259 Clean(Source),
262}
263
264impl FilteredSource {
265 pub fn source(&self) -> &Source {
267 match self {
268 FilteredSource::Dirty(s) => s,
269 FilteredSource::Clean(s) => s,
270 }
271 }
272
273 pub fn into_source(self) -> Source {
275 match self {
276 FilteredSource::Dirty(s) => s,
277 FilteredSource::Clean(s) => s,
278 }
279 }
280
281 pub fn is_dirty(&self) -> bool {
283 matches!(self, FilteredSource::Dirty(_))
284 }
285}
286
287#[derive(Debug)]
289pub struct FilteredSourceInfo {
290 pub file: PathBuf,
292
293 pub source: Source,
295
296 pub idx: usize,
298
299 pub dirty: bool,
303}