geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use crate::plan::{CustomConstructor, Expr, ValueShape};
use crate::planner::context::PlanContext;
use crate::planner::error::{InvalidModuleReferenceReason, InvalidTypedAstReason, PlanError};
use ecow::EcoString;
use gleam_core::type_::Type;
use std::sync::Arc;

pub(super) struct ResolvedRecordConstructor {
    module: EcoString,
    name: EcoString,
    constructor: CustomConstructor,
}

impl ResolvedRecordConstructor {
    pub(super) fn direct(
        module: EcoString,
        name: EcoString,
        constructor: CustomConstructor,
    ) -> Self {
        Self {
            module,
            name,
            constructor,
        }
    }

    pub(super) fn selected(
        context: &PlanContext<'_>,
        module: EcoString,
        name: EcoString,
        constructor_name: EcoString,
        type_: Arc<Type>,
        variant_index: usize,
        arity: usize,
    ) -> Result<Self, PlanError> {
        let _linked_module = context.resolve_module_reference(&module, &name)?;
        if constructor_name != name {
            return Err(PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ModuleReference {
                    module,
                    name,
                    reason: InvalidModuleReferenceReason::RecordConstructorName {
                        actual: constructor_name,
                    },
                },
            });
        }
        let constructor = context.module_custom_constructor(
            type_.as_ref(),
            constructor_name,
            &module,
            variant_index,
            arity,
        )?;
        Ok(Self {
            module,
            name,
            constructor,
        })
    }

    pub(super) fn into_constructor(self) -> CustomConstructor {
        self.constructor
    }

    pub(super) fn plan_reference(self, shape: ValueShape) -> Result<Expr, PlanError> {
        crate::plan::module::custom_constructor_expr(self.constructor)
            .with_shape(shape)
            .ok_or_else(|| PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ModuleReference {
                    module: self.module,
                    name: self.name,
                    reason: InvalidModuleReferenceReason::RecordConstructorResultShape,
                },
            })
    }
}

#[cfg(test)]
mod tests {
    use super::ResolvedRecordConstructor;
    use crate::plan::{
        CustomConstructor, CustomConstructorField, CustomType, CustomTypeName, ValueShape,
        ValueType,
    };
    use crate::planner::context::{AnonymousFunctions, PlanContext};
    use crate::planner::{InvalidModuleReferenceReason, InvalidTypedAstReason, PlanError};
    use ecow::EcoString;
    use std::collections::HashMap;

    #[test]
    fn selected_record_constructor_rejects_mismatched_name() {
        let module = EcoString::from("main");
        let functions = HashMap::new();
        let mut anonymous = AnonymousFunctions::default();
        let context = PlanContext::new(&module, &functions, &mut anonymous);

        assert_eq!(
            ResolvedRecordConstructor::selected(
                &context,
                module.clone(),
                "Expected".into(),
                "Actual".into(),
                gleam_core::type_::int(),
                0,
                0,
            )
            .map(ResolvedRecordConstructor::into_constructor),
            Err(PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ModuleReference {
                    module: "main".into(),
                    name: "Expected".into(),
                    reason: InvalidModuleReferenceReason::RecordConstructorName {
                        actual: "Actual".into(),
                    },
                },
            }),
        );
    }

    #[test]
    fn record_constructor_reference_rejects_conflicting_result_shape() {
        let constructor = CustomConstructor::new(
            CustomType::new(
                CustomTypeName::new("geam".into(), "main".into(), "Boxed".into()),
                Vec::new(),
            ),
            "Boxed".into(),
            0,
            vec![CustomConstructorField::new(None, ValueType::Int)],
        );

        assert_eq!(
            ResolvedRecordConstructor::direct("main".into(), "Boxed".into(), constructor)
                .plan_reference(ValueShape::Int),
            Err(PlanError::InvalidTypedAst {
                reason: InvalidTypedAstReason::ModuleReference {
                    module: "main".into(),
                    name: "Boxed".into(),
                    reason: InvalidModuleReferenceReason::RecordConstructorResultShape,
                },
            }),
        );
    }
}