Skip to main content

sim_lib_function/
callable.rs

1// conformance: function instances compose with ordinary callable dispatch.
2
3//! Optional composition between ordinary function instances and dispatch.
4
5use std::sync::Arc;
6
7use sim_lib_dispatch::MethodBody;
8
9use crate::{FunctionBodyPolicy, FunctionInstance};
10
11/// Projects a function instance into a dispatch method body.
12///
13/// This adapter is deliberately opt-in: [`FunctionInstance`] remains a kernel
14/// callable in its own right, and an ordinary call never constructs or consults
15/// a generic function. Dispatch supplies only method selection; after selection,
16/// the same neutral call boundary invokes the same guest policy.
17pub fn dispatch_method_body<B>(function: FunctionInstance<B>) -> MethodBody
18where
19    B: FunctionBodyPolicy,
20{
21    Arc::new(move |cx, arguments| function.invoke_values(cx, arguments.to_vec()))
22}
23
24#[cfg(test)]
25mod tests {
26    use std::sync::{
27        Arc, Mutex,
28        atomic::{AtomicUsize, Ordering},
29    };
30
31    use sim_kernel::{
32        Args, Callable, ClassRef, Cx, Expr, Result, Shape, Symbol, Value,
33        shape::{MatchScore, ShapeDoc, ShapeMatch},
34        testing::bare_cx,
35    };
36    use sim_lib_dispatch::{DispatchMethod, GenericFunction, MethodRole};
37
38    use super::*;
39    use crate::{ArgumentInput, ArgumentOrigin, BoundCall, FunctionPlan};
40
41    type RecordedArguments = Vec<(ArgumentOrigin, Value)>;
42    type RecordedCalls = Arc<Mutex<Vec<RecordedArguments>>>;
43
44    #[derive(Clone)]
45    struct RecordingBody {
46        calls: RecordedCalls,
47    }
48
49    impl FunctionBodyPolicy for RecordingBody {
50        fn invoke(
51            &self,
52            _cx: &mut Cx,
53            _plan: &FunctionPlan,
54            _captures: &[crate::CapturedBinding],
55            call: BoundCall,
56        ) -> Result<Value> {
57            let received = call
58                .arguments()
59                .iter()
60                .map(|argument| match argument.input() {
61                    ArgumentInput::Positional(value) => (argument.origin().clone(), value.clone()),
62                    _ => unreachable!("evaluated call plans contain positional inputs"),
63                })
64                .collect::<Vec<_>>();
65            let result = received[0].1.clone();
66            self.calls.lock().unwrap().push(received);
67            Ok(result)
68        }
69    }
70
71    struct InstrumentedShape {
72        selections: Arc<AtomicUsize>,
73    }
74
75    impl Shape for InstrumentedShape {
76        fn check_value(&self, _cx: &mut Cx, _value: Value) -> Result<ShapeMatch> {
77            self.selections.fetch_add(1, Ordering::SeqCst);
78            Ok(ShapeMatch::accept(MatchScore::exact(0)))
79        }
80
81        fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> Result<ShapeMatch> {
82            Ok(ShapeMatch::accept(MatchScore::exact(0)))
83        }
84
85        fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
86            Ok(ShapeDoc::new("instrumented"))
87        }
88    }
89
90    fn instance(cx: &mut Cx, calls: RecordedCalls) -> FunctionInstance<RecordingBody> {
91        let class: ClassRef = cx.factory().symbol(Symbol::new("guest-function")).unwrap();
92        FunctionInstance::new(
93            FunctionPlan::new(Symbol::new("guest:record"), vec![], vec![], None).unwrap(),
94            RecordingBody { calls },
95            vec![],
96            class,
97            None,
98            None,
99        )
100        .unwrap()
101    }
102
103    #[test]
104    fn direct_and_selected_invocation_deliver_identical_bound_calls() {
105        let mut cx = bare_cx();
106        let calls = Arc::new(Mutex::new(Vec::new()));
107        let selections = Arc::new(AtomicUsize::new(0));
108        let function = instance(&mut cx, calls.clone());
109        let argument = cx.factory().symbol(Symbol::new("argument")).unwrap();
110
111        function
112            .call(&mut cx, Args::new(vec![argument.clone()]))
113            .unwrap();
114        assert_eq!(selections.load(Ordering::SeqCst), 0);
115
116        let mut generic = GenericFunction::new(Symbol::new("guest:generic"));
117        generic
118            .add_method(DispatchMethod::new(
119                Symbol::new("guest:method"),
120                MethodRole::Primary,
121                vec![Arc::new(InstrumentedShape {
122                    selections: selections.clone(),
123                })],
124                dispatch_method_body(function),
125            ))
126            .unwrap();
127        generic.call(&mut cx, &[argument]).unwrap();
128
129        assert_eq!(selections.load(Ordering::SeqCst), 1);
130        let calls = calls.lock().unwrap();
131        assert_eq!(calls.len(), 2);
132        assert_eq!(calls[0].len(), calls[1].len());
133        for (direct, selected) in calls[0].iter().zip(&calls[1]) {
134            assert_eq!(direct.0, selected.0);
135            assert_eq!(direct.1, selected.1);
136        }
137        assert_eq!(calls[0][0].0, ArgumentOrigin::KernelPosition(0));
138    }
139}