knave 0.12.13

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::functions::{FunctionInfo, FunctionTable};
use super::problems::{ChangeSite, OwnershipProblems};
use super::tail_calls::TailCalls;
use super::whole_value_changes::WholeValueChanges;
use crate::core::BindingId;
use crate::diagnostic::{Diagnostic, Diagnostics};
use crate::types::{
    CopyRules, Type, TypedArgument, TypedExpr, TypedExprKind, TypedLeaf, TypedProgram,
};

pub struct PlaceArguments<'a> {
    program: &'a TypedProgram,
    functions: &'a FunctionTable,
    function_params: &'a FxHashMap<BindingId, Vec<Type>>,
    copy: &'a CopyRules<'a>,
}

impl<'a> PlaceArguments<'a> {
    pub fn new(
        program: &'a TypedProgram,
        functions: &'a FunctionTable,
        function_params: &'a FxHashMap<BindingId, Vec<Type>>,
        copy: &'a CopyRules<'a>,
    ) -> Self {
        Self {
            program,
            functions,
            function_params,
            copy,
        }
    }

    pub fn check(&self) -> Diagnostics {
        let mut diagnostics = Diagnostics::default();
        for definition in self
            .program
            .modules
            .iter()
            .flat_map(|module| &module.definitions)
        {
            let tail_calls = match &definition.value.kind {
                TypedExprKind::Fn(lambda) => TailCalls::of(Some(definition.binding), lambda)
                    .arguments()
                    .to_vec(),
                _ => Vec::new(),
            };
            self.visit(&definition.value, &tail_calls, &mut diagnostics);
        }
        diagnostics
    }

    fn visit(
        &self,
        expr: &TypedExpr,
        tail_calls: &[&[TypedArgument]],
        diagnostics: &mut Diagnostics,
    ) {
        if let TypedExprKind::Call(callee, arguments) = &expr.kind
            && !Self::is_tail_call(arguments, tail_calls)
        {
            self.call(callee, arguments, diagnostics);
        }
        for child in expr.children() {
            self.visit(child, tail_calls, diagnostics);
        }
    }

    fn is_tail_call(arguments: &[TypedArgument], tail_calls: &[&[TypedArgument]]) -> bool {
        tail_calls
            .iter()
            .any(|tail_call| std::ptr::eq(tail_call.as_ptr(), arguments.as_ptr()))
    }

    fn call(&self, callee: &TypedExpr, arguments: &[TypedArgument], diagnostics: &mut Diagnostics) {
        let TypedExprKind::Leaf(TypedLeaf::Global(function)) = callee.kind else {
            return;
        };
        let (Some(info), Some(params)) = (
            self.functions.definition(function),
            self.function_params.get(&function),
        ) else {
            return;
        };
        for ((slot, param), argument) in info.params.iter().zip(params).zip(arguments) {
            if slot.inferable
                && matches!(param, Type::Mut(..))
                && !Self::can_change(&argument.value)
            {
                let change = self.change_site(function, info, slot.binding);
                diagnostics.push(self.copy_argument(&argument.value, change));
            }
        }
    }

    fn can_change(value: &TypedExpr) -> bool {
        value.is_place() || matches!(value.kind, TypedExprKind::Borrow(..))
    }

    fn change_site(
        &self,
        function: BindingId,
        info: &FunctionInfo,
        param: BindingId,
    ) -> Option<ChangeSite> {
        WholeValueChanges::site(&info.lambda, param).map(|span| ChangeSite {
            function: self.program.bindings.get(function).name().into(),
            span,
        })
    }

    fn copy_argument(&self, value: &TypedExpr, change: Option<ChangeSite>) -> Diagnostic {
        if matches!(value.ty, Type::Ref(..)) {
            return OwnershipProblems::inferred_mut_of_shared_borrow(value.span, change);
        }
        let TypedExprKind::Leaf(TypedLeaf::Global(definition)) = value.kind else {
            return OwnershipProblems::inferred_mut_of_copy(value.span, change);
        };
        let name = self.program.bindings.get(definition).name();
        if self.copy.is_copy(&value.ty) {
            OwnershipProblems::inferred_mut_of_copy_definition(value.span, change, name)
        } else {
            OwnershipProblems::inferred_mut_of_cloned_definition(value.span, change, name)
        }
    }
}