geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use crate::plan::execution::explain::FunctionLabel;
use crate::plan::execution::explain::{Explain, ExplainContext};
use crate::plan::execution::function::NeverFunctionId;
use crate::plan::execution::graph::{NeverFunctionLocal, ParamLocal};

pub(crate) enum NeverCallTarget {
    Direct(NeverFunctionId),
    Value(NeverFunctionLocal),
}

pub(crate) struct NeverCall {
    function: NeverCallTarget,
    args: Box<[ParamLocal]>,
    site: crate::plan::HostCallSite,
}

impl NeverCall {
    pub(in crate::plan::execution) fn new(
        function: NeverCallTarget,
        args: Box<[ParamLocal]>,
        site: crate::plan::HostCallSite,
    ) -> Self {
        Self {
            function,
            args,
            site,
        }
    }

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

    pub(crate) fn args(&self) -> &[ParamLocal] {
        &self.args
    }

    pub(crate) fn site(&self) -> &crate::plan::HostCallSite {
        &self.site
    }
}

impl Explain for NeverCall {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        context.push_str("never_call ");
        match self.function() {
            NeverCallTarget::Direct(function) => {
                FunctionLabel::new("never", function.0).write(context.output());
            }
            NeverCallTarget::Value(function) => context.write(function),
        }
        context.push_str(" args=");
        context.write_list(self.args(), |context, argument| context.write(argument));
    }
}

#[cfg(test)]
mod explain_tests {
    use super::super::Terminator;
    use super::NeverCall;
    use crate::plan::execution::explain;
    use crate::plan::execution::function::IntFunctionId;

    #[test]
    fn writes_direct_never_call() {
        let source = r#"
fn stop(value: Int) -> value { panic }

pub fn main() -> Int {
  let _ = stop(1)
  1
}
"#;
        let expected = "never_call never#0 args=[%int#0]";

        assert_explanation(source, expected);
    }

    #[test]
    fn writes_function_value_never_call() {
        let source = r#"
fn stop(value: Int) -> value { panic }

pub fn main() -> Int {
  let function = stop
  let _ = function(1)
  1
}
"#;
        let expected = "never_call %function.never#0 args=[%int#0]";

        assert_explanation(source, expected);
    }

    #[test]
    #[should_panic(expected = "source should lower one Never call")]
    fn never_call_shape_guard_is_visible() {
        explain::with_execution_plan("pub fn main() { 1 }", |plan| {
            never_call(&terminators(plan));
        });
    }

    #[test]
    #[should_panic(expected = "source should lower one Never call")]
    fn never_call_uniqueness_guard_is_visible() {
        let source = r#"
fn stop() -> value { panic }

pub fn main() -> Int {
  let _ = stop()
  1
}
"#;
        explain::with_execution_plan(source, |plan| {
            let call = never_call(&terminators(plan));
            never_call_from_nodes(&[call, call]);
        });
    }

    fn terminators(
        plan: &crate::plan::execution::ExecutionPlan,
    ) -> Vec<&crate::plan::execution::graph::Terminator> {
        plan.int_function(IntFunctionId(0))
            .body()
            .block_graph()
            .blocks()
            .iter()
            .map(|block| block.terminator())
            .collect()
    }

    fn never_call<'a>(terminators: &[&'a Terminator]) -> &'a NeverCall {
        let calls = terminators
            .iter()
            .copied()
            .filter_map(|terminator| match terminator {
                Terminator::NeverCall(call) => Some(call),
                _ => None,
            })
            .collect::<Vec<_>>();
        never_call_from_nodes(&calls)
    }

    fn never_call_from_nodes<'a>(calls: &[&'a NeverCall]) -> &'a NeverCall {
        let mut calls = calls.iter().copied();
        let Some(call) = calls.next() else {
            panic!("source should lower one Never call");
        };
        if calls.next().is_some() {
            panic!("source should lower one Never call");
        }
        call
    }

    fn assert_explanation(source: &str, expected: &str) {
        explain::assert_rendered(source, expected, |plan, output| {
            let call = never_call(&terminators(plan));
            let mut context = explain::ExplainContext::new(plan, output);
            context.write(call);
        });
    }
}