1use std::cmp::Ordering;
2
3use sim_kernel::{Cx, Diagnostic, Error, HintMetadata, Result, Symbol, Value};
4
5use crate::{
6 DispatchMethod, MethodRole, MethodSpecificity,
7 method::{ambiguous_primary_error, compare_specificity},
8};
9
10pub type Multimethod = GenericFunction;
15
16pub struct GenericFunction {
55 name: Symbol,
56 methods: Vec<DispatchMethod>,
57}
58
59impl GenericFunction {
60 pub fn new(name: Symbol) -> Self {
62 Self {
63 name,
64 methods: Vec::new(),
65 }
66 }
67
68 pub fn name(&self) -> &Symbol {
70 &self.name
71 }
72
73 pub fn methods(&self) -> &[DispatchMethod] {
75 &self.methods
76 }
77
78 pub fn operation_hints(&self) -> Vec<HintMetadata> {
80 let mut hints = vec![
81 HintMetadata::new(
82 Symbol::qualified("runtime-hint", "operation"),
83 format!("operation {}", self.name),
84 )
85 .with_tag(Symbol::qualified("runtime", "operation"))
86 .with_argument(self.name.clone()),
87 ];
88 for method in &self.methods {
89 hints.push(
90 HintMetadata::new(
91 Symbol::qualified("runtime-hint", "method"),
92 format!("method {}", method.id()),
93 )
94 .with_detail(format!(
95 "{} method with {} argument shapes",
96 method.role().as_symbol(),
97 method.arity()
98 ))
99 .with_tag(Symbol::qualified("runtime", "method"))
100 .with_argument(method.id().clone()),
101 );
102 hints.extend(method.hints().iter().cloned());
103 }
104 hints
105 }
106
107 pub fn add_method(&mut self, method: DispatchMethod) -> Result<()> {
109 if let Some(existing) = self.methods.iter().find(|existing| {
110 existing.id() == method.id()
111 && existing.role() == method.role()
112 && existing.arity() == method.arity()
113 }) {
114 return Err(Error::Eval(format!(
115 "generic function {} already has method {} for role {:?}",
116 self.name,
117 existing.id(),
118 existing.role()
119 )));
120 }
121 self.methods.push(method);
122 Ok(())
123 }
124
125 pub fn select_primary(&self, cx: &mut Cx, args: &[Value]) -> Result<MethodSpecificity> {
130 let selected = self.selected_primary(cx, args)?;
131 Ok(selected.specificity)
132 }
133
134 pub fn dispatch_order(&self, cx: &mut Cx, args: &[Value]) -> Result<Vec<Symbol>> {
139 Ok(self
140 .execution_plan(cx, args)?
141 .into_iter()
142 .map(|index| self.methods[index].id().clone())
143 .collect())
144 }
145
146 pub fn inspect_specificity(
151 &self,
152 cx: &mut Cx,
153 args: &[Value],
154 ) -> Result<Vec<MethodSpecificity>> {
155 let mut accepted = Vec::new();
156 for method in &self.methods {
157 if let Some(specificity) = method.match_args(cx, args)? {
158 accepted.push(specificity);
159 }
160 }
161 accepted.sort_by(|left, right| {
162 left.role()
163 .combination_rank()
164 .cmp(&right.role().combination_rank())
165 .then_with(|| compare_specificity(right, left))
166 .then_with(|| left.method().cmp(right.method()))
167 });
168 Ok(accepted)
169 }
170
171 pub fn call(&self, cx: &mut Cx, args: &[Value]) -> Result<Value> {
176 let plan = self.execution_plan(cx, args)?;
177 let mut primary_result = None;
178 for index in plan {
179 let method = &self.methods[index];
180 let result = method.invoke(cx, args)?;
181 if method.role() == MethodRole::Primary {
182 primary_result = Some(result);
183 }
184 }
185 primary_result.ok_or_else(|| {
186 Error::Eval(format!(
187 "generic function {} has no applicable primary method",
188 self.name
189 ))
190 })
191 }
192
193 pub fn call_for_profile(
199 &self,
200 cx: &mut Cx,
201 _profile: &Symbol,
202 args: &[Value],
203 ) -> Result<Value> {
204 self.call(cx, args)
205 }
206
207 fn execution_plan(&self, cx: &mut Cx, args: &[Value]) -> Result<Vec<usize>> {
208 let mut around = self.applicable_for_role(cx, args, MethodRole::Around)?;
209 let mut before = self.applicable_for_role(cx, args, MethodRole::Before)?;
210 let mut after = self.applicable_for_role(cx, args, MethodRole::After)?;
211 sort_most_specific_first(&mut around);
212 sort_most_specific_first(&mut before);
213 sort_least_specific_first(&mut after);
214 let primary = self.selected_primary(cx, args)?;
215
216 let mut plan = Vec::with_capacity(around.len() + before.len() + 1 + after.len());
217 plan.extend(around.into_iter().map(|method| method.index));
218 plan.extend(before.into_iter().map(|method| method.index));
219 plan.push(primary.index);
220 plan.extend(after.into_iter().map(|method| method.index));
221 Ok(plan)
222 }
223
224 fn selected_primary(&self, cx: &mut Cx, args: &[Value]) -> Result<ApplicableMethod> {
225 let mut primary = self.applicable_for_role(cx, args, MethodRole::Primary)?;
226 if primary.is_empty() {
227 cx.push_diagnostic(self.selection_diagnostic("no applicable primary method"));
228 return Err(Error::Eval(format!(
229 "generic function {} has no applicable primary method",
230 self.name
231 )));
232 }
233 sort_most_specific_first(&mut primary);
234 if primary.len() > 1
235 && compare_specificity(&primary[0].specificity, &primary[1].specificity)
236 == Ordering::Equal
237 {
238 cx.push_diagnostic(self.selection_diagnostic("ambiguous primary methods"));
239 return Err(ambiguous_primary_error(
240 &self.name,
241 primary[0].specificity.method(),
242 primary[1].specificity.method(),
243 ));
244 }
245 Ok(primary.remove(0))
246 }
247
248 fn applicable_for_role(
249 &self,
250 cx: &mut Cx,
251 args: &[Value],
252 role: MethodRole,
253 ) -> Result<Vec<ApplicableMethod>> {
254 let mut accepted = Vec::new();
255 for (index, method) in self.methods.iter().enumerate() {
256 if method.role() != role {
257 continue;
258 }
259 if let Some(specificity) = method.match_args(cx, args)? {
260 accepted.push(ApplicableMethod { index, specificity });
261 }
262 }
263 Ok(accepted)
264 }
265
266 fn selection_diagnostic(&self, reason: &'static str) -> Diagnostic {
267 let message = format!("generic function {}: {reason}", self.name);
268 let mut diagnostic = HintMetadata::new(
269 Symbol::qualified("runtime-hint", "overload-selection"),
270 "dispatch selection",
271 )
272 .with_detail(message.clone())
273 .with_tag(Symbol::qualified("runtime", "dispatch"))
274 .with_argument(self.name.clone())
275 .attach_to(Diagnostic::error(message).with_code(Symbol::qualified("runtime", "dispatch")));
276 for hint in self.operation_hints() {
277 diagnostic = hint.attach_to(diagnostic);
278 }
279 diagnostic
280 }
281}
282
283struct ApplicableMethod {
284 index: usize,
285 specificity: MethodSpecificity,
286}
287
288fn sort_most_specific_first(methods: &mut [ApplicableMethod]) {
289 methods.sort_by(|left, right| {
290 compare_specificity(&right.specificity, &left.specificity)
291 .then_with(|| left.specificity.method().cmp(right.specificity.method()))
292 });
293}
294
295fn sort_least_specific_first(methods: &mut [ApplicableMethod]) {
296 methods.sort_by(|left, right| {
297 compare_specificity(&left.specificity, &right.specificity)
298 .then_with(|| left.specificity.method().cmp(right.specificity.method()))
299 });
300}