knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
use super::arguments::Arguments;
use super::host::Host;
use super::host_problems::HostProblems;
use crate::diagnostic::Diagnostic;

pub(super) struct BuiltinHost<'h, 'a> {
    host: &'h mut dyn Host,
    call: &'a Arguments<'a>,
}

impl<'h, 'a> BuiltinHost<'h, 'a> {
    pub(super) fn new(host: &'h mut dyn Host, call: &'a Arguments<'a>) -> Self {
        Self { host, call }
    }

    pub(super) fn granted<Group: ?Sized>(
        self,
        accessor: impl FnOnce(&'h mut dyn Host) -> Option<&'h mut Group>,
    ) -> Result<&'h mut Group, Diagnostic> {
        accessor(self.host).ok_or_else(|| {
            HostProblems::missing_group_of_builtin(self.call.function(), self.call.span())
        })
    }
}

#[cfg(test)]
mod tests {
    use super::BuiltinHost;
    use crate::diagnostic::{Diagnostic, DiagnosticCode};
    use crate::interpret::arguments::{Arguments, BuiltinSite};
    use crate::interpret::partial_host::PartialHost;
    use crate::interpret::{StepBudget, WorkMeter};
    use crate::source::{SourceId, Span};

    struct BuiltinHostFixture;

    impl BuiltinHostFixture {
        fn site() -> BuiltinSite {
            BuiltinSite {
                function: "warn",
                span: Span::new(SourceId::new(0), 3..9),
            }
        }

        fn warnings_granted(mut host: PartialHost) -> Result<(), Diagnostic> {
            let meter = WorkMeter::new(StepBudget::full(), Self::site().span);
            let call = Arguments::new(Self::site(), Vec::new(), &meter);
            BuiltinHost::new(&mut host, &call)
                .granted(|host| host.warnings())
                .map(drop)
        }
    }

    #[test]
    fn a_group_the_host_offers_is_granted() {
        assert!(BuiltinHostFixture::warnings_granted(PartialHost::offering_its_groups()).is_ok());
    }

    #[test]
    fn a_group_the_host_lacks_is_refused_at_the_call() {
        let refusal = BuiltinHostFixture::warnings_granted(PartialHost::offering_nothing())
            .expect_err("the host offers no warnings");
        assert_eq!(refusal.code(), Some(&DiagnosticCode::MISSING_HOST_GROUP));
        assert!(
            refusal
                .labels()
                .map(|label| label.span())
                .eq([BuiltinHostFixture::site().span])
        );
    }

    #[test]
    fn the_refusal_names_the_built_in() {
        let refusal = BuiltinHostFixture::warnings_granted(PartialHost::offering_nothing())
            .expect_err("the host offers no warnings");
        assert!(refusal.title().contains("`warn`"), "{}", refusal.title());
    }
}