knave 0.14.16

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

use super::admissible::Admissible;
use super::traits::{RustTrait, TraitTable};
use super::ty::{GenericId, GenericParam, Type};

pub(super) struct AdmissibleTypes<'a> {
    generics: &'a FxHashMap<GenericId, GenericParam>,
    traits: &'a TraitTable,
}

impl<'a> AdmissibleTypes<'a> {
    pub(super) fn new(
        generics: &'a FxHashMap<GenericId, GenericParam>,
        traits: &'a TraitTable,
    ) -> Self {
        Self { generics, traits }
    }

    pub(super) fn of(&self, generic: GenericId) -> Admissible {
        let traits = self.bounding_traits(generic);
        let Some((first, others)) = traits.split_first() else {
            return Admissible::Unbounded;
        };
        let mut admissible: Vec<Type> = first
            .implementors
            .iter()
            .map(|implementor| implementor.scheme.body.clone())
            .collect();
        for rust_trait in others {
            admissible.retain(|candidate| {
                rust_trait
                    .implementors
                    .iter()
                    .any(|implementor| implementor.scheme.body == *candidate)
            });
        }
        if traits.iter().any(|rust_trait| rust_trait.is_open()) {
            Admissible::Open(admissible)
        } else {
            Admissible::Closed(admissible)
        }
    }

    fn bounding_traits(&self, generic: GenericId) -> Vec<&'a RustTrait> {
        self.generics
            .get(&generic)
            .map(|param| {
                param
                    .bounds
                    .iter()
                    .filter_map(|bound| self.traits.get(bound.binding))
                    .collect()
            })
            .unwrap_or_default()
    }
}

#[cfg(test)]
mod tests {
    use rustc_hash::FxHashMap;

    use super::AdmissibleTypes;
    use crate::core::{BindingId, PrimitiveType};
    use crate::types::admissible::Admissible;
    use crate::types::{
        Bound, GenericId, GenericParam, Implementor, RustTrait, Scheme, TraitTable, Type,
    };

    struct AdmissibleFixture {
        generics: FxHashMap<GenericId, GenericParam>,
        traits: TraitTable,
    }

    impl AdmissibleFixture {
        const FIRST_TRAIT: BindingId = BindingId::from_index(0);
        const SECOND_TRAIT: BindingId = BindingId::from_index(1);

        fn new() -> Self {
            Self {
                generics: FxHashMap::default(),
                traits: TraitTable::default(),
            }
        }

        fn bounded_by(mut self, traits: &[BindingId]) -> Self {
            let generic = Self::generic();
            let bounds = traits.iter().copied().map(Bound::plain).collect();
            self.generics.insert(
                generic,
                GenericParam {
                    id: generic,
                    name: "T".into(),
                    bounds,
                },
            );
            self
        }

        fn implemented_by(mut self, rust_trait: BindingId, implementors: Vec<Implementor>) -> Self {
            self.traits.add(
                rust_trait,
                RustTrait {
                    path: "Shown".into(),
                    implementors,
                    help: None,
                    supertraits: Vec::new(),
                    associated: None,
                },
            );
            self
        }

        fn admissible(&self) -> Admissible {
            AdmissibleTypes::new(&self.generics, &self.traits).of(Self::generic())
        }

        fn generic() -> GenericId {
            GenericId::new(0)
        }

        fn concrete(body: PrimitiveType) -> Implementor {
            Self::implementor(Vec::new(), Type::Primitive(body))
        }

        fn generic_implementor() -> Implementor {
            let parameter = GenericId::new(1);
            let param = GenericParam {
                id: parameter,
                name: "U".into(),
                bounds: Vec::new(),
            };
            Self::implementor(vec![param], Type::Generic(parameter))
        }

        fn implementor(params: Vec<GenericParam>, body: Type) -> Implementor {
            Implementor {
                scheme: Scheme {
                    lifetimes: Vec::new(),
                    params,
                    body,
                },
                argument: None,
            }
        }
    }

    #[test]
    fn a_generic_without_a_trait_bound_admits_every_type() {
        let fixture = AdmissibleFixture::new().bounded_by(&[]);
        assert_eq!(fixture.admissible(), Admissible::Unbounded);
    }

    #[test]
    fn one_bound_admits_its_implementors() {
        let fixture = AdmissibleFixture::new()
            .bounded_by(&[AdmissibleFixture::FIRST_TRAIT])
            .implemented_by(
                AdmissibleFixture::FIRST_TRAIT,
                vec![AdmissibleFixture::concrete(PrimitiveType::I64)],
            );
        assert_eq!(
            fixture.admissible(),
            Admissible::Closed(vec![Type::Primitive(PrimitiveType::I64)])
        );
    }

    #[test]
    fn two_bounds_admit_only_the_types_that_implement_both() {
        let fixture = AdmissibleFixture::new()
            .bounded_by(&[
                AdmissibleFixture::FIRST_TRAIT,
                AdmissibleFixture::SECOND_TRAIT,
            ])
            .implemented_by(
                AdmissibleFixture::FIRST_TRAIT,
                vec![
                    AdmissibleFixture::concrete(PrimitiveType::I64),
                    AdmissibleFixture::concrete(PrimitiveType::F64),
                ],
            )
            .implemented_by(
                AdmissibleFixture::SECOND_TRAIT,
                vec![
                    AdmissibleFixture::concrete(PrimitiveType::U8),
                    AdmissibleFixture::concrete(PrimitiveType::I64),
                ],
            );
        assert_eq!(
            fixture.admissible(),
            Admissible::Closed(vec![Type::Primitive(PrimitiveType::I64)])
        );
    }

    #[test]
    fn one_bound_with_a_generic_implementor_leaves_the_types_open() {
        let fixture = AdmissibleFixture::new()
            .bounded_by(&[
                AdmissibleFixture::FIRST_TRAIT,
                AdmissibleFixture::SECOND_TRAIT,
            ])
            .implemented_by(
                AdmissibleFixture::FIRST_TRAIT,
                vec![AdmissibleFixture::concrete(PrimitiveType::I64)],
            )
            .implemented_by(
                AdmissibleFixture::SECOND_TRAIT,
                vec![
                    AdmissibleFixture::concrete(PrimitiveType::I64),
                    AdmissibleFixture::generic_implementor(),
                ],
            );
        assert_eq!(
            fixture.admissible(),
            Admissible::Open(vec![Type::Primitive(PrimitiveType::I64)])
        );
    }
}