geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use crate::plan::FunctionType;
use ecow::EcoString;
use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostSpecializationError {
    package: EcoString,
    module: EcoString,
    function: EcoString,
    signature: FunctionType,
    reason: HostSpecializationErrorReason,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HostSpecializationErrorReason {
    UndeterminedReturnStorage,
    UninhabitedCallbackArguments { callback: FunctionType },
}

impl HostSpecializationError {
    pub(in crate::plan::execution) fn undetermined_return_storage(
        package: EcoString,
        module: EcoString,
        function: EcoString,
        signature: FunctionType,
    ) -> Self {
        Self {
            package,
            module,
            function,
            signature,
            reason: HostSpecializationErrorReason::UndeterminedReturnStorage,
        }
    }

    pub(in crate::plan::execution) fn uninhabited_callback_arguments(
        package: EcoString,
        module: EcoString,
        function: EcoString,
        signature: FunctionType,
        callback: FunctionType,
    ) -> Self {
        Self {
            package,
            module,
            function,
            signature,
            reason: HostSpecializationErrorReason::UninhabitedCallbackArguments { callback },
        }
    }

    pub fn package(&self) -> &EcoString {
        &self.package
    }

    pub fn module(&self) -> &EcoString {
        &self.module
    }

    pub fn function(&self) -> &EcoString {
        &self.function
    }

    pub fn signature(&self) -> &FunctionType {
        &self.signature
    }

    pub fn reason(&self) -> &HostSpecializationErrorReason {
        &self.reason
    }
}

impl fmt::Display for HostSpecializationError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.reason {
            HostSpecializationErrorReason::UndeterminedReturnStorage => write!(
                formatter,
                "host function `{}::{}.{}` has an executable specialization `{:?}` whose successful return storage cannot be determined",
                self.package, self.module, self.function, self.signature,
            ),
            HostSpecializationErrorReason::UninhabitedCallbackArguments { callback } => write!(
                formatter,
                "host function `{}::{}.{}` has an executable specialization `{:?}` that exposes callback `{:?}` with uninhabited arguments",
                self.package, self.module, self.function, self.signature, callback,
            ),
        }
    }
}

impl std::error::Error for HostSpecializationError {}

#[cfg(test)]
mod tests {
    use super::{HostSpecializationError, HostSpecializationErrorReason};
    use crate::{FunctionType, ValueType};

    #[test]
    fn exposes_the_undetermined_return_storage_specialization() {
        let signature = FunctionType::new(
            Vec::new(),
            ValueType::Parameter(crate::plan::TypeParameterId(0)),
        );
        let error = HostSpecializationError::undetermined_return_storage(
            "host_support".into(),
            "host/generic".into(),
            "produce".into(),
            signature.clone(),
        );

        assert_eq!(error.package(), "host_support");
        assert_eq!(error.module(), "host/generic");
        assert_eq!(error.function(), "produce");
        assert_eq!(error.signature(), &signature);
        assert_eq!(
            error.reason(),
            &HostSpecializationErrorReason::UndeterminedReturnStorage,
        );
        assert_eq!(
            error.to_string(),
            "host function `host_support::host/generic.produce` has an executable specialization `FunctionType { arguments: [], return_: Parameter(TypeParameterId(0)) }` whose successful return storage cannot be determined",
        );
        assert_eq!(error.clone(), error);
    }

    #[test]
    fn exposes_the_uninhabited_callback_specialization() {
        let callback = FunctionType::new(
            vec![ValueType::Parameter(crate::plan::TypeParameterId(0))],
            ValueType::Int,
        );
        let signature = FunctionType::new(
            vec![ValueType::Function(Box::new(callback.clone()))],
            ValueType::Int,
        );
        let error = HostSpecializationError::uninhabited_callback_arguments(
            "host_support".into(),
            "host/function".into(),
            "apply".into(),
            signature.clone(),
            callback.clone(),
        );

        assert_eq!(error.package(), "host_support");
        assert_eq!(error.module(), "host/function");
        assert_eq!(error.function(), "apply");
        assert_eq!(error.signature(), &signature);
        assert_eq!(
            error.reason(),
            &HostSpecializationErrorReason::UninhabitedCallbackArguments {
                callback: callback.clone(),
            },
        );
        assert_eq!(
            error.to_string(),
            "host function `host_support::host/function.apply` has an executable specialization `FunctionType { arguments: [Function(FunctionType { arguments: [Parameter(TypeParameterId(0))], return_: Int })], return_: Int }` that exposes callback `FunctionType { arguments: [Parameter(TypeParameterId(0))], return_: Int }` with uninhabited arguments",
        );
        assert_eq!(error.clone(), error);
    }
}