Skip to main content

sim_shape/functions/
select.rs

1//! Overload selection for shape-typed function cases.
2
3use std::cmp::Ordering;
4
5use sim_kernel::{Cx, Diagnostic, PreparedArgs, Result, shape_is_subshape_of};
6
7use super::{FunctionCase, FunctionObject, SelectedCase};
8use crate::diagnostics::{callable_mismatch_diagnostic, overload_selection_diagnostic};
9
10impl FunctionObject {
11    /// Pick the overload case that best matches the prepared arguments.
12    ///
13    /// Cases whose argument shape accepts the arguments are ordered by priority
14    /// first, then by proven argument-shape specificity (the subshape lattice),
15    /// and only then by additive match score: a case whose argument shape is a
16    /// proven subshape of another's is strictly more specific and wins even at
17    /// an equal or lower score. Returns [`Error::NoMatchingOverload`] when
18    /// nothing matches and [`Error::AmbiguousOverload`] when two top cases
19    /// remain unordered (equal priority, neither shape subsumes the other, and
20    /// equal score).
21    ///
22    /// [`Error::NoMatchingOverload`]: sim_kernel::Error::NoMatchingOverload
23    /// [`Error::AmbiguousOverload`]: sim_kernel::Error::AmbiguousOverload
24    pub fn select_case<'a>(
25        &'a self,
26        cx: &mut Cx,
27        prepared: &PreparedArgs,
28    ) -> Result<SelectedCase<'a>> {
29        let cases = self.cases.iter().collect::<Vec<_>>();
30        let (matches, diagnostics) = self.collect_matches(cx, prepared, &cases)?;
31        self.select_best_case(cx, matches, diagnostics)
32    }
33
34    pub(in crate::functions) fn collect_matches<'a>(
35        &'a self,
36        cx: &mut Cx,
37        prepared: &PreparedArgs,
38        cases: &[&'a FunctionCase],
39    ) -> Result<(Vec<SelectedCase<'a>>, Vec<Diagnostic>)> {
40        let mut matches = Vec::new();
41        let mut diagnostics = Vec::new();
42        let args = cx.new_list(prepared.values().to_vec())?;
43
44        for case in cases {
45            let matched = case.args.check_value(cx, args.clone())?;
46            if matched.accepted {
47                matches.push(SelectedCase {
48                    case,
49                    match_result: matched,
50                });
51            } else {
52                diagnostics.extend(matched.diagnostics);
53            }
54        }
55
56        Ok((matches, diagnostics))
57    }
58
59    pub(in crate::functions) fn select_best_case<'a>(
60        &'a self,
61        cx: &mut Cx,
62        mut matches: Vec<SelectedCase<'a>>,
63        mut diagnostics: Vec<Diagnostic>,
64    ) -> Result<SelectedCase<'a>> {
65        if matches.is_empty() {
66            diagnostics.insert(
67                0,
68                overload_selection_diagnostic(&self.symbol, "no matching overload case"),
69            );
70            diagnostics.push(callable_mismatch_diagnostic(
71                &self.symbol,
72                "an applicable case",
73                "rejected arguments",
74            ));
75            return Err(sim_kernel::Error::NoMatchingOverload {
76                function: self.id,
77                diagnostics,
78            });
79        }
80
81        // Order by priority then additive score as a stable starting point,
82        // then refine with the proven subshape lattice so a strictly more
83        // specific overload is preferred over a tying or higher-scoring one.
84        matches.sort_by(|left, right| {
85            right
86                .case
87                .priority
88                .cmp(&left.case.priority)
89                .then_with(|| right.match_result.score.cmp(&left.match_result.score))
90        });
91
92        let mut best = 0usize;
93        for index in 1..matches.len() {
94            if self
95                .compare_cases(cx, &matches[index], &matches[best])?
96                .is_gt()
97            {
98                best = index;
99            }
100        }
101
102        // The selection is ambiguous only when another accepted case cannot be
103        // ordered against the best one: equal priority, neither argument shape
104        // a proven subshape of the other, and an equal additive score.
105        let mut candidates = vec![matches[best].case.id];
106        for index in 0..matches.len() {
107            if index != best
108                && self
109                    .compare_cases(cx, &matches[best], &matches[index])?
110                    .is_eq()
111            {
112                candidates.push(matches[index].case.id);
113            }
114        }
115        if candidates.len() > 1 {
116            cx.push_diagnostic(overload_selection_diagnostic(
117                &self.symbol,
118                "ambiguous top-ranked overload cases",
119            ));
120            return Err(sim_kernel::Error::AmbiguousOverload {
121                function: self.id,
122                candidates,
123            });
124        }
125
126        Ok(matches[best].clone())
127    }
128
129    /// Order two accepted cases: priority, then proven subshape specificity,
130    /// then additive match score.
131    ///
132    /// `Greater` means `a` is preferred over `b`. A case whose argument shape
133    /// is a proven subshape of the other's is strictly more specific and wins
134    /// before the additive score is consulted; `Equal` means the runtime could
135    /// not order the two and selection is ambiguous.
136    fn compare_cases(
137        &self,
138        cx: &mut Cx,
139        a: &SelectedCase<'_>,
140        b: &SelectedCase<'_>,
141    ) -> Result<Ordering> {
142        match a.case.priority.cmp(&b.case.priority) {
143            Ordering::Equal => {}
144            other => return Ok(other),
145        }
146
147        let a_subshape = shape_is_subshape_of(cx, a.case.args.as_ref(), b.case.args.as_ref())?;
148        let b_subshape = shape_is_subshape_of(cx, b.case.args.as_ref(), a.case.args.as_ref())?;
149        match (a_subshape, b_subshape) {
150            (true, false) => return Ok(Ordering::Greater),
151            (false, true) => return Ok(Ordering::Less),
152            _ => {}
153        }
154
155        Ok(a.match_result.score.cmp(&b.match_result.score))
156    }
157}