Skip to main content

fallow_config/config/
entry_effect.rs

1use std::sync::{Arc, Mutex};
2
3/// Which entries of one config list had an effect in the latest pass.
4///
5/// The stage that decides the effect (source discovery for `ignorePatterns`,
6/// entry point discovery for `entry`) runs before the dead-code envelope is
7/// built and does not return its answer to that point. It writes the answer
8/// here, and the envelope reads it through the resolved config.
9///
10/// Each pass replaces the whole answer. It never clears it first, so two
11/// passes over the same config that run at the same time cannot leave a
12/// half-written answer.
13#[derive(Debug, Clone, Default)]
14pub struct EntryEffectRecord {
15    effective: Arc<Mutex<Option<Vec<bool>>>>,
16}
17
18impl EntryEffectRecord {
19    /// Store the answer of one pass: one flag per config entry, in config
20    /// order, `true` when the entry had an effect.
21    pub fn record(&self, effective: Vec<bool>) {
22        if let Ok(mut slot) = self.effective.lock() {
23            *slot = Some(effective);
24        }
25    }
26
27    /// The entries of `entries` that had no effect in the latest pass, in
28    /// config order. Empty when no pass recorded an answer, or when the
29    /// answer has another length than `entries`.
30    #[must_use]
31    pub fn ineffective<'a>(&self, entries: &'a [String]) -> Vec<&'a str> {
32        let Ok(slot) = self.effective.lock() else {
33            return Vec::new();
34        };
35        let Some(effective) = slot.as_ref() else {
36            return Vec::new();
37        };
38        if effective.len() != entries.len() {
39            return Vec::new();
40        }
41        entries
42            .iter()
43            .zip(effective)
44            .filter(|(_, effective)| !**effective)
45            .map(|(entry, _)| entry.as_str())
46            .collect()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    fn entries(values: &[&str]) -> Vec<String> {
55        values.iter().map(|value| (*value).to_owned()).collect()
56    }
57
58    #[test]
59    fn no_answer_reports_nothing() {
60        let record = EntryEffectRecord::default();
61        assert!(record.ineffective(&entries(&["a"])).is_empty());
62    }
63
64    #[test]
65    fn the_latest_answer_wins_and_clones_share_it() {
66        let record = EntryEffectRecord::default();
67        let clone = record.clone();
68        let list = entries(&["a", "b", "c"]);
69        clone.record(vec![true, false, false]);
70        assert_eq!(record.ineffective(&list), vec!["b", "c"]);
71        record.record(vec![true, true, false]);
72        assert_eq!(clone.ineffective(&list), vec!["c"]);
73    }
74
75    #[test]
76    fn an_answer_of_another_length_reports_nothing() {
77        let record = EntryEffectRecord::default();
78        record.record(vec![false]);
79        assert!(record.ineffective(&entries(&["a", "b"])).is_empty());
80    }
81}