knave 0.14.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 super::problems::TypeProblems;
use crate::core::{FieldLabel, Pattern, PatternArguments, PatternKind};
use crate::diagnostic::{Diagnostic, SimilarName};
use crate::source::Span;

pub enum PatternFields<'a> {
    Named(Vec<&'a str>),
    Positional(usize),
}

impl PatternFields<'_> {
    fn count(&self) -> usize {
        match self {
            Self::Named(names) => names.len(),
            Self::Positional(count) => *count,
        }
    }
}

pub struct PatternPlacement<'a> {
    written: &'a PatternArguments,
    skipped_field: Pattern,
    span: Span,
}

impl<'a> PatternPlacement<'a> {
    pub fn new(written: &'a PatternArguments, span: Span) -> Self {
        Self {
            written,
            skipped_field: Pattern::new(PatternKind::Wildcard, span),
            span,
        }
    }

    pub fn place(
        &self,
        owner: &str,
        fields: &PatternFields<'_>,
    ) -> Result<Vec<&Pattern>, Diagnostic> {
        self.reject_mixed_forms(owner, fields)?;
        match (self.written.labeled().next(), fields) {
            (None, _) => self.by_position(owner, fields.count()),
            (Some(_), PatternFields::Named(names)) => self.by_label(owner, names),
            (Some((label, _)), PatternFields::Positional(count)) => Err(
                TypeProblems::label_on_positional_payload(owner, label, *count),
            ),
        }
    }

    fn reject_mixed_forms(
        &self,
        owner: &str,
        fields: &PatternFields<'_>,
    ) -> Result<(), Diagnostic> {
        let (Some((label, _)), Some(positional)) = (
            self.written.labeled().next(),
            self.written.positional().next(),
        ) else {
            return Ok(());
        };
        Err(match fields {
            PatternFields::Named(_) => {
                TypeProblems::mixed_named_pattern(owner, label.span, positional.span)
            }
            PatternFields::Positional(count) => {
                TypeProblems::mixed_positional_pattern(owner, label.span, positional.span, *count)
            }
        })
    }

    fn by_position(&self, owner: &str, expected: usize) -> Result<Vec<&Pattern>, Diagnostic> {
        let found = self.written.positional().count();
        if expected != found {
            return Err(TypeProblems::pattern_count(
                owner, expected, found, self.span,
            ));
        }
        Ok(self.written.positional().collect())
    }

    fn by_label(&self, owner: &str, names: &[&str]) -> Result<Vec<&Pattern>, Diagnostic> {
        let mut slots: Vec<Option<(&FieldLabel, &Pattern)>> = vec![None; names.len()];
        for (label, pattern) in self.written.labeled() {
            let Some(index) = names.iter().position(|name| *name == &*label.name) else {
                return Err(Self::unknown_field(owner, label, names));
            };
            if let Some((first, _)) = slots[index] {
                return Err(TypeProblems::repeated_pattern_field(
                    owner, label, first.span,
                ));
            }
            let Some(pattern) = pattern else {
                return Err(TypeProblems::keyword_without_pattern(label));
            };
            slots[index] = Some((label, pattern));
        }
        Ok(slots
            .into_iter()
            .map(|slot| slot.map_or(&self.skipped_field, |(_, pattern)| pattern))
            .collect())
    }

    fn unknown_field(owner: &str, label: &FieldLabel, names: &[&str]) -> Diagnostic {
        let names: Vec<Rc<str>> = names.iter().map(|name| Rc::from(*name)).collect();
        TypeProblems::unknown_pattern_field(owner, label, SimilarName::closest(&label.name, &names))
    }
}