geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::{Expr, ExprKind, GenericExpr, TupleExpr};
use crate::plan::ParamLocal;

#[derive(Debug, Clone, PartialEq)]
pub struct CallArg {
    value: Expr,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct CaptureArg {
    local: ParamLocal,
}

#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum CallArgStorage<'a> {
    Stored,
    PotentiallyUninhabited(PotentiallyUninhabitedCallArg<'a>),
}

#[cfg_attr(test, derive(Debug, PartialEq))]
pub(crate) enum PotentiallyUninhabitedCallArg<'a> {
    Generic(&'a GenericExpr),
    Tuple(&'a TupleExpr),
    Custom(&'a super::CustomExpr),
}

impl CallArg {
    pub(crate) fn new(value: Expr) -> Self {
        Self { value }
    }

    pub(crate) fn value(&self) -> &Expr {
        &self.value
    }

    pub(crate) fn storage(&self) -> CallArgStorage<'_> {
        match self.value.kind() {
            ExprKind::Generic(value) => CallArgStorage::PotentiallyUninhabited(
                PotentiallyUninhabitedCallArg::Generic(value),
            ),
            ExprKind::Tuple(value) => {
                CallArgStorage::PotentiallyUninhabited(PotentiallyUninhabitedCallArg::Tuple(value))
            }
            ExprKind::Custom(value) => {
                CallArgStorage::PotentiallyUninhabited(PotentiallyUninhabitedCallArg::Custom(value))
            }
            ExprKind::Int(_)
            | ExprKind::Float(_)
            | ExprKind::String(_)
            | ExprKind::BitArray(_)
            | ExprKind::UtfCodepoint(_)
            | ExprKind::External(_)
            | ExprKind::Bool(_)
            | ExprKind::Nil(_)
            | ExprKind::List(_)
            | ExprKind::Function(_) => CallArgStorage::Stored,
        }
    }

    #[cfg(test)]
    pub(crate) fn parameter_shape(&self) -> crate::plan::ValueShape {
        self.value.shape().clone()
    }
}

impl CaptureArg {
    pub(crate) fn new(local: ParamLocal) -> Self {
        Self { local }
    }

    pub(crate) fn local(&self) -> &ParamLocal {
        &self.local
    }
}

#[cfg(test)]
mod tests {
    use super::{CallArg, CallArgStorage, CaptureArg, PotentiallyUninhabitedCallArg};
    use crate::plan::{
        CustomConstructorRefinement, CustomExpr, CustomLocal, CustomLocalId, CustomTypeName,
        CustomValueShape, Expr, FunctionExpr, FunctionReference, FunctionShape, IntExpr, ListExpr,
        TupleExpr, TypeParameterId, ValueShape, monomorphic_function_instantiation,
    };
    use num_bigint::BigInt;

    #[test]
    fn call_arguments_own_expressions_and_capture_arguments_own_typed_locals() {
        let value = Expr::int(IntExpr::value(BigInt::from(1)));
        let local = crate::plan::ParamLocal::int(crate::plan::IntLocalId(2));

        assert_eq!(CallArg::new(value.clone()).value(), &value);
        assert_eq!(CaptureArg::new(local.clone()).local(), &local);
        assert_eq!(CallArg::new(value).parameter_shape(), ValueShape::Int);
    }

    #[test]
    fn call_argument_storage_distinguishes_stored_values() {
        let list = CallArg::new(Expr::list(
            ListExpr::try_value(
                Vec::new(),
                crate::plan::ValueType::Parameter(TypeParameterId(0)),
            )
            .expect("an empty parameter list has no mismatching element"),
        ));
        let function_shape = FunctionShape::new(vec![ValueShape::Int], ValueShape::Int);
        let function = CallArg::new(Expr::function(FunctionExpr::reference(
            FunctionReference::new(monomorphic_function_instantiation(
                0,
                function_shape.clone(),
            )),
        )));

        assert_eq!(list.storage(), CallArgStorage::Stored);
        assert_eq!(function.storage(), CallArgStorage::Stored);
    }

    #[test]
    fn potentially_uninhabited_arguments_keep_their_typed_source() {
        let parameter = TypeParameterId(0);
        let generic = crate::plan::GenericExpr::panic(
            parameter,
            crate::plan::PanicExpr::panic_at(None, crate::plan::PanicSite::unknown()),
        );
        let argument = CallArg::new(Expr::generic(generic.clone()));

        assert_eq!(
            argument.storage(),
            CallArgStorage::PotentiallyUninhabited(PotentiallyUninhabitedCallArg::Generic(
                &generic,
            )),
        );

        let tuple = TupleExpr::value(
            vec![Expr::generic(generic)],
            vec![crate::plan::ValueType::Parameter(parameter)],
        );
        let argument = CallArg::new(Expr::tuple(tuple.clone()));
        assert_eq!(
            argument.storage(),
            CallArgStorage::PotentiallyUninhabited(PotentiallyUninhabitedCallArg::Tuple(&tuple)),
        );

        let custom = CustomExpr::local_get(
            CustomLocal::from_shape(
                CustomLocalId(0),
                CustomValueShape::new(
                    CustomTypeName::new("geam".into(), "main".into(), "Boxed".into()),
                    vec![ValueShape::Parameter(parameter)],
                    CustomConstructorRefinement::Exact(0),
                ),
            ),
            "boxed".into(),
        );
        let argument = CallArg::new(Expr::custom(custom.clone()));
        assert_eq!(
            argument.storage(),
            CallArgStorage::PotentiallyUninhabited(PotentiallyUninhabitedCallArg::Custom(&custom)),
        );
    }
}