doge_runtime/methods/mod.rs
1//! Built-in method dispatch for collection values — the counterpart of
2//! [`crate::objects`] for `many` instances. A method call on a non-`Object`
3//! receiver (`xs.append(1)`, `d.keys()`) routes here; the generated `call_method`
4//! dispatcher handles `Object` receivers itself and forwards everything else.
5//!
6//! Behaviour lives here, not in codegen (generated Rust is thin glue): each
7//! method mutates or reads the value's shared cell and returns a `Value`, and
8//! every failure is a catchable [`DogeError`], never a panic.
9
10use std::rc::Rc;
11
12use crate::error::{DogeError, DogeResult};
13use crate::objects::method_arity_error;
14use crate::value::Value;
15
16mod dict;
17mod list;
18#[cfg(test)]
19mod tests;
20
21use dict::dict_method;
22use list::list_method;
23
24/// Dispatch a method call on a List or Dict. `recv` is the receiver, `name` the
25/// method, `args` the already-evaluated arguments. A non-collection receiver, an
26/// unknown method, or a wrong argument count is a catchable error.
27pub fn builtin_method(recv: &Value, name: &str, args: Vec<Value>) -> DogeResult {
28 match recv {
29 Value::List(_) => list_method(recv, name, args),
30 Value::Dict(_) => dict_method(recv, name, args),
31 // The dispatcher routes objects to its own class match, never here; this
32 // defensive branch names the class rather than claiming "no methods".
33 Value::Object(_) => Err(crate::objects::no_such_method(recv, name)),
34 // No other value has methods at all. Listed by variant rather than a
35 // wildcard, so a new Value variant with methods forces a decision here.
36 Value::Int(_)
37 | Value::Float(_)
38 | Value::Str(_)
39 | Value::Bool(_)
40 | Value::None
41 | Value::Function(_)
42 | Value::Class(_)
43 | Value::BoundMethod(_)
44 | Value::Error(_)
45 | Value::Socket(_)
46 | Value::Pup(_)
47 | Value::Bowl(_) => Err(crate::objects::no_methods_error(recv)),
48 }
49}
50
51/// Whether a List or Dict has a built-in method named `name` — the gate a bound
52/// method read (`such f = xs.append`) checks before capturing the receiver. The
53/// method-name sets live beside each collection's dispatch (`list::LIST_METHODS`,
54/// `dict::DICT_METHODS`), so binding and dispatch never disagree.
55pub fn has_builtin_method(recv: &Value, name: &str) -> bool {
56 match recv {
57 Value::List(_) => list::LIST_METHODS.contains(&name),
58 Value::Dict(_) => dict::DICT_METHODS.contains(&name),
59 _ => false,
60 }
61}
62
63/// The arity gate every method runs first: reuses the object-method wording so a
64/// List/Dict arity error reads exactly like `Shibe.speak takes 1 argument, got 0`.
65pub(super) fn check_arity(
66 class: &str,
67 method: &str,
68 expected: usize,
69 got: usize,
70) -> DogeResult<()> {
71 if got == expected {
72 Ok(())
73 } else {
74 Err(method_arity_error(
75 class,
76 method,
77 expected,
78 Some(expected),
79 got,
80 ))
81 }
82}
83
84/// Take an argument that must be an Int, or raise the standard type error naming
85/// the method and what it got. `what` is the argument's role, e.g. `List.insert
86/// needs an Int index`.
87pub(super) fn expect_int(value: Value, what: &str) -> DogeResult<i64> {
88 match value {
89 Value::Int(n) => Ok(n),
90 other => Err(DogeError::type_error(format!(
91 "{what}, got {}",
92 other.describe()
93 ))),
94 }
95}
96
97/// Take an argument that must be a Str, or raise the standard type error naming
98/// the method and what it got.
99pub(super) fn expect_str(value: Value, what: &str) -> DogeResult<Rc<str>> {
100 match value {
101 Value::Str(s) => Ok(s),
102 other => Err(DogeError::type_error(format!(
103 "{what}, got {}",
104 other.describe()
105 ))),
106 }
107}