knave 0.12.2

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

use rustc_hash::FxHashMap;

use super::selection::{ImplementorMatch, PatternMatch};
use super::traits::Implementor;
use super::ty::Type;
use crate::core::BindingId;

pub struct AssociatedType {
    pub trait_binding: BindingId,
    pub parameter: Rc<str>,
    pub rust_name: Rc<str>,
}

struct ProjectionRule {
    implementor: Implementor,
    value: Type,
}

impl ProjectionRule {
    fn value_for(&self, owner: &Type) -> Option<Type> {
        match ImplementorMatch::match_pattern(
            &self.implementor,
            &self.implementor.scheme.body,
            owner,
        ) {
            PatternMatch::Matched(bindings) => Some(self.value.substitute(&bindings)),
            PatternMatch::Undecided | PatternMatch::Missing => None,
        }
    }

    fn admits(&self, candidate: &Type) -> bool {
        !matches!(
            ImplementorMatch::match_pattern(&self.implementor, &self.value, candidate),
            PatternMatch::Missing
        )
    }
}

#[derive(Default)]
pub struct ProjectionTable {
    associated: FxHashMap<BindingId, AssociatedType>,
    rules: FxHashMap<BindingId, Vec<ProjectionRule>>,
}

impl ProjectionTable {
    pub fn declare(&mut self, projection: BindingId, associated: AssociatedType) {
        self.associated.insert(projection, associated);
    }

    pub fn add_rule(&mut self, projection: BindingId, implementor: &Implementor, value: Type) {
        let rule = ProjectionRule {
            implementor: Implementor {
                scheme: implementor.scheme.clone(),
                argument: implementor.argument.clone(),
            },
            value,
        };
        self.rules.entry(projection).or_default().push(rule);
    }

    pub fn associated(&self, projection: BindingId) -> Option<&AssociatedType> {
        self.associated.get(&projection)
    }

    pub fn is_projection(&self, binding: BindingId) -> bool {
        self.associated.contains_key(&binding)
    }

    pub fn declared_by(&self, trait_binding: BindingId) -> Vec<BindingId> {
        let mut declared: Vec<BindingId> = self
            .associated
            .iter()
            .filter(|(_, associated)| associated.trait_binding == trait_binding)
            .map(|(projection, _)| *projection)
            .collect();
        declared.sort();
        declared
    }

    pub fn normalize(&self, projected: &Type) -> Option<Type> {
        let (projection, owner) = Self::projection_parts(projected)?;
        self.normalize_projection(projection, owner)
    }

    pub fn normalize_projection(&self, projection: BindingId, owner: &Type) -> Option<Type> {
        self.rules
            .get(&projection)?
            .iter()
            .find_map(|rule| rule.value_for(owner))
    }

    pub fn normalize_deep(&self, shown: &Type) -> Type {
        let normalized = match shown {
            Type::Named(binding, arguments) => Type::Named(*binding, self.normalize_all(arguments)),
            Type::Function(kind, parameters, result) => Type::Function(
                *kind,
                self.normalize_all(parameters),
                Box::new(self.normalize_deep(result)),
            ),
            Type::Ref(lifetime, inner) => {
                Type::Ref(*lifetime, Box::new(self.normalize_deep(inner)))
            }
            Type::Mut(lifetime, inner) => {
                Type::Mut(*lifetime, Box::new(self.normalize_deep(inner)))
            }
            other => other.clone(),
        };
        self.normalize(&normalized)
            .map_or(normalized, |value| self.normalize_deep(&value))
    }

    fn normalize_all(&self, shown: &[Type]) -> Vec<Type> {
        shown.iter().map(|part| self.normalize_deep(part)).collect()
    }

    pub fn awaits_owner(&self, projected: &Type, candidate: &Type) -> bool {
        let Some((projection, owner)) = Self::projection_parts(projected) else {
            return false;
        };
        owner.has_vars()
            && self
                .rules
                .get(&projection)
                .is_some_and(|rules| rules.iter().any(|rule| rule.admits(candidate)))
    }

    fn projection_parts(projected: &Type) -> Option<(BindingId, &Type)> {
        match projected {
            Type::Named(projection, arguments) => match arguments.as_slice() {
                [owner] => Some((*projection, owner)),
                _ => None,
            },
            _ => None,
        }
    }
}