1use std::sync::Arc;
8
9use sim_kernel::{
10 Args, Callable, ClaimPattern, ClaimSink, ClassRef, Cx, Datum, Error, Object, ObjectCompat,
11 ObjectHeader, OpKey, RawArgs, Result, ShapeRef, Value,
12};
13
14#[derive(Clone)]
20pub struct BrowseSignature {
21 callable: Value,
22 args: Option<ShapeRef>,
23 result: Option<ShapeRef>,
24}
25
26impl BrowseSignature {
27 pub fn new(callable: Value, args: Option<ShapeRef>, result: Option<ShapeRef>) -> Result<Self> {
29 if callable.object().as_callable().is_none() {
30 return Err(Error::HostError(
31 "browse signature requires a callable value".to_owned(),
32 ));
33 }
34 for (slot, shape) in [("arguments", args.as_ref()), ("result", result.as_ref())] {
35 if shape.is_some_and(|value| value.object().as_shape().is_none()) {
36 return Err(Error::HostError(format!(
37 "browse signature {slot} metadata must be a Shape value"
38 )));
39 }
40 }
41 Ok(Self {
42 callable,
43 args,
44 result,
45 })
46 }
47
48 fn inner(&self) -> &dyn Callable {
49 self.callable
50 .object()
51 .as_callable()
52 .expect("BrowseSignature construction validated the callable")
53 }
54}
55
56pub fn browse_signature(
58 cx: &mut Cx,
59 callable: Value,
60 args: Option<ShapeRef>,
61 result: Option<ShapeRef>,
62) -> Result<Value> {
63 cx.factory()
64 .opaque(Arc::new(BrowseSignature::new(callable, args, result)?))
65}
66
67impl Object for BrowseSignature {
68 fn header(&self) -> &ObjectHeader {
69 self.callable.object().header()
70 }
71
72 fn op(&self, key: &OpKey) -> Option<&dyn sim_kernel::Op> {
73 self.callable.object().op(key)
74 }
75
76 fn claims(&self, cx: &mut Cx, pattern: &ClaimPattern, sink: &mut dyn ClaimSink) -> Result<()> {
77 self.callable.object().claims(cx, pattern, sink)
78 }
79
80 fn snapshot(&self, cx: &mut Cx) -> Result<Option<Datum>> {
81 self.callable.object().snapshot(cx)
82 }
83
84 fn display(&self, cx: &mut Cx) -> Result<String> {
85 self.callable.object().display(cx)
86 }
87
88 fn as_any(&self) -> &dyn std::any::Any {
89 self
90 }
91}
92
93impl ObjectCompat for BrowseSignature {
94 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
95 self.callable.object().class(cx)
96 }
97
98 fn as_callable(&self) -> Option<&dyn Callable> {
99 Some(self)
100 }
101
102 fn as_expr(&self, cx: &mut Cx) -> Result<sim_kernel::Expr> {
103 self.callable.object().as_expr(cx)
104 }
105
106 fn truth(&self, cx: &mut Cx) -> Result<bool> {
107 self.callable.object().truth(cx)
108 }
109
110 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
111 self.callable.object().as_table(cx)
112 }
113}
114
115impl Callable for BrowseSignature {
116 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
117 self.inner().call(cx, args)
118 }
119
120 fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
121 Ok(self.args.clone())
122 }
123
124 fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
125 Ok(self.result.clone())
126 }
127
128 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
129 self.inner().call_exprs(cx, args)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use std::sync::{
136 Arc,
137 atomic::{AtomicUsize, Ordering},
138 };
139
140 use sim_kernel::{Expr, Object, ObjectCompat, RawArgs, Symbol, testing::bare_cx as cx};
141
142 use super::*;
143 use crate::{
144 AnyShape, ExactExprShape, ExprKindShape, ListShape, OneOfShape, Shape, shape_value,
145 };
146
147 struct Specimen {
148 evaluated_calls: Arc<AtomicUsize>,
149 raw_calls: Arc<AtomicUsize>,
150 }
151
152 impl Object for Specimen {
153 fn display(&self, _cx: &mut Cx) -> Result<String> {
154 Ok("#<non-typescript-specimen>".to_owned())
155 }
156
157 fn as_any(&self) -> &dyn std::any::Any {
158 self
159 }
160 }
161
162 impl ObjectCompat for Specimen {
163 fn as_callable(&self) -> Option<&dyn Callable> {
164 Some(self)
165 }
166 }
167
168 impl Callable for Specimen {
169 fn call(&self, cx: &mut Cx, _args: Args) -> Result<Value> {
170 self.evaluated_calls.fetch_add(1, Ordering::SeqCst);
171 cx.factory().string("unchecked-result".to_owned())
172 }
173
174 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
175 self.raw_calls.fetch_add(1, Ordering::SeqCst);
176 cx.factory().expr(Expr::List(args.into_exprs()))
177 }
178 }
179
180 fn specimen(cx: &mut Cx) -> (Value, Arc<AtomicUsize>, Arc<AtomicUsize>) {
181 let evaluated_calls = Arc::new(AtomicUsize::new(0));
182 let raw_calls = Arc::new(AtomicUsize::new(0));
183 let value = cx
184 .factory()
185 .opaque(Arc::new(Specimen {
186 evaluated_calls: evaluated_calls.clone(),
187 raw_calls: raw_calls.clone(),
188 }))
189 .unwrap();
190 (value, evaluated_calls, raw_calls)
191 }
192
193 #[test]
194 fn metadata_is_observational_for_evaluated_and_raw_calls() {
195 let mut cx = cx();
196 let (inner, evaluated_calls, raw_calls) = specimen(&mut cx);
197 let impossible_args = shape_value(
198 Symbol::new("literal-args"),
199 Arc::new(ExactExprShape::new(Expr::List(vec![Expr::String(
200 "never supplied".to_owned(),
201 )]))),
202 );
203 let impossible_result = shape_value(
204 Symbol::new("number-result"),
205 Arc::new(ExprKindShape::new(sim_kernel::ExprKind::Number)),
206 );
207 let wrapped = browse_signature(
208 &mut cx,
209 inner,
210 Some(impossible_args.clone()),
211 Some(impossible_result.clone()),
212 )
213 .unwrap();
214 let callable = wrapped.object().as_callable().unwrap();
215
216 let result = callable.call(&mut cx, Args::new(Vec::new())).unwrap();
217 assert_eq!(
218 result.object().display(&mut cx).unwrap(),
219 "unchecked-result"
220 );
221 assert_eq!(evaluated_calls.load(Ordering::SeqCst), 1);
222
223 let raw = vec![Expr::Symbol(Symbol::new("unevaluated"))];
224 let result = callable
225 .call_exprs(&mut cx, RawArgs::new(raw.clone()))
226 .unwrap();
227 assert_eq!(result.object().as_expr(&mut cx).unwrap(), Expr::List(raw));
228 assert_eq!(raw_calls.load(Ordering::SeqCst), 1);
229 assert_eq!(evaluated_calls.load(Ordering::SeqCst), 1);
230 assert_eq!(
231 callable.browse_args_shape(&mut cx).unwrap(),
232 Some(impossible_args)
233 );
234 assert_eq!(
235 callable.browse_result_shape(&mut cx).unwrap(),
236 Some(impossible_result)
237 );
238 }
239
240 #[test]
241 fn faithful_shape_categories_are_retained_and_non_shapes_fail_closed() {
242 let mut cx = cx();
243 let categories: Vec<(&str, Arc<dyn Shape>)> = vec![
244 (
245 "primitive",
246 Arc::new(ExprKindShape::new(sim_kernel::ExprKind::Bool)),
247 ),
248 ("literal", Arc::new(ExactExprShape::new(Expr::Bool(true)))),
249 (
250 "union",
251 Arc::new(OneOfShape::new(vec![
252 Arc::new(ExprKindShape::new(sim_kernel::ExprKind::Bool)),
253 Arc::new(ExprKindShape::new(sim_kernel::ExprKind::String)),
254 ])),
255 ),
256 (
257 "tuple-or-array",
258 Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
259 ),
260 ("bounded-named", Arc::new(AnyShape)),
261 ];
262
263 for (category, shape) in categories {
264 let (inner, _, _) = specimen(&mut cx);
265 let metadata = shape_value(Symbol::qualified("law", category), shape);
266 let wrapped = browse_signature(&mut cx, inner, Some(metadata.clone()), None).unwrap();
267 assert_eq!(
268 wrapped
269 .object()
270 .as_callable()
271 .unwrap()
272 .browse_args_shape(&mut cx)
273 .unwrap(),
274 Some(metadata),
275 "{category} metadata must be retained without projection"
276 );
277 }
278
279 let (inner, _, _) = specimen(&mut cx);
280 let not_a_shape = cx
281 .factory()
282 .string("conditional type widened to any".to_owned())
283 .unwrap();
284 let error = BrowseSignature::new(inner, Some(not_a_shape), None)
285 .err()
286 .expect("non-equivalent metadata must be rejected");
287 assert!(error.to_string().contains("must be a Shape value"));
288 }
289}