crisp-errors 1.6.1

Fallibility analysis and CrispError enum synthesis (spec §9)
Documentation
use crate::result::{CrispErrorEnum, ErrorResult, ErrorSig};
use crisp_typeck::{InferredSig, format_ty};

pub fn format_error_sig(sig: &ErrorSig, typed: Option<&InferredSig>) -> String {
    let ret = typed
        .map(|t| format_ty(&t.ret))
        .unwrap_or_else(|| "()".into());
    if !sig.fallible {
        if let Some(ts) = typed {
            let params = ts
                .params
                .iter()
                .map(|(n, t)| format!("{n}: {}", format_ty(t)))
                .collect::<Vec<_>>()
                .join(", ");
            return format!("{}({params}) -> {ret}", sig.name);
        }
        return format!("{}() -> {ret}", sig.name);
    }

    let params = typed
        .map(|t| {
            t.params
                .iter()
                .map(|(n, ty)| format!("{n}: {}", format_ty(ty)))
                .collect::<Vec<_>>()
                .join(", ")
        })
        .unwrap_or_default();

    let err_set = sig.errors.iter().cloned().collect::<Vec<_>>().join(" | ");

    if params.is_empty() {
        format!("{}() -> {ret} ! {err_set}", sig.name)
    } else {
        format!("{}({params}) -> {ret} ! {err_set}", sig.name)
    }
}

pub fn format_crisp_error_enum(en: &CrispErrorEnum) -> String {
    if en.variants.is_empty() {
        return "// CrispError: (no fallible functions)".into();
    }
    let mut lines = vec![
        "#[derive(Debug)]".to_string(),
        "enum CrispError {".to_string(),
    ];
    for v in &en.variants {
        if v.name == "Thrown" {
            lines.push("    Thrown(String),".into());
        } else {
            lines.push(format!("    {}({}),", v.name, v.payload_type));
        }
    }
    lines.push("}".into());
    lines.join("\n")
}

pub fn format_errors_crate(result: &ErrorResult, typed: &crisp_typeck::TypedCrate) -> String {
    let mut lines: Vec<String> = result
        .signatures
        .values()
        .map(|sig| {
            let key = format!("{}::{}", sig.module, sig.name);
            format_error_sig(sig, typed.signatures.get(&key))
        })
        .collect();
    lines.sort();
    let mut out = lines.join("\n");
    if !result.crisp_error.variants.is_empty() {
        out.push_str("\n\n");
        out.push_str(&format_crisp_error_enum(&result.crisp_error));
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ErrorPass;
    use crisp_typeck::TypeChecker;
    use std::path::PathBuf;

    #[test]
    fn format_fallible_read_config() {
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/fallible");
        let typed = TypeChecker::check_crate(&root).unwrap();
        let errors = ErrorPass::analyze_crate(&root).unwrap();
        let out = format_errors_crate(&errors, &typed);
        assert!(out.contains("read_config"));
        assert!(out.contains("IoError"));
        assert!(out.contains("enum CrispError"));
    }
}