knave 0.10.2

A small Lisp for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use rustc_hash::{FxHashMap, FxHashSet};

use super::enums::EnumTable;
use super::size::TypeSizes;
use super::structs::StructTable;
use super::traits::TraitTable;
use super::ty::{Bound, GenericId, GenericParam, Type};
use crate::core::{BindingArena, BindingId, CopyTrait};

#[derive(Clone, Default)]
pub struct InferredCopies {
    parameters: FxHashSet<BindingId>,
}

impl InferredCopies {
    pub fn insert(&mut self, binding: BindingId) {
        self.parameters.insert(binding);
    }

    pub fn extend(&mut self, other: InferredCopies) {
        self.parameters.extend(other.parameters);
    }

    pub fn contains(&self, binding: BindingId) -> bool {
        self.parameters.contains(&binding)
    }

    pub fn is_empty(&self) -> bool {
        self.parameters.is_empty()
    }
}

pub struct CopyRules<'a> {
    traits: &'a TraitTable,
    generics: &'a FxHashMap<GenericId, GenericParam>,
    sizes: TypeSizes<'a>,
    copy_traits: Vec<BindingId>,
}

impl<'a> CopyRules<'a> {
    pub fn new(
        traits: &'a TraitTable,
        generics: &'a FxHashMap<GenericId, GenericParam>,
        structs: &'a StructTable,
        enums: &'a EnumTable,
    ) -> Self {
        Self {
            traits,
            generics,
            sizes: TypeSizes::new(structs, enums),
            copy_traits: traits.with_paths(&[CopyTrait::PATH]),
        }
    }

    pub fn copy_trait_name<'b>(&self, bindings: &'b BindingArena) -> &'b str {
        let binding = self
            .copy_traits
            .first()
            .expect("the prelude defines a copy trait");
        bindings.get(*binding).name()
    }

    pub fn is_copy(&self, value: &Type) -> bool {
        self.copy_traits.iter().any(|copy| {
            self.traits
                .satisfies(&Bound::plain(*copy), value, &|generic, _| {
                    self.generic_is_copy(generic)
                })
        })
    }

    pub fn is_small_copy(&self, value: &Type) -> bool {
        self.is_copy(value) && self.sizes.is_small(value)
    }

    fn generic_is_copy(&self, generic: GenericId) -> bool {
        self.generics.get(&generic).is_some_and(|param| {
            param.bounds.iter().any(|bound| {
                self.copy_traits.contains(&bound.binding)
                    || self.bound_allows_only_copy(bound.binding)
            })
        })
    }

    pub fn bound_allows_only_copy_values(&self, bound: BindingId) -> bool {
        self.bound_allows_only_copy(bound)
            && self.traits.get(bound).is_some_and(|rust_trait| {
                rust_trait
                    .implementors
                    .iter()
                    .all(|implementor| !implementor.scheme.body.is_reference())
            })
    }

    fn bound_allows_only_copy(&self, bound: BindingId) -> bool {
        self.traits.get(bound).is_some_and(|rust_trait| {
            !rust_trait.implementors.is_empty()
                && rust_trait.implementors.iter().all(|implementor| {
                    implementor.scheme.params.is_empty() && self.is_copy(&implementor.scheme.body)
                })
        })
    }
}

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

    use super::CopyRules;
    use crate::core::{BindingArena, BindingInfo, BindingKind, ModuleId, PrimitiveType};
    use crate::types::Type;
    use crate::types::structs::StructTable;
    use crate::types::traits::{Implementor, RustTrait, TraitTable};
    use crate::types::ty::Scheme;

    struct CopyTraitFixture {
        traits: TraitTable,
        structs: StructTable,
        enums: crate::types::EnumTable,
        generics: FxHashMap<crate::types::GenericId, crate::types::GenericParam>,
        bound: crate::core::BindingId,
    }

    impl CopyTraitFixture {
        fn new() -> Self {
            let mut bindings = BindingArena::default();
            let bound = bindings.add(BindingInfo::new(
                "copy".into(),
                BindingKind::Trait(ModuleId::new(0)),
                None,
            ));
            let mut traits = TraitTable::default();
            traits.add(
                bound,
                RustTrait {
                    path: "Copy".into(),
                    implementors: vec![Implementor {
                        scheme: Scheme::monomorphic(Type::Primitive(PrimitiveType::I64)),
                        argument: None,
                    }],
                    help: None,
                    supertraits: Vec::new(),
                    associated: None,
                },
            );
            Self {
                traits,
                structs: StructTable::default(),
                enums: crate::types::EnumTable::default(),
                generics: FxHashMap::default(),
                bound,
            }
        }

        fn rules(&self) -> CopyRules<'_> {
            CopyRules::new(&self.traits, &self.generics, &self.structs, &self.enums)
        }
    }

    #[test]
    fn a_trait_whose_implementors_are_copy_allows_only_copy_values() {
        let fixture = CopyTraitFixture::new();
        assert!(fixture.rules().bound_allows_only_copy_values(fixture.bound));
    }
}