knave-expand 0.13.2

The reading, scoping and expansion stages of knave
Documentation
use super::Expander;
use super::globals::CompileTimeCheck;
use super::problems::ExpandProblems;
use crate::core::{BindingId, BindingKind, Control, Expr, ExprKind, Lambda, Leaf};
use crate::diagnostic::{Diagnostic, Label};
use crate::interpret::InterpretProblems;
use crate::source::Span;

impl Expander<'_> {
    pub(super) fn check_compile_time(&mut self, lambda: &Lambda) -> Result<(), Diagnostic> {
        for expr in lambda.body.expressions() {
            self.check_expr(expr, None)?;
        }
        Ok(())
    }

    fn check_expr(&mut self, expr: &Expr, through: Option<Span>) -> Result<(), Diagnostic> {
        self.check_node(expr, through)?;
        for child in expr.children() {
            self.check_expr(child, through)?;
        }
        Ok(())
    }

    fn check_node(&mut self, expr: &Expr, through: Option<Span>) -> Result<(), Diagnostic> {
        match expr.kind() {
            ExprKind::Leaf(Leaf::Var(binding)) => self.check_global(*binding, expr.span(), through),
            ExprKind::Leaf(Leaf::Rust(_)) => {
                let problem = self
                    .macro_program_only(expr.span())
                    .unwrap_or_else(|| InterpretProblems::program_only(expr.span()));
                Err(Self::routed(problem, through))
            }
            _ => self.check_value_form(expr, through),
        }
    }

    fn macro_program_only(&self, span: Span) -> Option<Diagnostic> {
        let origin = self.tables.macro_origins.get(span)?;
        Some(ExpandProblems::macro_program_only(
            &origin.name,
            span,
            origin.defined,
        ))
    }

    fn check_value_form(&self, expr: &Expr, through: Option<Span>) -> Result<(), Diagnostic> {
        let problem = match expr.kind() {
            ExprKind::Struct(_) => InterpretProblems::runtime_only("structs", expr.span()),
            ExprKind::Assoc(..) => {
                InterpretProblems::runtime_only("collection updates", expr.span())
            }
            ExprKind::MapAt(..) | ExprKind::SetAt(..) => {
                InterpretProblems::runtime_only("map operations", expr.span())
            }
            ExprKind::Try(_) => InterpretProblems::runtime_only("`try` forms", expr.span()),
            ExprKind::Infer(_) => InterpretProblems::runtime_only("`infer` forms", expr.span()),
            ExprKind::Control(Control::Match(..)) => {
                InterpretProblems::match_only_at_runtime(expr.span())
            }
            _ => return Ok(()),
        };
        Err(Self::routed(problem, through))
    }

    fn check_global(
        &mut self,
        binding: BindingId,
        span: Span,
        through: Option<Span>,
    ) -> Result<(), Diagnostic> {
        if !matches!(self.kind(binding), BindingKind::Global(_)) {
            return Ok(());
        }
        let value = self.lowered_global(binding, span)?;
        let name = self.tables.bindings.get(binding).name().to_string();
        if let ExprKind::Leaf(Leaf::Rust(_)) = value.kind() {
            let problem = self
                .macro_program_only(span)
                .unwrap_or_else(|| ExpandProblems::program_only(&name, span, value.span()));
            return Err(Self::routed(problem, through));
        }
        match self.tables.globals.compile_time_check(binding) {
            Some(CompileTimeCheck::InProgress | CompileTimeCheck::Passed) => Ok(()),
            Some(CompileTimeCheck::Failed) => Err(ExpandProblems::broken_definition(&name, span)),
            None => self.check_helper(binding, &value, through.or(Some(span))),
        }
    }

    fn check_helper(
        &mut self,
        binding: BindingId,
        value: &Expr,
        through: Option<Span>,
    ) -> Result<(), Diagnostic> {
        self.tables
            .globals
            .record_compile_time_check(binding, CompileTimeCheck::InProgress);
        let checked = self.check_expr(value, through);
        let outcome = if checked.is_ok() {
            CompileTimeCheck::Passed
        } else {
            CompileTimeCheck::Failed
        };
        self.tables
            .globals
            .record_compile_time_check(binding, outcome);
        checked
    }

    fn routed(problem: Diagnostic, through: Option<Span>) -> Diagnostic {
        match through {
            Some(span) => {
                problem.with_label(Label::new(span, "macro code reaches it through this"))
            }
            None => problem,
        }
    }
}