Skip to main content

harn_builtin_meta/
methods.rs

1//! Descriptor shapes for builtin value methods (`xs.len()`, `s.lower()`,
2//! `xs.push(v)`).
3//!
4//! The method table itself lives in `harn-parser` (`builtin_methods`), which
5//! builds each row's [`BuiltinSignature`] with the same sig grammar
6//! `#[harn_builtin]` uses. Only the shapes live here so every consumer can
7//! name them without depending on the parser.
8
9use crate::BuiltinSignature;
10
11/// The builtin value kind a method is dispatched on.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub enum MethodReceiver {
14    String,
15    List,
16    Set,
17    Dict,
18    Range,
19    Iter,
20    Generator,
21}
22
23impl MethodReceiver {
24    pub const ALL: [MethodReceiver; 7] = [
25        MethodReceiver::String,
26        MethodReceiver::List,
27        MethodReceiver::Set,
28        MethodReceiver::Dict,
29        MethodReceiver::Range,
30        MethodReceiver::Iter,
31        MethodReceiver::Generator,
32    ];
33
34    /// The runtime type name (`type_of`) of the receiver.
35    pub const fn type_name(self) -> &'static str {
36        match self {
37            MethodReceiver::String => "string",
38            MethodReceiver::List => "list",
39            MethodReceiver::Set => "set",
40            MethodReceiver::Dict => "dict",
41            MethodReceiver::Range => "range",
42            MethodReceiver::Iter => "iter",
43            MethodReceiver::Generator => "generator",
44        }
45    }
46
47    /// Generic parameter names that a method signature binds from the
48    /// receiver itself rather than from its arguments: the element type of a
49    /// list, set, iter, or generator, and the key/value types of a dict.
50    pub const fn receiver_type_params(self) -> &'static [&'static str] {
51        match self {
52            MethodReceiver::List
53            | MethodReceiver::Set
54            | MethodReceiver::Iter
55            | MethodReceiver::Generator => &["T"],
56            MethodReceiver::Dict => &["K", "V"],
57            MethodReceiver::String | MethodReceiver::Range => &[],
58        }
59    }
60}
61
62/// Whether calling the method changes its receiver.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum MethodEffect {
65    /// Returns a value and leaves the receiver untouched. Discarding the
66    /// result of a pure method is always a bug.
67    Pure,
68    /// Swift-style `mutating`: the receiver must be a mutable place (a `let`
69    /// binding or a property/subscript path rooted at one), and the call
70    /// rewrites that place with the updated value before returning.
71    Mutating,
72}
73
74/// One builtin value method: the single owner of its name, signature, effect,
75/// and reference documentation.
76#[derive(Debug, Clone, Copy)]
77pub struct MethodDescriptor {
78    pub receiver: MethodReceiver,
79    /// `signature.name` is the method name. Generic parameters listed in
80    /// [`MethodReceiver::receiver_type_params`] are bound from the receiver.
81    pub signature: BuiltinSignature,
82    pub effect: MethodEffect,
83    pub doc: &'static str,
84}
85
86impl MethodDescriptor {
87    pub const fn pure(
88        receiver: MethodReceiver,
89        signature: BuiltinSignature,
90        doc: &'static str,
91    ) -> Self {
92        Self {
93            receiver,
94            signature,
95            effect: MethodEffect::Pure,
96            doc,
97        }
98    }
99
100    pub const fn mutating(
101        receiver: MethodReceiver,
102        signature: BuiltinSignature,
103        doc: &'static str,
104    ) -> Self {
105        Self {
106            receiver,
107            signature,
108            effect: MethodEffect::Mutating,
109            doc,
110        }
111    }
112
113    pub const fn name(&self) -> &'static str {
114        self.signature.name
115    }
116
117    pub const fn is_mutating(&self) -> bool {
118        matches!(self.effect, MethodEffect::Mutating)
119    }
120}
121
122/// How source written against a removed method spelling is repaired.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum MethodMigration {
125    /// Same operation under its canonical name; a mechanical rename.
126    Rename(&'static str),
127    /// `xs = xs.appending(v)` becomes `xs.push(v)`; any other use becomes
128    /// `xs + [v]`.
129    AppendingToPush,
130    /// `s = s.adding(v)` becomes the mutating call `s.add(v)`; any other use
131    /// needs the guidance's copy-returning spelling.
132    MutatingWhenSelfAssigned {
133        mutating: &'static str,
134        guidance: &'static str,
135    },
136    /// No mechanical equivalent; the guidance names the replacement.
137    Manual(&'static str),
138}
139
140/// A method spelling that no longer exists on a receiver.
141#[derive(Debug, Clone, Copy)]
142pub struct RemovedMethod {
143    pub receiver: MethodReceiver,
144    pub name: &'static str,
145    pub migration: MethodMigration,
146}
147
148impl RemovedMethod {
149    pub const fn renamed(receiver: MethodReceiver, name: &'static str, to: &'static str) -> Self {
150        Self {
151            receiver,
152            name,
153            migration: MethodMigration::Rename(to),
154        }
155    }
156
157    pub const fn mutating_when_self_assigned(
158        receiver: MethodReceiver,
159        name: &'static str,
160        mutating: &'static str,
161        guidance: &'static str,
162    ) -> Self {
163        Self {
164            receiver,
165            name,
166            migration: MethodMigration::MutatingWhenSelfAssigned { mutating, guidance },
167        }
168    }
169
170    pub const fn manual(
171        receiver: MethodReceiver,
172        name: &'static str,
173        guidance: &'static str,
174    ) -> Self {
175        Self {
176            receiver,
177            name,
178            migration: MethodMigration::Manual(guidance),
179        }
180    }
181
182    /// One-line repair text for diagnostics and runtime errors.
183    pub fn repair_hint(&self) -> String {
184        match self.migration {
185            MethodMigration::Rename(to) => format!("did you mean `{to}`?"),
186            MethodMigration::AppendingToPush => {
187                "use `xs.push(v)` to append in place, or `xs + [v]` for a new list".to_string()
188            }
189            MethodMigration::MutatingWhenSelfAssigned { guidance, .. }
190            | MethodMigration::Manual(guidance) => guidance.to_string(),
191        }
192    }
193}