runmat-builtins 0.6.2

RunMat built-in functions and standard library components
Documentation
use super::{BuiltinBindingAvailability, BuiltinCatalogEntry, BuiltinContractMaturity};
use crate::BuiltinAsyncBehavior;
use runmat_types::EffectKind;
use std::collections::{BTreeMap, BTreeSet};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltinCatalogValidationError {
    pub identity: Option<&'static str>,
    pub message: String,
}

pub fn validate_builtin_catalog(
    entries: &[&'static BuiltinCatalogEntry],
) -> Vec<BuiltinCatalogValidationError> {
    let mut errors = Vec::new();
    let mut identities = BTreeSet::new();
    let mut bindings = BTreeMap::new();
    for entry in entries {
        let name = entry.identity.name;
        if name.is_empty() {
            errors.push(error(None, "builtin identity must not be empty"));
            continue;
        }
        if !identities.insert(name) {
            errors.push(error(Some(name), "duplicate builtin catalog identity"));
        }
        if entry.category.is_empty() {
            errors.push(error(Some(name), "category must not be empty"));
        }
        if entry.documentation.summary.is_empty() {
            errors.push(error(Some(name), "documentation summary must not be empty"));
        }
        if entry.bindings.is_empty() {
            errors.push(error(
                Some(name),
                "catalog entry has no runtime binding declaration",
            ));
        }
        if matches!(entry.contract.maturity, BuiltinContractMaturity::Complete)
            && entry.contract.inference_rule.0.is_empty()
        {
            errors.push(error(
                Some(name),
                "complete contract has no inference rule identity",
            ));
        }
        let declares_suspension = entry.contract.effects.contains(&EffectKind::MaySuspend);
        if matches!(
            entry.contract.async_behavior,
            BuiltinAsyncBehavior::NeverSuspends
        ) == declares_suspension
        {
            errors.push(error(
                Some(name),
                "async behavior and MaySuspend effect disagree",
            ));
        }
        if !entry
            .contract
            .effects
            .windows(2)
            .all(|pair| pair[0] < pair[1])
        {
            errors.push(error(
                Some(name),
                "effect declarations must be sorted and unique",
            ));
        }
        if !entry
            .contract
            .capabilities
            .windows(2)
            .all(|pair| pair[0] < pair[1])
        {
            errors.push(error(
                Some(name),
                "capability declarations must be sorted and unique",
            ));
        }
        for binding in entry.bindings {
            if binding.identity.builtin != entry.identity {
                errors.push(error(
                    Some(name),
                    "binding declaration refers to a different builtin identity",
                ));
            }
            if binding.identity.variant.is_empty() {
                errors.push(error(Some(name), "binding variant must not be empty"));
            }
            if bindings.insert(binding.identity, name).is_some() {
                errors.push(error(Some(name), "duplicate builtin binding identity"));
            }
            if matches!(binding.availability, BuiltinBindingAvailability::Required)
                && matches!(
                    entry.link.reachability,
                    super::BuiltinReachability::Feature(_)
                )
            {
                errors.push(error(
                    Some(name),
                    "feature-gated entry declares an unconditional required binding",
                ));
            }
        }
    }
    errors
}

fn error(
    identity: Option<&'static str>,
    message: impl Into<String>,
) -> BuiltinCatalogValidationError {
    BuiltinCatalogValidationError {
        identity,
        message: message.into(),
    }
}