Skip to main content

sim_shape/
functions.rs

1//! The callable shape object: function objects with shape-typed cases,
2//! overload selection across those cases, and shape-as-value wrapping.
3
4use std::cmp::Ordering;
5use std::sync::Arc;
6
7mod shape_object;
8
9use sim_kernel::{
10    Args, Callable, ClassRef, Cx, Demand, FunctionId, Object, PreparedArgs, RawArgs,
11    ReadConstructor, Result, ShapeId, ShapeRef, Symbol, Value, shape_is_subshape_of,
12};
13
14use crate::base::{Bindings, Shape, ShapeMatch};
15use crate::diagnostics::{callable_mismatch_diagnostic, overload_selection_diagnostic};
16use crate::primitives::OneOfShape;
17pub use shape_object::{ShapeObject, shape_value, shape_value_with_encoding};
18
19/// Native implementation backing a single [`FunctionCase`].
20///
21/// Invoked with the forced/prepared arguments and the bindings captured while
22/// the case's argument shape matched.
23pub type NativeFunctionImpl = fn(&mut Cx, &PreparedArgs, Bindings) -> Result<Value>;
24
25/// One overload case of a [`FunctionObject`]: a shape-typed signature paired
26/// with the native code that runs when it is selected.
27#[derive(Clone)]
28pub struct FunctionCase {
29    /// Stable identifier for this case within the function.
30    pub id: sim_kernel::CaseId,
31    /// Symbol naming the case.
32    pub name: Symbol,
33    /// Shape the argument list must match for this case to apply.
34    pub args: Arc<dyn Shape>,
35    /// Optional shape the result is checked against after the call.
36    pub result: Option<Arc<dyn Shape>>,
37    /// Per-argument evaluation demand (how far each argument is forced).
38    pub demand: Vec<sim_kernel::Demand>,
39    /// Tie-break priority; higher wins before match score is consulted.
40    pub priority: i32,
41    /// Native code run when this case is selected.
42    pub implementation: NativeFunctionImpl,
43}
44
45/// A callable function object: a named set of shape-typed overload cases with
46/// selection driven by case priority and argument match score.
47#[derive(Clone)]
48pub struct FunctionObject {
49    /// Stable function identifier.
50    pub id: FunctionId,
51    /// Symbol naming the function.
52    pub symbol: Symbol,
53    /// Overload cases in registration order.
54    pub cases: Vec<FunctionCase>,
55}
56
57/// The case chosen by overload selection together with its match result.
58#[derive(Clone)]
59pub struct SelectedCase<'a> {
60    /// The selected overload case.
61    pub case: &'a FunctionCase,
62    /// The match (score, captures, diagnostics) that selected it.
63    pub match_result: ShapeMatch,
64}
65
66impl FunctionObject {
67    /// Build a function object from an id, symbol, and its overload cases.
68    pub fn new(id: FunctionId, symbol: Symbol, cases: Vec<FunctionCase>) -> Self {
69        Self { id, symbol, cases }
70    }
71
72    /// Pick the overload case that best matches the prepared arguments.
73    ///
74    /// Cases whose argument shape accepts the arguments are ordered by priority
75    /// first, then by proven argument-shape specificity (the subshape lattice),
76    /// and only then by additive match score: a case whose argument shape is a
77    /// proven subshape of another's is strictly more specific and wins even at
78    /// an equal or lower score. Returns [`Error::NoMatchingOverload`] when
79    /// nothing matches and [`Error::AmbiguousOverload`] when two top cases
80    /// remain unordered (equal priority, neither shape subsumes the other, and
81    /// equal score).
82    ///
83    /// [`Error::NoMatchingOverload`]: sim_kernel::Error::NoMatchingOverload
84    /// [`Error::AmbiguousOverload`]: sim_kernel::Error::AmbiguousOverload
85    pub fn select_case<'a>(
86        &'a self,
87        cx: &mut Cx,
88        prepared: &PreparedArgs,
89    ) -> Result<SelectedCase<'a>> {
90        let mut matches = Vec::new();
91        let mut diagnostics = Vec::new();
92        let args = cx.new_list(prepared.values().to_vec())?;
93
94        for case in &self.cases {
95            let matched = case.args.check_value(cx, args.clone())?;
96            if matched.accepted {
97                matches.push(SelectedCase {
98                    case,
99                    match_result: matched,
100                });
101            } else {
102                diagnostics.extend(matched.diagnostics);
103            }
104        }
105
106        if matches.is_empty() {
107            diagnostics.insert(
108                0,
109                overload_selection_diagnostic(&self.symbol, "no matching overload case"),
110            );
111            diagnostics.push(callable_mismatch_diagnostic(
112                &self.symbol,
113                "an applicable case",
114                "rejected arguments",
115            ));
116            return Err(sim_kernel::Error::NoMatchingOverload {
117                function: self.id,
118                diagnostics,
119            });
120        }
121
122        // Order by priority then additive score as a stable starting point,
123        // then refine with the proven subshape lattice so a strictly more
124        // specific overload is preferred over a tying or higher-scoring one.
125        matches.sort_by(|left, right| {
126            right
127                .case
128                .priority
129                .cmp(&left.case.priority)
130                .then_with(|| right.match_result.score.cmp(&left.match_result.score))
131        });
132
133        let mut best = 0usize;
134        for index in 1..matches.len() {
135            if self
136                .compare_cases(cx, &matches[index], &matches[best])?
137                .is_gt()
138            {
139                best = index;
140            }
141        }
142
143        // The selection is ambiguous only when another accepted case cannot be
144        // ordered against the best one: equal priority, neither argument shape
145        // a proven subshape of the other, and an equal additive score.
146        let mut candidates = vec![matches[best].case.id];
147        for index in 0..matches.len() {
148            if index != best
149                && self
150                    .compare_cases(cx, &matches[best], &matches[index])?
151                    .is_eq()
152            {
153                candidates.push(matches[index].case.id);
154            }
155        }
156        if candidates.len() > 1 {
157            cx.push_diagnostic(overload_selection_diagnostic(
158                &self.symbol,
159                "ambiguous top-ranked overload cases",
160            ));
161            return Err(sim_kernel::Error::AmbiguousOverload {
162                function: self.id,
163                candidates,
164            });
165        }
166
167        Ok(matches[best].clone())
168    }
169
170    /// Order two accepted cases: priority, then proven subshape specificity,
171    /// then additive match score.
172    ///
173    /// `Greater` means `a` is preferred over `b`. A case whose argument shape
174    /// is a proven subshape of the other's is strictly more specific and wins
175    /// before the additive score is consulted; `Equal` means the runtime could
176    /// not order the two and selection is ambiguous.
177    fn compare_cases(
178        &self,
179        cx: &mut Cx,
180        a: &SelectedCase<'_>,
181        b: &SelectedCase<'_>,
182    ) -> Result<Ordering> {
183        match a.case.priority.cmp(&b.case.priority) {
184            Ordering::Equal => {}
185            other => return Ok(other),
186        }
187
188        let a_subshape = shape_is_subshape_of(cx, a.case.args.as_ref(), b.case.args.as_ref())?;
189        let b_subshape = shape_is_subshape_of(cx, b.case.args.as_ref(), a.case.args.as_ref())?;
190        match (a_subshape, b_subshape) {
191            (true, false) => return Ok(Ordering::Greater),
192            (false, true) => return Ok(Ordering::Less),
193            _ => {}
194        }
195
196        Ok(a.match_result.score.cmp(&b.match_result.score))
197    }
198
199    /// Shape accepting any case's arguments: the lone case's shape, or a
200    /// one-of over every case. `None` when the function has no cases.
201    pub fn combined_args_shape(&self) -> Option<Arc<dyn Shape>> {
202        match self.cases.as_slice() {
203            [] => None,
204            [one] => Some(one.args.clone()),
205            many => Some(Arc::new(OneOfShape::new(
206                many.iter().map(|case| case.args.clone()).collect(),
207            ))),
208        }
209    }
210
211    /// Shape covering every case's result, or `None` if any case omits a
212    /// result shape. A single result is returned directly; many become a
213    /// one-of.
214    pub fn combined_result_shape(&self) -> Option<Arc<dyn Shape>> {
215        let shapes = self
216            .cases
217            .iter()
218            .map(|case| case.result.clone())
219            .collect::<Option<Vec<_>>>()?;
220        match shapes.as_slice() {
221            [] => None,
222            [one] => Some(one.clone()),
223            many => Some(Arc::new(OneOfShape::new(many.to_vec()))),
224        }
225    }
226
227    /// Evaluation demand declared for argument `index` across all cases.
228    ///
229    /// Returns the shared demand when every case agrees, [`Demand::Value`] when
230    /// they disagree, and `None` when no case declares that position.
231    pub fn declared_demand(&self, index: usize) -> Option<Demand> {
232        let mut declared = None;
233        for case in &self.cases {
234            let case_demand = case.demand.get(index).copied().unwrap_or(Demand::Value);
235            match declared {
236                None => declared = Some(case_demand),
237                Some(existing) if existing == case_demand => {}
238                Some(_) => return Some(Demand::Value),
239            }
240        }
241        declared
242    }
243
244    /// Per-position demands for the call, sized to the widest case and
245    /// defaulting unspecified positions to [`Demand::Value`].
246    pub fn declared_demands(&self) -> Vec<Demand> {
247        let max_len = self
248            .cases
249            .iter()
250            .map(|case| case.demand.len())
251            .max()
252            .unwrap_or(0);
253        (0..max_len)
254            .map(|index| self.declared_demand(index).unwrap_or(Demand::Value))
255            .collect()
256    }
257}
258
259impl Object for FunctionObject {
260    fn display(&self, _cx: &mut Cx) -> Result<String> {
261        Ok(format!("#<function {}>", self.symbol))
262    }
263
264    fn as_any(&self) -> &dyn std::any::Any {
265        self
266    }
267}
268
269impl sim_kernel::ObjectCompat for FunctionObject {
270    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
271        if let Some(value) = cx
272            .registry()
273            .class_by_symbol(&Symbol::qualified("core", "Function"))
274        {
275            return Ok(value.clone());
276        }
277        cx.factory().class_stub(
278            sim_kernel::CORE_FUNCTION_CLASS_ID,
279            Symbol::qualified("core", "Function"),
280        )
281    }
282    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
283        let mut entries = vec![
284            (
285                Symbol::new("symbol"),
286                cx.factory().string(self.symbol.to_string())?,
287            ),
288            (
289                Symbol::new("case-count"),
290                cx.factory().number_literal(
291                    Symbol::qualified("numbers", "f64"),
292                    self.cases.len().to_string(),
293                )?,
294            ),
295        ];
296        for (index, case) in self.cases.iter().enumerate() {
297            entries.push((
298                Symbol::qualified("case", case.name.name.clone()),
299                cx.factory().string(case.name.to_string())?,
300            ));
301            let args_doc = case.args.describe(cx)?;
302            entries.push((
303                Symbol::qualified("case-args", index.to_string()),
304                cx.factory().string(args_doc.name)?,
305            ));
306            if let Some(result) = &case.result {
307                let result_doc = result.describe(cx)?;
308                entries.push((
309                    Symbol::qualified("case-result", index.to_string()),
310                    cx.factory().string(result_doc.name)?,
311                ));
312            }
313            if !case.demand.is_empty() {
314                entries.push((
315                    Symbol::qualified("case-demand", index.to_string()),
316                    cx.factory().list(
317                        case.demand
318                            .iter()
319                            .map(|demand| {
320                                let name = match demand {
321                                    Demand::Never => "never",
322                                    Demand::Bool => "bool",
323                                    Demand::Value => "value",
324                                    Demand::Expr => "expr",
325                                    Demand::Class(_) => "class",
326                                    Demand::Shape(_) => "shape",
327                                };
328                                cx.factory().symbol(Symbol::new(name))
329                            })
330                            .collect::<Result<Vec<_>>>()?,
331                    )?,
332                ));
333            }
334        }
335        cx.factory().table(entries)
336    }
337    fn as_callable(&self) -> Option<&dyn Callable> {
338        Some(self)
339    }
340    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
341        Some(self)
342    }
343}
344
345impl Callable for FunctionObject {
346    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
347        let prepared = PreparedArgs::new(args.into_vec());
348        let selected = self.select_case(cx, &prepared)?;
349        let prepared = refine_prepared_args(cx, &prepared, selected.case)?;
350        let bindings = selected.match_result.captures;
351        let env = bindings.clone().into_child_env(cx)?;
352        let result = cx.with_env(env, |cx| {
353            (selected.case.implementation)(cx, &prepared, bindings)
354        })?;
355
356        if let Some(shape) = &selected.case.result {
357            let matched = shape.check_value(cx, result.clone())?;
358            if !matched.accepted {
359                return Err(sim_kernel::Error::WrongShape {
360                    expected: shape.id().unwrap_or(ShapeId(0)),
361                    diagnostics: matched.diagnostics,
362                });
363            }
364        }
365
366        Ok(result)
367    }
368
369    fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
370        Ok(self
371            .combined_args_shape()
372            .map(|shape| shape_value(Symbol::qualified(self.symbol.to_string(), "args"), shape)))
373    }
374
375    fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
376        Ok(self
377            .combined_result_shape()
378            .map(|shape| shape_value(Symbol::qualified(self.symbol.to_string(), "result"), shape)))
379    }
380
381    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
382        let prepared =
383            cx.eval_policy_ref()
384                .prepare_call_args(cx, args, &self.declared_demands())?;
385        let selected = self.select_case(cx, &prepared)?;
386        let prepared = refine_prepared_args(cx, &prepared, selected.case)?;
387        let bindings = selected.match_result.captures;
388        let env = bindings.clone().into_child_env(cx)?;
389        let result = cx.with_env(env, |cx| {
390            (selected.case.implementation)(cx, &prepared, bindings)
391        })?;
392
393        if let Some(shape) = &selected.case.result {
394            let matched = shape.check_value(cx, result.clone())?;
395            if !matched.accepted {
396                return Err(sim_kernel::Error::WrongShape {
397                    expected: shape.id().unwrap_or(ShapeId(0)),
398                    diagnostics: matched.diagnostics,
399                });
400            }
401        }
402
403        Ok(result)
404    }
405}
406
407fn refine_prepared_args(
408    cx: &mut Cx,
409    prepared: &PreparedArgs,
410    case: &FunctionCase,
411) -> Result<PreparedArgs> {
412    let mut values = Vec::with_capacity(prepared.len());
413    for index in 0..prepared.len() {
414        let value = prepared
415            .get(index)
416            .cloned()
417            .ok_or_else(|| sim_kernel::Error::Eval(format!("missing prepared arg {index}")))?;
418        let demand = case.demand.get(index).copied().unwrap_or(Demand::Value);
419        values.push(force_for_case_demand(cx, value, demand)?);
420    }
421    Ok(PreparedArgs::new(values))
422}
423
424fn force_for_case_demand(cx: &mut Cx, value: Value, demand: Demand) -> Result<Value> {
425    match demand {
426        Demand::Shape(shape_id) => {
427            let value = cx.force(value, Demand::Value)?;
428            let shape_value = cx
429                .registry()
430                .shape_value(shape_id)
431                .cloned()
432                .ok_or_else(|| sim_kernel::Error::WrongShape {
433                    expected: shape_id,
434                    diagnostics: Vec::new(),
435                })?;
436            let shape = shape_value
437                .object()
438                .as_shape()
439                .ok_or(sim_kernel::Error::TypeMismatch {
440                    expected: "shape object",
441                    found: "non-shape object",
442                })?;
443            let matched = shape.check_value(cx, value.clone())?;
444            if matched.accepted {
445                Ok(value)
446            } else {
447                Err(sim_kernel::Error::WrongShape {
448                    expected: shape_id,
449                    diagnostics: matched.diagnostics,
450                })
451            }
452        }
453        other => cx.force(value, other),
454    }
455}
456
457impl ReadConstructor for FunctionObject {
458    fn symbol(&self) -> Symbol {
459        self.symbol.clone()
460    }
461
462    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
463        match self.combined_args_shape() {
464            Some(shape) => Ok(shape_value(
465                Symbol::qualified(self.symbol.to_string(), "args-shape"),
466                shape,
467            )),
468            None => cx.factory().nil(),
469        }
470    }
471
472    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
473        self.call(cx, Args::new(args))
474    }
475}
476
477/// Merge several functions into one whose cases are the union of theirs.
478///
479/// The result is a fresh [`FunctionObject`] with a generated `overload:` symbol
480/// and a new function id; selection then ranks across all combined cases.
481pub fn overload(cx: &mut Cx, functions: Vec<FunctionObject>) -> Result<FunctionObject> {
482    let mut cases = Vec::new();
483    let mut names = Vec::new();
484
485    for function in functions {
486        names.push(function.symbol.to_string());
487        cases.extend(function.cases);
488    }
489
490    let symbol = Symbol::new(format!("overload:{}", names.join("+")));
491    Ok(FunctionObject {
492        id: cx.registry_mut().fresh_function_id(),
493        symbol,
494        cases,
495    })
496}
497
498/// Borrow the overload cases of a function object.
499pub fn function_cases(function: &FunctionObject) -> &[FunctionCase] {
500    &function.cases
501}
502
503/// Borrow the argument shape of a single case.
504pub fn case_shape(case: &FunctionCase) -> &dyn Shape {
505    case.args.as_ref()
506}
507
508/// Borrow the result shape of a case, if it declares one.
509pub fn case_result_shape(case: &FunctionCase) -> Option<&dyn Shape> {
510    case.result.as_deref()
511}