knave 0.14.22

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::rc::Rc;

use super::name_records::NameRecords;
use super::set_aside_drop::SetAsideDrop;
use super::set_aside_source::SetAsideSource;
use crate::diagnostic::Diagnostic;
use crate::expand::{BoundName, InputTerms, SetAsideInput, SetAsideProblems};

pub struct DroppedDefinitions {
    source: SetAsideSource,
    dropped: Vec<BoundName>,
    replacing: Rc<str>,
}

impl DroppedDefinitions {
    pub fn left_behind(
        source: SetAsideSource,
        earlier: &InputTerms,
        entered: &InputTerms,
    ) -> Option<Self> {
        let dropped: Vec<BoundName> = earlier
            .bound()
            .iter()
            .filter(|name| !entered.binds(name))
            .cloned()
            .collect();
        let replacing = entered.bound().iter().find(|bound| earlier.binds(bound))?;
        (!dropped.is_empty()).then(|| Self {
            source,
            dropped,
            replacing: Rc::clone(&replacing.name),
        })
    }
}

impl SetAsideDrop for DroppedDefinitions {
    fn warning(&self, setter: &str) -> Diagnostic {
        let names: Vec<&str> = self.dropped.iter().map(|name| &*name.name).collect();
        let input = SetAsideInput {
            names: &names,
            source: &self.source.name,
            setter,
        };
        SetAsideProblems::dropped_definitions(&input, &self.replacing, self.dropped[0].span)
    }

    fn record(&self, records: &mut NameRecords, setter: &str) {
        for name in &self.dropped {
            let reason = SetAsideProblems::set_aside_reason(&self.replacing, &name.name);
            records.set_aside(name, setter, &reason);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cli::name_records::Tense;
    use crate::core::Namespace;
    use crate::diagnostic::MissingName;
    use crate::source::{SourceId, Span};

    struct DefinitionsFixture;

    impl DefinitionsFixture {
        const SOURCE: &str = "<input 2>";
        const SETTER: &str = "<input 3>";

        fn name(name: &str) -> BoundName {
            BoundName {
                name: name.into(),
                namespace: Namespace::Value,
                span: Span::new(SourceId::new(0), 0..1),
            }
        }

        fn terms(names: &[&str]) -> InputTerms {
            InputTerms::binding(names.iter().map(|name| Self::name(name)).collect())
        }

        fn source() -> SetAsideSource {
            SetAsideSource {
                name: Self::SOURCE.to_string(),
                span: Span::new(SourceId::new(0), 0..1),
            }
        }

        fn dropped() -> DroppedDefinitions {
            DroppedDefinitions::left_behind(
                Self::source(),
                &Self::terms(&["a", "b"]),
                &Self::terms(&["b"]),
            )
            .expect("`b` replaces the input that also defined `a`")
        }
    }

    #[test]
    fn a_replacement_that_keeps_every_name_leaves_nothing_behind() {
        let earlier = DefinitionsFixture::terms(&["a"]);
        let left =
            DroppedDefinitions::left_behind(DefinitionsFixture::source(), &earlier, &earlier);
        assert!(left.is_none());
    }

    #[test]
    fn an_input_that_replaces_nothing_leaves_nothing_behind() {
        let left = DroppedDefinitions::left_behind(
            DefinitionsFixture::source(),
            &DefinitionsFixture::terms(&["a"]),
            &DefinitionsFixture::terms(&["c"]),
        );
        assert!(left.is_none());
    }

    #[test]
    fn the_warning_names_the_definitions_left_behind() {
        let warning = DefinitionsFixture::dropped().warning(DefinitionsFixture::SETTER);
        assert_eq!(warning.title(), "set aside `a` after `<input 3>`");
        assert_eq!(warning.notes(), ["`b` replaced the input that defined `a`"]);
    }

    #[test]
    fn each_definition_left_behind_is_recorded_as_set_aside() {
        let mut records = NameRecords::default();
        DefinitionsFixture::dropped().record(&mut records, DefinitionsFixture::SETTER);
        let missing = MissingName {
            name: "a".into(),
            namespace: Namespace::Value,
        };
        assert_eq!(
            records.note(&missing, Tense::Done).as_deref(),
            Some("`<input 3>` set aside `a`: `b` replaced the input that defined `a`")
        );
    }
}