Skip to main content

doge_interp/
call.rs

1//! Calls in every form Doge has: direct function calls, calls through a function
2//! value, method dispatch, constructors, module-member calls, and `super`. Each
3//! resolves its target, evaluates arguments, binds them to the callee's header
4//! (defaults, keyword arguments, and the `many` variadic), and runs the body.
5
6use doge_compiler as dc;
7use doge_runtime::{
8    builtin_method, callee_function, enter_call, exit_call, function_arity_error, object_class_id,
9    DogeError, DogeResult, ErrorKind, Value,
10};
11
12use crate::natives::call_native;
13use crate::{cell, scope, Callable, Flow, Interp, Scope, Template};
14
15/// A call's evaluated arguments: positional values, then `(name, value)` keyword
16/// pairs in source order.
17type EvaluatedArgs = (Vec<Value>, Vec<(String, Value)>);
18
19impl Interp {
20    /// Evaluate a call expression, resolving the callee the same way the compiler
21    /// does: a known function/constructor/module member statically, anything else
22    /// through the value it evaluates to.
23    pub(crate) fn eval_call(
24        &mut self,
25        callee: &dc::Expr,
26        args: &[dc::Expr],
27        kwargs: &[(String, dc::Expr)],
28        frame: &Scope,
29        fid: u32,
30    ) -> DogeResult<Value> {
31        match callee {
32            dc::Expr::Ident { name, .. } => {
33                // A bound name (local, or a top-level function/variable held as a
34                // value) is called through its value.
35                if let Some(cell) = self.lookup(frame, fid, name) {
36                    let value = cell.borrow().clone();
37                    let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
38                    return self.call_value(value, args, kwargs);
39                }
40                if let Some(id) = self.builtin_ids.get(name).copied() {
41                    let args = self.eval_positional(args, frame, fid)?;
42                    return self.call_id(id, Vec::new(), args, Vec::new(), name);
43                }
44                if let Some(class_id) = self.class_id_in(fid, name) {
45                    let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
46                    return self.construct(class_id, args, kwargs, name);
47                }
48                Err(DogeError::type_error(format!(
49                    "cannot call {name} — it is not a function"
50                )))
51            }
52            // `nerd.sqrt(...)` / `utils.square(...)` — a member call on a module.
53            dc::Expr::Attr { obj, name, .. }
54                if matches!(obj.as_ref(), dc::Expr::Ident { name: base, .. }
55                    if self.lookup(frame, fid, base).is_none()
56                        && self.import_ref(fid, base).is_some()) =>
57            {
58                let dc::Expr::Ident { name: base, .. } = obj.as_ref() else {
59                    unreachable!("guarded to an Ident base")
60                };
61                let module = self
62                    .import_ref(fid, base)
63                    .expect("guarded: base is an import");
64                self.call_module_member(module, base, name, args, kwargs, frame, fid)
65            }
66            // `kabosu.speak(...)` — a method call, dispatched on the receiver.
67            dc::Expr::Attr { obj, name, .. } => {
68                let recv = self.eval(obj, frame, fid)?;
69                let args = self.eval_positional(args, frame, fid)?;
70                self.call_method(recv, name, args)
71            }
72            // Any other callee expression is called through its value.
73            other => {
74                let value = self.eval(other, frame, fid)?;
75                let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
76                self.call_value(value, args, kwargs)
77            }
78        }
79    }
80
81    /// Call a top-level function of the entry file by name with no arguments,
82    /// returning its value or the (catchable) error it raised. The test runner
83    /// drives each discovered `test`-prefixed function through this, after
84    /// [`Interp::prepare`](crate::Interp::prepare) has integrated the program.
85    pub fn call_entry_function(&mut self, name: &str) -> DogeResult<Value> {
86        let value = self
87            .lookup(&self.globals(0), 0, name)
88            .map(|cell| cell.borrow().clone())
89            .ok_or_else(|| {
90                DogeError::new(ErrorKind::ValueError, format!("no function named {name}"))
91            })?;
92        self.call_value(value, Vec::new(), Vec::new())
93    }
94
95    /// Evaluate positional arguments only.
96    fn eval_positional(
97        &mut self,
98        args: &[dc::Expr],
99        frame: &Scope,
100        fid: u32,
101    ) -> DogeResult<Vec<Value>> {
102        let mut out = Vec::with_capacity(args.len());
103        for arg in args {
104            out.push(self.eval(arg, frame, fid)?);
105        }
106        Ok(out)
107    }
108
109    /// Evaluate positional then keyword arguments, left to right.
110    fn eval_args(
111        &mut self,
112        args: &[dc::Expr],
113        kwargs: &[(String, dc::Expr)],
114        frame: &Scope,
115        fid: u32,
116    ) -> DogeResult<EvaluatedArgs> {
117        let positional = self.eval_positional(args, frame, fid)?;
118        let mut keyword = Vec::with_capacity(kwargs.len());
119        for (name, value) in kwargs {
120            keyword.push((name.clone(), self.eval(value, frame, fid)?));
121        }
122        Ok((positional, keyword))
123    }
124
125    /// Call a function value: unwrap it, then dispatch on its `fn_id`. Arity
126    /// diagnostics name the function by its own definition name, not the call site.
127    fn call_value(
128        &mut self,
129        value: Value,
130        args: Vec<Value>,
131        kwargs: Vec<(String, Value)>,
132    ) -> DogeResult<Value> {
133        // A bound method routes back through method dispatch, exactly as a direct
134        // `a.speak(...)` would. Indirect calls carry no keyword arguments (the
135        // checker rejects them), so the defensive guard here never fires in a
136        // checked program.
137        if let Value::BoundMethod(m) = &value {
138            if !kwargs.is_empty() {
139                return Err(DogeError::type_error(
140                    "cannot call a bound method with keyword arguments",
141                ));
142            }
143            let recv = m.receiver.clone();
144            let method = m.method.clone();
145            return self.call_method(recv, &method, args);
146        }
147        let func = callee_function(&value)?;
148        self.call_id(
149            func.fn_id as usize,
150            func.captures.clone(),
151            args,
152            kwargs,
153            &func.name,
154        )
155    }
156
157    /// Dispatch a call to the callable at `fn_id`: a native, or a user function
158    /// with its captured cells.
159    fn call_id(
160        &mut self,
161        id: usize,
162        captures: Vec<crate::Cell>,
163        args: Vec<Value>,
164        kwargs: Vec<(String, Value)>,
165        label: &str,
166    ) -> DogeResult<Value> {
167        let callable = self.callables[id].clone();
168        match callable.as_ref() {
169            Callable::Native(native) => call_native(native, args),
170            Callable::User(template) => {
171                self.call_user(template, &captures, args, kwargs, None, label)
172            }
173            // A class value called: build an instance through its constructor.
174            Callable::Ctor(class_id) => self.construct(*class_id, args, kwargs, label),
175        }
176    }
177
178    /// Run a user function/method/closure body: guard recursion, build its frame
179    /// (captures, `self`, parameters, hoisted locals), execute, and return the
180    /// value it `return`s (or `none` if it falls off the end).
181    fn call_user(
182        &mut self,
183        template: &Template,
184        captures: &[crate::Cell],
185        args: Vec<Value>,
186        kwargs: Vec<(String, Value)>,
187        self_value: Option<Value>,
188        label: &str,
189    ) -> DogeResult<Value> {
190        enter_call(&mut self.depth)?;
191        let saved_class = self.current_method_class;
192        self.current_method_class = template.method_class;
193        let result = self.user_body(template, captures, args, kwargs, self_value, label);
194        self.current_method_class = saved_class;
195        exit_call(&mut self.depth);
196        result
197    }
198
199    fn user_body(
200        &mut self,
201        template: &Template,
202        captures: &[crate::Cell],
203        args: Vec<Value>,
204        kwargs: Vec<(String, Value)>,
205        self_value: Option<Value>,
206        label: &str,
207    ) -> DogeResult<Value> {
208        let bound = self.bind_args(&template.params, args, kwargs, label, template.file_id)?;
209
210        let frame = scope();
211        {
212            let mut f = frame.borrow_mut();
213            for (name, captured) in template.capture_names.iter().zip(captures) {
214                f.insert(name.clone(), captured.clone());
215            }
216            if let Some(self_value) = self_value {
217                f.insert("self".to_string(), cell(self_value));
218            }
219            for (name, value) in template.params.binding_names().iter().zip(bound) {
220                f.insert(name.clone(), cell(value));
221            }
222        }
223        // Hoist the body's remaining local names to `none`, like the compiler's
224        // hoisted `Env` fields; nested-function values bind when their statement runs.
225        for name in dc::hoisted_names(&template.body) {
226            frame
227                .borrow_mut()
228                .entry(name)
229                .or_insert_with(|| cell(Value::None));
230        }
231
232        match self.exec_stmts(&template.body, &frame, template.file_id)? {
233            Flow::Return(value) => Ok(value),
234            // Falling off the end (or a checked-away stray bork/continue) yields none.
235            _ => Ok(Value::None),
236        }
237    }
238
239    /// Map a call's positional and keyword arguments onto a header's slots — filling
240    /// from positionals, then keywords, then defaults, and collecting any surplus
241    /// into the `many` variadic — returning the values in binding order. The arity
242    /// and keyword diagnostics match the compiler's wording.
243    fn bind_args(
244        &mut self,
245        params: &dc::Params,
246        args: Vec<Value>,
247        kwargs: Vec<(String, Value)>,
248        label: &str,
249        fid: u32,
250    ) -> DogeResult<Vec<Value>> {
251        let n = params.params.len();
252        let has_vararg = params.has_vararg();
253        let required = params.required();
254        let max = params.max_positional();
255        let total = args.len() + kwargs.len();
256
257        if !has_vararg && args.len() > n {
258            return Err(function_arity_error(label, required, max, total));
259        }
260
261        let mut slot: Vec<Option<Value>> = vec![None; n];
262        let mut extras: Vec<Value> = Vec::new();
263        for (i, arg) in args.into_iter().enumerate() {
264            if i < n {
265                slot[i] = Some(arg);
266            } else {
267                extras.push(arg);
268            }
269        }
270        for (name, value) in kwargs {
271            match params.params.iter().position(|p| p.name == name) {
272                Some(idx) if slot[idx].is_none() => slot[idx] = Some(value),
273                Some(_) => {
274                    return Err(DogeError::type_error(format!(
275                        "{label} got parameter {name} twice"
276                    )))
277                }
278                None => {
279                    return Err(DogeError::type_error(format!(
280                        "{label} has no parameter {name}"
281                    )))
282                }
283            }
284        }
285
286        let mut out = Vec::with_capacity(n + has_vararg as usize);
287        for (i, filled) in slot.into_iter().enumerate() {
288            match filled {
289                Some(value) => out.push(value),
290                None => match &params.params[i].default {
291                    Some(default) => out.push(self.eval(default, &scope(), fid)?),
292                    None => return Err(function_arity_error(label, required, max, total)),
293                },
294            }
295        }
296        if has_vararg {
297            out.push(Value::list(extras));
298        }
299        Ok(out)
300    }
301
302    /// Dispatch a method call: an object routes through its class's method table
303    /// (walking its ancestry); any other receiver forwards to the runtime's
304    /// collection methods, exactly like the compiled dispatcher.
305    fn call_method(&mut self, recv: Value, name: &str, args: Vec<Value>) -> DogeResult<Value> {
306        if !matches!(recv, Value::Object(_)) {
307            return builtin_method(&recv, name, args);
308        }
309        let class_id = object_class_id(&recv)?;
310        match self.resolve_method(class_id, name) {
311            Some((fn_id, def_class)) => {
312                let label = format!("{}.{name}", self.classes[def_class as usize].name);
313                self.invoke_method(fn_id, recv, args, &label)
314            }
315            None => Err(doge_runtime::no_such_method(&recv, name)),
316        }
317    }
318
319    /// Invoke a resolved method template with `recv` bound as `self`.
320    fn invoke_method(
321        &mut self,
322        fn_id: usize,
323        recv: Value,
324        args: Vec<Value>,
325        label: &str,
326    ) -> DogeResult<Value> {
327        let callable = self.callables[fn_id].clone();
328        let Callable::User(template) = callable.as_ref() else {
329            unreachable!("interp bug: a method id points at a native");
330        };
331        self.call_user(template, &[], args, Vec::new(), Some(recv), label)
332    }
333
334    /// `super.method(args)`: resolve `method` in the enclosing class's parent chain
335    /// and call it with the current `self`.
336    pub(crate) fn eval_super_call(
337        &mut self,
338        method: &str,
339        args: &[dc::Expr],
340        frame: &Scope,
341        fid: u32,
342    ) -> DogeResult<Value> {
343        let class_id = self
344            .current_method_class
345            .expect("checker guarantees super is inside a method");
346        let parent = self.classes[class_id as usize]
347            .parent
348            .expect("checker guarantees the class has a parent");
349        let (fn_id, def_class) = self
350            .resolve_method(parent, method)
351            .expect("checker guarantees a parent defines the method");
352        let self_value = self
353            .lookup(frame, fid, "self")
354            .expect("a method frame always binds self")
355            .borrow()
356            .clone();
357        let args = self.eval_positional(args, frame, fid)?;
358        let label = format!("{}.{method}", self.classes[def_class as usize].name);
359        self.invoke_method(fn_id, self_value, args, &label)
360    }
361
362    /// The method named `name` callable on `class_id`: the nearest definition up
363    /// its ancestry, returning its `fn_id` and the class that defines it.
364    pub(crate) fn resolve_method(&self, class_id: u32, name: &str) -> Option<(usize, u32)> {
365        let mut current = Some(class_id);
366        let mut guard = 0;
367        while let Some(cid) = current {
368            let class = &self.classes[cid as usize];
369            if let Some(fn_id) = class.methods.get(name) {
370                return Some((*fn_id, cid));
371            }
372            guard += 1;
373            if guard > self.classes.len() {
374                break;
375            }
376            current = class.parent;
377        }
378        None
379    }
380
381    /// Construct an instance of `class_id`: build the object, then run its
382    /// effective `init` (its own or the nearest inherited one) with `self` bound.
383    fn construct(
384        &mut self,
385        class_id: u32,
386        args: Vec<Value>,
387        kwargs: Vec<(String, Value)>,
388        label: &str,
389    ) -> DogeResult<Value> {
390        let class = self.classes[class_id as usize].clone();
391        let object = Value::object(class_id, &class.name);
392        match self.resolve_method(class_id, "init") {
393            Some((fn_id, _)) => {
394                self.invoke_method_kw(fn_id, object.clone(), args, kwargs, label)?;
395            }
396            None => {
397                // No init anywhere in the chain: construction takes no arguments.
398                self.bind_args(&dc::Params::default(), args, kwargs, label, class.file_id)?;
399            }
400        }
401        Ok(object)
402    }
403
404    /// Invoke a method template that accepts keyword arguments (a constructor's
405    /// `init`), binding `recv` as `self`.
406    fn invoke_method_kw(
407        &mut self,
408        fn_id: usize,
409        recv: Value,
410        args: Vec<Value>,
411        kwargs: Vec<(String, Value)>,
412        label: &str,
413    ) -> DogeResult<Value> {
414        let callable = self.callables[fn_id].clone();
415        let Callable::User(template) = callable.as_ref() else {
416            unreachable!("interp bug: a method id points at a native");
417        };
418        self.call_user(template, &[], args, kwargs, Some(recv), label)
419    }
420
421    /// A call to a module member: a stdlib function, or a user module's function or
422    /// constructor.
423    #[allow(clippy::too_many_arguments)]
424    fn call_module_member(
425        &mut self,
426        module: crate::ModuleRef,
427        base: &str,
428        member: &str,
429        args: &[dc::Expr],
430        kwargs: &[(String, dc::Expr)],
431        frame: &Scope,
432        fid: u32,
433    ) -> DogeResult<Value> {
434        match module {
435            crate::ModuleRef::Stdlib(m) => {
436                let args = self.eval_positional(args, frame, fid)?;
437                match self
438                    .module_fn_ids
439                    .get(&(m.name.to_string(), member.to_string()))
440                {
441                    Some(id) => self.call_id(*id, Vec::new(), args, Vec::new(), member),
442                    None => Err(DogeError::attr_error(format!(
443                        "{base} has no member {member}"
444                    ))),
445                }
446            }
447            crate::ModuleRef::User(mfid) => {
448                let (args, kwargs) = self.eval_args(args, kwargs, frame, fid)?;
449                if let Some(class_id) = self.class_id_in(mfid, member) {
450                    let label = format!("{base}.{member}");
451                    return self.construct(class_id, args, kwargs, &label);
452                }
453                let value = self
454                    .lookup(&self.globals(mfid), mfid, member)
455                    .map(|c| c.borrow().clone())
456                    .ok_or_else(|| {
457                        DogeError::attr_error(format!("{base} has no member {member}"))
458                    })?;
459                self.call_value(value, args, kwargs)
460            }
461        }
462    }
463}