fuel_abi_types/
utils.rs

1use lazy_static::lazy_static;
2use regex::Regex;
3
4use std::{
5    cmp::min,
6    fmt::{Display, Formatter},
7};
8
9use itertools::{chain, izip, Itertools};
10use proc_macro2::{Ident, Span, TokenStream};
11use quote::{quote, ToTokens};
12
13use crate::error::{error, Result};
14
15/// Expands a identifier string into an token.
16pub fn ident(name: &str) -> Ident {
17    Ident::new(name, Span::call_site())
18}
19
20pub fn safe_ident(name: &str) -> Ident {
21    syn::parse_str::<Ident>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
22}
23
24#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
25pub struct TypePath {
26    parts: Vec<Ident>,
27    is_absolute: bool,
28}
29
30impl Display for TypePath {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        let prefix = if self.is_absolute { "::" } else { "" };
33        let parts_str = self.parts.iter().join("::");
34
35        write!(f, "{prefix}{parts_str}")
36    }
37}
38
39impl From<&Ident> for TypePath {
40    fn from(value: &Ident) -> Self {
41        TypePath::new(value).expect("All Idents are valid TypePaths")
42    }
43}
44
45impl From<Ident> for TypePath {
46    fn from(value: Ident) -> Self {
47        (&value).into()
48    }
49}
50
51impl TypePath {
52    pub fn new<T: ToString>(path: T) -> Result<Self> {
53        let path_str = path.to_string();
54        if path_str.trim().is_empty() {
55            return Ok(Self {
56                parts: vec![],
57                is_absolute: false,
58            });
59        }
60
61        let is_absolute = Self::is_absolute(&path_str);
62
63        let parts = path_str
64            .split("::")
65            .skip(is_absolute as usize)
66            .map(|part| {
67                let trimmed_part = part.trim().to_string();
68                if trimmed_part.is_empty() {
69                    return Err(error!("TypePath cannot be constructed from '{path_str}' since it has it has empty parts"))
70                }
71                Ok(ident(&trimmed_part))
72            })
73            .collect::<Result<Vec<_>>>()?;
74
75        Ok(Self { parts, is_absolute })
76    }
77
78    fn len(&self) -> usize {
79        self.parts.len()
80    }
81
82    fn starts_with(&self, path: &TypePath) -> bool {
83        if self.parts.len() < path.parts.len() {
84            false
85        } else {
86            self.parts[..path.parts.len()] == path.parts
87        }
88    }
89
90    pub fn relative_path_from(&self, path: &TypePath) -> TypePath {
91        let our_parent = self.parent();
92
93        let number_of_consecutively_matching_parts = izip!(&our_parent.parts, &path.parts)
94            .enumerate()
95            .find_map(|(matches_so_far, (our_part, their_part))| {
96                (our_part != their_part).then_some(matches_so_far)
97            })
98            .unwrap_or_else(|| min(our_parent.len(), path.len()));
99
100        let prefix = if our_parent.starts_with(path) {
101            vec![ident("self")]
102        } else {
103            vec![ident("super"); path.len() - number_of_consecutively_matching_parts]
104        };
105
106        let non_matching_path_parts = our_parent
107            .parts
108            .iter()
109            .skip(number_of_consecutively_matching_parts)
110            .cloned();
111
112        let type_ident = self.ident().cloned();
113
114        TypePath {
115            parts: chain!(prefix, non_matching_path_parts, type_ident).collect(),
116            is_absolute: false,
117        }
118    }
119
120    pub fn parent(&self) -> TypePath {
121        let parts = if self.parts.is_empty() {
122            vec![]
123        } else {
124            self.parts[..self.parts.len() - 1].to_vec()
125        };
126
127        TypePath {
128            parts,
129            is_absolute: self.is_absolute,
130        }
131    }
132
133    pub fn take_parts(self) -> Vec<Ident> {
134        self.parts
135    }
136
137    pub fn has_multiple_parts(&self) -> bool {
138        self.parts.len() > 1
139    }
140
141    fn is_absolute(path_str: &str) -> bool {
142        path_str.trim_start().starts_with("::")
143    }
144
145    pub fn prepend(self, mut another: TypePath) -> Self {
146        another.parts.extend(self.parts);
147        another
148    }
149    pub fn append(mut self, another: TypePath) -> Self {
150        self.parts.extend(another.parts);
151        self
152    }
153
154    pub fn ident(&self) -> Option<&Ident> {
155        self.parts.last()
156    }
157}
158
159impl ToTokens for TypePath {
160    fn to_tokens(&self, tokens: &mut TokenStream) {
161        let parts = &self.parts;
162        let leading_delimiter = self.is_absolute.then_some(quote! {::});
163
164        tokens.extend(quote! { #leading_delimiter #(#parts)::* });
165    }
166}
167
168/// Does `type_name` describe a Tuple type?
169///
170/// # Arguments
171///
172/// * `type_name`: `type_name` field from [`TypeDeclaration`]( `crate::program_abi::TypeDeclaration` )
173pub fn has_tuple_format(type_name: &str) -> bool {
174    type_name.starts_with('(') && type_name.ends_with(')')
175}
176
177/// If `type_name` contains a generic parameter, it will be returned.
178///
179/// # Arguments
180///
181/// * `type_name`: `type_name` field from [`TypeDeclaration`]( `crate::program_abi::TypeDeclaration` )
182pub fn extract_generic_name(type_name: &str) -> Option<String> {
183    lazy_static! {
184        static ref RE: Regex = Regex::new(r"^\s*generic\s+(\S+)\s*$").unwrap();
185    }
186    RE.captures(type_name)
187        .map(|captures| String::from(&captures[1]))
188}
189
190/// If `type_name` represents an Array, its size will be returned;
191///
192/// # Arguments
193///
194/// * `type_name`: `type_name` field from [`TypeDeclaration`]( `crate::program_abi::TypeDeclaration` )
195pub fn extract_array_len(type_name: &str) -> Option<usize> {
196    lazy_static! {
197        static ref RE: Regex = Regex::new(r"^\s*\[.+;\s*(\d+)\s*\]\s*$").unwrap();
198    }
199    RE.captures(type_name)
200        .map(|captures| captures[1].to_string())
201        .map(|length: String| {
202            length.parse::<usize>().unwrap_or_else(|_| {
203                panic!("Could not extract array length from {length}! Original field {type_name}")
204            })
205        })
206}
207
208/// If `type_name` represents a string, its size will be returned;
209///
210/// # Arguments
211///
212/// * `type_name`: `type_name` field from [`TypeDeclaration`]( `crate::program_abi::TypeDeclaration` )
213pub fn extract_str_len(type_name: &str) -> Option<usize> {
214    lazy_static! {
215        static ref RE: Regex = Regex::new(r"^\s*str\s*\[\s*(\d+)\s*\]\s*$").unwrap();
216    }
217    RE.captures(type_name)
218        .map(|captures| captures[1].to_string())
219        .map(|length: String| {
220            length.parse::<usize>().unwrap_or_else(|_| {
221                panic!(
222                    "Could not extract string length from {length}! Original field '{type_name}'"
223                )
224            })
225        })
226}
227
228/// If `type_name` represents a custom type, its name will be returned.
229///
230/// # Arguments
231///
232/// * `type_name`: `type_name` field from [`TypeDeclaration`]( `crate::program_abi::TypeDeclaration` )
233pub fn extract_custom_type_name(type_field: &str) -> Option<String> {
234    lazy_static! {
235        static ref RE: Regex = Regex::new(r"\s*(?:struct|enum)\s*(\S*)").unwrap();
236    }
237
238    RE.captures(type_field)
239        .map(|captures| String::from(&captures[1]))
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn can_be_empty() {
248        let empty_path = "   ";
249
250        let type_path = TypePath::new(empty_path).unwrap();
251
252        assert!(type_path.take_parts().is_empty());
253    }
254
255    #[test]
256    fn must_have_ident_at_end() {
257        let no_ident = "  ::missing_ident:: ";
258
259        let err = TypePath::new(no_ident).expect_err("Should have failed!");
260
261        assert_eq!(
262            err.to_string(),
263            "TypePath cannot be constructed from '  ::missing_ident:: ' since it has it has empty parts"
264        );
265    }
266
267    #[test]
268    fn trims_whitespace() {
269        let path = " some_mod :: ident ";
270
271        let path = TypePath::new(path).expect("Should have passed.");
272
273        assert_eq!(path.parts, vec!["some_mod", "ident"])
274    }
275
276    #[test]
277    fn can_be_prepended_to() {
278        let path = TypePath::new(" some_mod :: ident ").expect("Should have passed.");
279        let another_path = TypePath::new(" something :: else ").expect("the type path is valid");
280
281        let joined = path.prepend(another_path);
282
283        assert_eq!(joined.parts, vec!["something", "else", "some_mod", "ident"])
284    }
285
286    #[test]
287    fn can_handle_absolute_paths() {
288        let absolute_path = " ::std :: vec:: Vec";
289
290        let type_path = TypePath::new(absolute_path);
291
292        type_path.unwrap();
293    }
294
295    #[test]
296    fn leading_delimiter_present_when_path_is_absolute() {
297        let type_path = TypePath::new(" ::std :: vec:: Vec").unwrap();
298
299        let tokens = type_path.to_token_stream();
300
301        let expected = quote! {::std::vec::Vec};
302        assert_eq!(expected.to_string(), tokens.to_string())
303    }
304
305    #[test]
306    fn leading_delimiter_not_present_when_path_is_relative() {
307        let type_path = TypePath::new(" std :: vec:: Vec").unwrap();
308
309        let tokens = type_path.to_token_stream();
310
311        let expected = quote! {std::vec::Vec};
312        assert_eq!(expected.to_string(), tokens.to_string())
313    }
314
315    #[test]
316    fn path_with_two_or_more_parts_has_a_parent() {
317        let type_path = TypePath::new(":: std::Type").unwrap();
318
319        let parent = type_path.parent();
320
321        let expected_parent = TypePath::new("::std").unwrap();
322        assert_eq!(parent, expected_parent)
323    }
324
325    #[test]
326    fn path_with_only_one_part_has_empty_parent() {
327        let type_path = TypePath::new(":: std").unwrap();
328
329        let parent = type_path.parent();
330
331        assert!(parent.take_parts().is_empty());
332    }
333
334    #[test]
335    fn relative_path_from_same_mod() {
336        let deeper_path = TypePath::new("a::b::SomeType").unwrap();
337        let the_same_mod = TypePath::new("a::b").unwrap();
338
339        let relative_path = deeper_path.relative_path_from(&the_same_mod);
340
341        let expected_relative_path = TypePath::new("self::SomeType").unwrap();
342        assert_eq!(relative_path, expected_relative_path);
343    }
344
345    #[test]
346    fn relative_path_from_root_mod() {
347        let deeper_path = TypePath::new("SomeType").unwrap();
348        let root_mod = TypePath::new("").unwrap();
349
350        let relative_path = deeper_path.relative_path_from(&root_mod);
351
352        let expected_relative_path = TypePath::new("self::SomeType").unwrap();
353        assert_eq!(relative_path, expected_relative_path);
354    }
355
356    #[test]
357    fn relative_path_from_deeper_mod() {
358        let a_path = TypePath::new("a::b::SomeType").unwrap();
359        let deeper_mod = TypePath::new("a::b::c::d").unwrap();
360
361        let relative_path = a_path.relative_path_from(&deeper_mod);
362
363        let expected_relative_path = TypePath::new("super::super::SomeType").unwrap();
364        assert_eq!(relative_path, expected_relative_path);
365    }
366
367    #[test]
368    fn relative_path_going_deeper() {
369        let a_path = TypePath::new("a::b::c::SomeType").unwrap();
370        let higher_level_mod = TypePath::new("a").unwrap();
371
372        let relative_path = a_path.relative_path_from(&higher_level_mod);
373
374        let expected_relative_path = TypePath::new("self::b::c::SomeType").unwrap();
375        assert_eq!(relative_path, expected_relative_path);
376    }
377
378    #[test]
379    fn relative_path_up_then_down() {
380        let a_path = TypePath::new("a::b::c::SomeType").unwrap();
381        let sister_path = TypePath::new("d::e").unwrap();
382
383        let relative_path = a_path.relative_path_from(&sister_path);
384
385        let expected_relative_path = TypePath::new("super::super::a::b::c::SomeType").unwrap();
386        assert_eq!(relative_path, expected_relative_path);
387    }
388
389    #[test]
390    fn path_starts_with_another() {
391        let a_path = TypePath::new("a::b::c::d").unwrap();
392        let prefix = TypePath::new("a::b").unwrap();
393
394        assert!(a_path.starts_with(&prefix));
395    }
396    #[test]
397    fn path_does_not_start_with_another() {
398        let a_path = TypePath::new("a::b::c::d").unwrap();
399        let prefix = TypePath::new("c::d").unwrap();
400
401        assert!(!a_path.starts_with(&prefix));
402    }
403
404    #[test]
405    fn start_with_size_guard() {
406        let a_path = TypePath::new("a::b::c").unwrap();
407        let prefix = TypePath::new("a::b::c::d").unwrap();
408
409        assert!(!a_path.starts_with(&prefix));
410    }
411
412    #[test]
413    fn tuples_start_and_end_with_round_brackets() {
414        assert!(has_tuple_format("(_, _)"));
415
416        assert!(!has_tuple_format("(.."));
417
418        assert!(!has_tuple_format("..)"));
419    }
420
421    #[test]
422    fn generic_name_extracted() {
423        let type_name = "    generic     T    ";
424
425        let name = extract_generic_name(type_name).expect("Should have succeeded");
426
427        assert_eq!(name, "T");
428    }
429
430    #[test]
431    fn array_len_extracted() {
432        let type_name = "  [  _  ;  8   ]  ";
433
434        let size = extract_array_len(type_name).expect("Should have succeeded");
435
436        assert_eq!(size, 8);
437    }
438
439    #[test]
440    fn str_len_extracted() {
441        let type_name = "  str [ 10  ] ";
442
443        let str_len = extract_str_len(type_name).expect("Should have succeeded");
444
445        assert_eq!(str_len, 10);
446    }
447
448    #[test]
449    fn custom_struct_type_name_extracted() {
450        let type_name = "  struct   SomeStruct ";
451
452        let struct_name = extract_custom_type_name(type_name).expect("Should have succeeded");
453
454        assert_eq!(struct_name, "SomeStruct");
455    }
456
457    #[test]
458    fn custom_enum_type_name_extracted() {
459        let type_name = "  enum   SomeEnum ";
460
461        let enum_name = extract_custom_type_name(type_name).expect("Should have succeeded");
462
463        assert_eq!(enum_name, "SomeEnum");
464    }
465}