harn_parser/builtin_methods/
mod.rs1mod reference;
11mod table;
12
13use crate::ast::{Node, SNode};
14
15pub use harn_builtin_meta::{
16 MethodDescriptor, MethodEffect, MethodMigration, MethodReceiver, RemovedMethod,
17};
18pub use reference::{
19 render_reference, splice_reference, REFERENCE_BEGIN_MARKER, REFERENCE_END_MARKER,
20};
21pub use table::{BUILTIN_METHODS, REMOVED_METHODS};
22
23pub fn method(receiver: MethodReceiver, name: &str) -> Option<&'static MethodDescriptor> {
25 BUILTIN_METHODS
26 .iter()
27 .find(|row| row.receiver == receiver && row.name() == name)
28}
29
30pub fn methods_of(receiver: MethodReceiver) -> impl Iterator<Item = &'static MethodDescriptor> {
32 BUILTIN_METHODS
33 .iter()
34 .filter(move |row| row.receiver == receiver)
35}
36
37pub fn method_names(receiver: MethodReceiver) -> Vec<&'static str> {
39 methods_of(receiver).map(MethodDescriptor::name).collect()
40}
41
42pub fn is_method_name(name: &str) -> bool {
44 BUILTIN_METHODS.iter().any(|row| row.name() == name)
45}
46
47pub fn removed_method(receiver: MethodReceiver, name: &str) -> Option<&'static RemovedMethod> {
49 REMOVED_METHODS
50 .iter()
51 .find(|row| row.receiver == receiver && row.name == name)
52}
53
54const HOST_VERBS: &[&str] = &["delete"];
58
59pub fn removed_method_on_any_receiver(name: &str) -> Vec<&'static RemovedMethod> {
63 if is_method_name(name) || HOST_VERBS.contains(&name) {
64 return Vec::new();
65 }
66 REMOVED_METHODS
67 .iter()
68 .filter(|row| row.name == name)
69 .collect()
70}
71
72pub const COPY_COUNTERPARTS: &[(&str, &str)] = &[
75 ("sort", "sorted"),
76 ("sort_by", "sorted_by"),
77 ("reverse", "reversed"),
78];
79
80pub fn copy_counterpart(mutating: &str) -> Option<&'static str> {
82 COPY_COUNTERPARTS
83 .iter()
84 .find(|(name, _)| *name == mutating)
85 .map(|(_, copy)| *copy)
86}
87
88pub fn in_place_counterpart(copy: &str) -> Option<&'static str> {
90 COPY_COUNTERPARTS
91 .iter()
92 .find(|(_, name)| *name == copy)
93 .map(|(mutating, _)| *mutating)
94}
95
96pub fn returns_generic(row: &MethodDescriptor) -> bool {
99 fn mentions(ty: &harn_builtin_meta::Ty, params: &[&str]) -> bool {
100 use harn_builtin_meta::Ty;
101 match ty {
102 Ty::Generic(name) | Ty::Named(name) => params.contains(name),
103 Ty::Optional(inner) => mentions(inner, params),
104 Ty::Apply(_, args) | Ty::Union(args) => args.iter().any(|t| mentions(t, params)),
105 Ty::Fn(args, ret) => args.iter().any(|t| mentions(t, params)) || mentions(ret, params),
106 Ty::Shape(fields) => fields.iter().any(|f| mentions(&f.ty, params)),
107 Ty::OpenShape(fields, rests) => {
108 fields.iter().any(|f| mentions(&f.ty, params))
109 || rests.iter().any(|t| mentions(t, params))
110 }
111 Ty::SchemaOf(name) => params.contains(name),
112 Ty::Any | Ty::Never | Ty::LitInt(_) | Ty::LitString(_) => false,
113 }
114 }
115 mentions(&row.signature.returns, row.signature.type_params)
116}
117
118pub fn is_mutating_method_name(name: &str) -> bool {
120 BUILTIN_METHODS
121 .iter()
122 .any(|row| row.is_mutating() && row.name() == name)
123}
124
125pub fn is_pure_on_every_receiver(name: &str) -> bool {
129 is_method_name(name) && !is_mutating_method_name(name)
130}
131
132pub fn is_mutable_place(node: &SNode) -> bool {
135 place_root(node).is_some()
136}
137
138pub fn place_root(node: &SNode) -> Option<&str> {
140 match &node.node {
141 Node::Identifier(name) => Some(name),
142 Node::PropertyAccess { object, .. } | Node::SubscriptAccess { object, .. } => {
143 place_root(object)
144 }
145 _ => None,
146 }
147}
148
149pub fn mutating_call_place(node: &SNode) -> Option<&SNode> {
154 match &node.node {
155 Node::MethodCall { object, method, .. } if is_mutating_method_name(method) => {
156 is_mutable_place(object).then_some(&**object)
157 }
158 _ => None,
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn method_names_are_unique_per_receiver() {
168 for receiver in MethodReceiver::ALL {
169 let mut names = method_names(receiver);
170 let total = names.len();
171 names.sort_unstable();
172 names.dedup();
173 assert_eq!(names.len(), total, "{receiver:?} lists a method twice");
174 }
175 }
176
177 #[test]
178 fn removed_spellings_are_not_live_on_their_receiver() {
179 for row in REMOVED_METHODS {
180 assert!(
181 method(row.receiver, row.name).is_none(),
182 "`{}` is both removed and live on {:?}",
183 row.name,
184 row.receiver
185 );
186 if let MethodMigration::Rename(to) = row.migration {
187 assert!(
188 method(row.receiver, to).is_some(),
189 "`{}` on {:?} renames to `{to}`, which does not exist",
190 row.name,
191 row.receiver
192 );
193 }
194 }
195 }
196
197 #[test]
198 fn receiver_generics_are_declared_by_the_signatures_that_use_them() {
199 for row in BUILTIN_METHODS {
200 for param in row.receiver.receiver_type_params() {
201 let rendered = row.signature.to_string();
202 let used = rendered
203 .split(|c: char| !c.is_alphanumeric() && c != '_')
204 .skip(1)
205 .any(|token| token == *param);
206 if used {
207 assert!(
208 row.signature.is_type_param(param),
209 "{:?}.{} uses `{param}` without declaring it: {rendered}",
210 row.receiver,
211 row.name()
212 );
213 }
214 }
215 }
216 }
217
218 #[test]
219 fn copy_counterparts_pair_a_mutating_and_a_pure_list_method() {
220 for (mutating, copy) in COPY_COUNTERPARTS {
221 assert!(method(MethodReceiver::List, mutating).is_some_and(|row| row.is_mutating()));
222 assert!(method(MethodReceiver::List, copy).is_some_and(|row| !row.is_mutating()));
223 }
224 }
225
226 #[test]
227 fn retired_spellings_only_flag_unknown_receivers_when_unambiguous() {
228 assert_eq!(removed_method_on_any_receiver("to_lower").len(), 1);
229 assert_eq!(removed_method_on_any_receiver("removing").len(), 2);
230 assert!(removed_method_on_any_receiver("count").is_empty());
233 assert!(removed_method_on_any_receiver("reverse").is_empty());
234 assert!(removed_method_on_any_receiver("delete").is_empty());
236 }
237}