knave 0.14.6

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use super::column_head::ColumnHead;
use super::constructor::Constructor;
use super::constructor_catalog::ConstructorCatalog;
use super::constructor_tag::ConstructorTag;
use super::pattern_matrix::PatternMatrix;
use super::query::Query;
use crate::core::Literal;

pub(super) struct Usefulness<'c, 'a> {
    catalog: &'c ConstructorCatalog<'a>,
}

impl<'c, 'a> Usefulness<'c, 'a> {
    pub(super) fn new(catalog: &'c ConstructorCatalog<'a>) -> Self {
        Self { catalog }
    }

    pub(super) fn is_useful(&self, matrix: &PatternMatrix, query: Query) -> bool {
        if query.is_empty() {
            return matrix.is_empty();
        }
        if matrix.is_empty() {
            return true;
        }
        if matrix.has_wild_row() {
            return false;
        }
        match query.first_pattern().kind().head() {
            ColumnHead::Any => self.is_useful_wild(matrix, query),
            ColumnHead::Constructor(tag) => self.is_useful_tagged(matrix, query, tag),
            ColumnHead::Literal(literal) => self.is_useful_literal(matrix, query, literal),
        }
    }

    fn is_useful_wild(&self, matrix: &PatternMatrix, query: Query) -> bool {
        match self
            .catalog
            .constructors_named_by(query.first_type(), matrix)
        {
            Some(constructors) => constructors
                .iter()
                .any(|constructor| self.is_useful_constructor(matrix, query, constructor)),
            None => self.is_useful(&matrix.default_rows(), query.rest()),
        }
    }

    fn is_useful_tagged(&self, matrix: &PatternMatrix, query: Query, tag: ConstructorTag) -> bool {
        self.catalog
            .constructors(query.first_type())
            .and_then(|constructors| {
                constructors
                    .into_iter()
                    .find(|constructor| constructor.tag() == tag)
            })
            .is_some_and(|constructor| self.is_useful_constructor(matrix, query, &constructor))
    }

    fn is_useful_constructor(
        &self,
        matrix: &PatternMatrix,
        query: Query,
        constructor: &Constructor,
    ) -> bool {
        let arguments = constructor
            .arguments_of(query.first_pattern())
            .expect("a query's first pattern fits each constructor it is searched under");
        let expanded = query.expanded(constructor, &arguments);
        self.is_useful(&matrix.specialize(constructor), expanded.query())
    }

    fn is_useful_literal(&self, matrix: &PatternMatrix, query: Query, literal: &Literal) -> bool {
        self.is_useful(&matrix.specialize_literal(literal), query.rest())
    }
}

#[cfg(test)]
mod tests {
    use crate::types::coverage::cover_pattern::CoverPattern;
    use crate::types::coverage::search_fixture::SearchFixture;

    #[test]
    fn columns_of_one_type_are_searched_apart() {
        let fixture = SearchFixture::new();
        let exhaustive = fixture.is_useful(
            SearchFixture::twin_arms(),
            CoverPattern::wild(),
            SearchFixture::twin_type(),
        );
        assert!(!exhaustive);
    }

    #[test]
    fn a_nested_recursive_enum_keeps_its_wildcard_rows() {
        let fixture = SearchFixture::new();
        let exhaustive = fixture.is_useful(
            SearchFixture::chain_arms(),
            CoverPattern::wild(),
            SearchFixture::chain_type(),
        );
        assert!(!exhaustive);
    }

    #[test]
    fn a_missing_case_of_a_recursive_enum_is_useful() {
        let fixture = SearchFixture::new();
        let mut arms = SearchFixture::chain_arms();
        arms.remove(2);
        assert!(fixture.is_useful(arms, CoverPattern::wild(), SearchFixture::chain_type()));
    }

    #[test]
    fn a_missing_case_beside_a_column_of_the_same_type_is_useful() {
        let fixture = SearchFixture::new();
        let mut arms = SearchFixture::twin_arms();
        arms.remove(3);
        assert!(fixture.is_useful(arms, CoverPattern::wild(), SearchFixture::twin_type()));
    }

    #[test]
    fn an_arm_that_earlier_arms_cover_together_is_not_useful() {
        let fixture = SearchFixture::new();
        let wild = CoverPattern::wild;
        let covered =
            SearchFixture::twin(SearchFixture::side(1), wild(), SearchFixture::flag(true));
        assert!(!fixture.is_useful(
            SearchFixture::twin_arms(),
            covered,
            SearchFixture::twin_type()
        ));
    }

    #[test]
    fn an_arm_with_a_case_no_earlier_arm_covers_is_useful() {
        let fixture = SearchFixture::new();
        let wild = CoverPattern::wild;
        let arms = vec![SearchFixture::twin(SearchFixture::side(0), wild(), wild())];
        let new_case = SearchFixture::twin(SearchFixture::side(1), wild(), wild());
        assert!(fixture.is_useful(arms, new_case, SearchFixture::twin_type()));
    }
}