sim_lib_function/
callable.rs1use std::sync::Arc;
6
7use sim_lib_dispatch::MethodBody;
8
9use crate::{FunctionBodyPolicy, FunctionInstance};
10
11pub 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}