syn_utils/
type_parser.rs

1use std::rc::Rc;
2
3use crate::*;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct Ref {
7  pub lifetime: Option<Lifetime>,
8  pub kind: RefKind,
9}
10
11impl ToTokens for Ref {
12  fn to_tokens(&self, tokens: &mut TokenStream2) {
13    let lifetime = &self.lifetime;
14    let output = match &self.kind {
15      RefKind::Ref => quote! { & #lifetime },
16      RefKind::MutRef => quote! { & #lifetime mut },
17    };
18
19    tokens.extend(output);
20  }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct TypeInfo {
25  pub reference: Option<Ref>,
26  pub type_: Rc<RustType>,
27}
28
29#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
30pub enum RefKind {
31  Ref,
32  MutRef,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Array {
37  pub len: Expr,
38  pub inner: Rc<TypeInfo>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub enum RustType {
43  Slice(Rc<TypeInfo>),
44  Array(Rc<Array>),
45  Tuple(Rc<[TypeInfo]>),
46  Option(Rc<TypeInfo>),
47  Box(Rc<TypeInfo>),
48  Vec(Rc<TypeInfo>),
49  HashMap((Rc<TypeInfo>, Rc<TypeInfo>)),
50  Other(Rc<TypePath>),
51}
52
53impl RustType {
54  /// Returns `true` if the rust type is [`Slice`].
55  ///
56  /// [`Slice`]: RustType::Slice
57  #[must_use]
58  pub fn is_slice(&self) -> bool {
59    matches!(self, Self::Slice(..))
60  }
61
62  /// Returns `true` if the rust type is [`Array`].
63  ///
64  /// [`Array`]: RustType::Array
65  #[must_use]
66  pub fn is_array(&self) -> bool {
67    matches!(self, Self::Array { .. })
68  }
69
70  /// Returns `true` if the rust type is [`Tuple`].
71  ///
72  /// [`Tuple`]: RustType::Tuple
73  #[must_use]
74  pub fn is_tuple(&self) -> bool {
75    matches!(self, Self::Tuple(..))
76  }
77
78  /// Returns `true` if the rust type is [`Option`].
79  ///
80  /// [`Option`]: RustType::Option
81  #[must_use]
82  pub fn is_option(&self) -> bool {
83    matches!(self, Self::Option(..))
84  }
85
86  /// Returns `true` if the rust type is [`Box`].
87  ///
88  /// [`Box`]: RustType::Box
89  #[must_use]
90  pub fn is_box(&self) -> bool {
91    matches!(self, Self::Box(..))
92  }
93
94  /// Returns `true` if the rust type is [`Vec`].
95  ///
96  /// [`Vec`]: RustType::Vec
97  #[must_use]
98  pub fn is_vec(&self) -> bool {
99    matches!(self, Self::Vec(..))
100  }
101
102  /// Returns `true` if the rust type is [`HashMap`].
103  ///
104  /// [`HashMap`]: RustType::HashMap
105  #[must_use]
106  pub fn is_hash_map(&self) -> bool {
107    matches!(self, Self::HashMap(..))
108  }
109
110  /// Returns `true` if the rust type is [`Other`].
111  ///
112  /// [`Other`]: RustType::Other
113  #[must_use]
114  pub fn is_other(&self) -> bool {
115    matches!(self, Self::Other(..))
116  }
117
118  pub fn as_option(&self) -> Option<&TypeInfo> {
119    if let Self::Option(v) = self {
120      Some(v)
121    } else {
122      None
123    }
124  }
125
126  pub fn as_slice(&self) -> Option<&TypeInfo> {
127    if let Self::Slice(v) = self {
128      Some(v)
129    } else {
130      None
131    }
132  }
133
134  pub fn as_tuple(&self) -> Option<&[TypeInfo]> {
135    if let Self::Tuple(v) = self {
136      Some(v.as_ref())
137    } else {
138      None
139    }
140  }
141
142  pub fn as_box(&self) -> Option<&TypeInfo> {
143    if let Self::Box(v) = self {
144      Some(v)
145    } else {
146      None
147    }
148  }
149
150  pub fn as_vec(&self) -> Option<&TypeInfo> {
151    if let Self::Vec(v) = self {
152      Some(v)
153    } else {
154      None
155    }
156  }
157
158  pub fn as_hash_map(&self) -> Option<&(Rc<TypeInfo>, Rc<TypeInfo>)> {
159    if let Self::HashMap(v) = self {
160      Some(v)
161    } else {
162      None
163    }
164  }
165
166  pub fn as_other(&self) -> Option<&TypePath> {
167    if let Self::Other(v) = self {
168      Some(v)
169    } else {
170      None
171    }
172  }
173
174  pub fn as_array(&self) -> Option<&Array> {
175    if let Self::Array(v) = self {
176      Some(v)
177    } else {
178      None
179    }
180  }
181}
182
183impl ToTokens for TypeInfo {
184  fn to_tokens(&self, tokens: &mut TokenStream2) {
185    let ref_tokens = &self.reference;
186    let type_tokens = &self.type_;
187
188    tokens.extend(quote! {
189      #ref_tokens #type_tokens
190    });
191  }
192}
193
194impl ToTokens for RustType {
195  fn to_tokens(&self, tokens: &mut TokenStream2) {
196    let output = match self {
197      RustType::Slice(ty) => quote! { [#ty] },
198      RustType::Array(array) => {
199        let Array { len, inner } = array.as_ref();
200        quote! { [#inner; #len] }
201      }
202      RustType::Tuple(types) => quote! { (#(#types),*) },
203      RustType::Option(ty) => quote! { ::core::option::Option<#ty> },
204      RustType::Box(ty) => quote! { Box<#ty> },
205      RustType::Vec(ty) => quote! { Vec<#ty> },
206      RustType::HashMap((k, v)) => quote! { HashMap<#k, #v> },
207      RustType::Other(path) => quote! { #path },
208    };
209
210    tokens.extend(output);
211  }
212}
213
214impl From<TypeInfo> for Type {
215  fn from(value: TypeInfo) -> Self {
216    parse_quote!(#value)
217  }
218}
219
220impl TypeInfo {
221  pub fn as_type(&self) -> Type {
222    parse_quote!(#self)
223  }
224
225  pub fn is_mut_ref(&self) -> bool {
226    self
227      .reference
228      .as_ref()
229      .is_some_and(|r| matches!(r.kind, RefKind::MutRef))
230  }
231
232  pub fn is_ref(&self) -> bool {
233    self
234      .reference
235      .as_ref()
236      .is_some_and(|r| matches!(r.kind, RefKind::Ref))
237  }
238
239  pub fn is_owned(&self) -> bool {
240    self.reference.is_none()
241  }
242
243  pub fn from_type(typ: &Type) -> syn::Result<Self> {
244    if let Type::Reference(reference) = typ {
245      let ownership = if reference.mutability.is_some() {
246        RefKind::MutRef
247      } else {
248        RefKind::Ref
249      };
250
251      if let Type::Slice(slice) = &*reference.elem {
252        return Ok(Self {
253          reference: Some(Ref {
254            lifetime: reference.lifetime.clone(),
255            kind: ownership,
256          }),
257          type_: RustType::Slice(Self::from_type(&slice.elem)?.into()).into(),
258        });
259      } else {
260        let mut ref_type = Self::from_type(&reference.elem)?;
261        ref_type.reference = Some(Ref {
262          lifetime: reference.lifetime.clone(),
263          kind: ownership,
264        });
265
266        return Ok(ref_type);
267      }
268    }
269
270    let output = match typ {
271      Type::Slice(slice) => {
272        let inner = Self::from_type(&slice.elem)?;
273
274        Self {
275          reference: None,
276          type_: RustType::Slice(inner.into()).into(),
277        }
278      }
279      Type::Array(TypeArray { elem, len, .. }) => {
280        let inner = Self::from_type(elem)?;
281
282        Self {
283          reference: None,
284          type_: RustType::Array(
285            Array {
286              len: len.clone(),
287              inner: inner.into(),
288            }
289            .into(),
290          )
291          .into(),
292        }
293      }
294      Type::Path(path) => {
295        let last_segment = path.path.last_segment();
296
297        let last_segment_ident = last_segment.ident.to_string();
298
299        match last_segment_ident.as_str() {
300          "HashMap" => {
301            let (k, v) = last_segment.first_two_generics().unwrap();
302
303            Self {
304              reference: None,
305              type_: RustType::HashMap((
306                Self::from_type(k.as_type()?)?.into(),
307                Self::from_type(v.as_type()?)?.into(),
308              ))
309              .into(),
310            }
311          }
312          "Box" => {
313            let inner = last_segment.first_generic().unwrap();
314
315            Self {
316              reference: None,
317
318              type_: RustType::Box(Self::from_type(inner.as_type()?)?.into()).into(),
319            }
320          }
321          "Vec" => {
322            let inner = last_segment.first_generic().unwrap();
323
324            Self {
325              reference: None,
326
327              type_: RustType::Vec(Self::from_type(inner.as_type()?)?.into()).into(),
328            }
329          }
330          "Option" => {
331            let inner = last_segment.first_generic().unwrap();
332
333            Self {
334              reference: None,
335
336              type_: RustType::Option(Self::from_type(inner.as_type()?)?.into()).into(),
337            }
338          }
339          _ => Self {
340            reference: None,
341
342            type_: RustType::Other(path.clone().into()).into(),
343          },
344        }
345      }
346      Type::Tuple(tuple) => {
347        let types: Vec<Self> = tuple
348          .elems
349          .iter()
350          .map(Self::from_type)
351          .collect::<syn::Result<Vec<Self>>>()?;
352
353        let type_enum = RustType::Tuple(types.into());
354
355        Self {
356          reference: None,
357          type_: type_enum.into(),
358        }
359      }
360
361      _ => bail!(
362        typ,
363        "Unsupported type {}",
364        typ.to_token_stream().to_string()
365      ),
366    };
367
368    Ok(output)
369  }
370
371  /// Returns `true` if the rust type is [`Slice`].
372  ///
373  /// [`Slice`]: RustType::Slice
374  #[must_use]
375  pub fn is_slice(&self) -> bool {
376    matches!(*self.type_, RustType::Slice(..))
377  }
378
379  /// Returns `true` if the rust type is [`Array`].
380  ///
381  /// [`Array`]: RustType::Array
382  #[must_use]
383  pub fn is_array(&self) -> bool {
384    matches!(*self.type_, RustType::Array { .. })
385  }
386
387  /// Returns `true` if the rust type is [`Tuple`].
388  ///
389  /// [`Tuple`]: RustType::Tuple
390  #[must_use]
391  pub fn is_tuple(&self) -> bool {
392    matches!(*self.type_, RustType::Tuple(..))
393  }
394
395  /// Returns `true` if the rust type is [`Option`].
396  ///
397  /// [`Option`]: RustType::Option
398  #[must_use]
399  pub fn is_option(&self) -> bool {
400    matches!(*self.type_, RustType::Option(..))
401  }
402
403  /// Returns `true` if the rust type is [`Box`].
404  ///
405  /// [`Box`]: RustType::Box
406  #[must_use]
407  pub fn is_box(&self) -> bool {
408    matches!(*self.type_, RustType::Box(..))
409  }
410
411  /// Returns `true` if the rust type is [`Vec`].
412  ///
413  /// [`Vec`]: RustType::Vec
414  #[must_use]
415  pub fn is_vec(&self) -> bool {
416    matches!(*self.type_, RustType::Vec(..))
417  }
418
419  /// Returns `true` if the rust type is [`HashMap`].
420  ///
421  /// [`HashMap`]: RustType::HashMap
422  #[must_use]
423  pub fn is_hash_map(&self) -> bool {
424    matches!(*self.type_, RustType::HashMap(..))
425  }
426
427  /// Returns `true` if the rust type is [`Other`].
428  ///
429  /// [`Other`]: RustType::Other
430  #[must_use]
431  pub fn is_other(&self) -> bool {
432    matches!(*self.type_, RustType::Other(..))
433  }
434}
435
436#[cfg(test)]
437mod tests {
438  use quote::ToTokens;
439  use syn::Type;
440
441  use super::*;
442
443  fn assert_round_trip(input_str: &str) {
444    let original_type: Type = syn::parse_str(input_str).expect("Invalid Rust syntax in test");
445
446    let info = TypeInfo::from_type(&original_type).unwrap();
447
448    let tokens = info.to_token_stream();
449
450    let output_str = tokens.to_string();
451
452    let normalized_input = quote!(#original_type).to_string();
453
454    assert_eq!(
455      output_str, normalized_input,
456      "Round trip failed for: {}",
457      input_str
458    );
459  }
460
461  fn assert_is_option(ty: &str) {
462    let original: Type = syn::parse_str(ty).unwrap();
463    let info = TypeInfo::from_type(&original).unwrap();
464
465    if let RustType::Option(_) = *info.type_ {
466      // OK
467    } else {
468      panic!("Expected Option, got {:?}", info.type_);
469    }
470  }
471
472  #[test]
473  fn test_primitives() {
474    assert_round_trip("i32");
475    assert_round_trip("String");
476    assert_round_trip("bool");
477  }
478
479  #[test]
480  fn test_references() {
481    assert_round_trip("&i32");
482    assert_round_trip("&mut String");
483    assert_round_trip("&'a [u8]");
484    assert_round_trip("&'static mut Vec<i32>");
485  }
486
487  #[test]
488  fn test_wrappers() {
489    assert_round_trip("::core::option::Option<i32>");
490    assert_round_trip("Vec<String>");
491    assert_round_trip("Box<MyStruct>");
492    assert_round_trip("HashMap<String, i32>");
493
494    assert_is_option("::core::option::Option<i32>");
495  }
496
497  #[test]
498  fn test_nested_complex() {
499    assert_round_trip("::core::option::Option<Vec<Box<i32>>>");
500    assert_round_trip("&mut HashMap<String, ::core::option::Option<Vec<u8>>>");
501  }
502
503  #[test]
504  fn test_arrays_and_tuples() {
505    assert_round_trip("[u8; 4]");
506    assert_round_trip("(&str, i32, ::core::option::Option<bool>)");
507  }
508
509  #[test]
510  fn test_slices() {
511    assert_round_trip("[u8]");
512    assert_round_trip("&[i32]");
513  }
514
515  fn get_info(s: &str) -> TypeInfo {
516    let ty: Type =
517      syn::parse_str(s).unwrap_or_else(|_| panic!("Failed to parse rust syntax: {}", s));
518    TypeInfo::from_type(&ty).unwrap()
519  }
520
521  fn assert_inner_eq(info: &TypeInfo, expected: &str) {
522    let tokens = info.to_token_stream().to_string();
523    assert_eq!(tokens, expected, "Inner type mismatch");
524  }
525
526  #[test]
527  fn test_wrappers_option() {
528    let info = get_info("Option<i32>");
529
530    if let RustType::Option(inner) = &*info.type_ {
531      assert_inner_eq(inner, "i32");
532    } else {
533      panic!("Failed to parse Option. Got different variant.");
534    }
535  }
536
537  #[test]
538  fn test_wrappers_vec() {
539    let info = get_info("Vec<String>");
540
541    if let RustType::Vec(inner) = &*info.type_ {
542      assert_inner_eq(inner, "String");
543    } else {
544      panic!("Failed to parse Vec.");
545    }
546  }
547
548  #[test]
549  fn test_wrappers_box() {
550    let info = get_info("Box<MyStruct>");
551
552    if let RustType::Box(inner) = &*info.type_ {
553      assert_inner_eq(inner, "MyStruct");
554    } else {
555      panic!("Failed to parse Box.");
556    }
557  }
558
559  #[test]
560  fn test_hashmap() {
561    let info = get_info("HashMap<String, i32>");
562
563    if let RustType::HashMap((k, v)) = &*info.type_ {
564      assert_inner_eq(k, "String");
565      assert_inner_eq(v, "i32");
566    } else {
567      panic!("Failed to parse HashMap. Got type {:#?}", info.type_);
568    }
569  }
570
571  #[test]
572  fn test_slice() {
573    let info = get_info("[u8]");
574
575    if let RustType::Slice(inner) = &*info.type_ {
576      assert_inner_eq(inner, "u8");
577    } else {
578      panic!("Failed to parse Slice.");
579    }
580  }
581
582  #[test]
583  fn test_array() {
584    let info = get_info("[u8; 4]");
585
586    if let RustType::Array(array) = &*info.type_ {
587      let Array { len, inner } = array.as_ref();
588      assert_inner_eq(inner, "u8");
589      let len_str = quote!(#len).to_string();
590      assert_eq!(len_str, "4");
591    } else {
592      panic!("Failed to parse Array.");
593    }
594  }
595
596  #[test]
597  fn test_tuple() {
598    let info = get_info("(i32, bool, String)");
599
600    if let RustType::Tuple(items) = &*info.type_ {
601      assert_eq!(items.len(), 3);
602      assert_inner_eq(&items[0], "i32");
603      assert_inner_eq(&items[1], "bool");
604      assert_inner_eq(&items[2], "String");
605    } else {
606      panic!("Failed to parse Tuple.");
607    }
608  }
609
610  #[test]
611  fn test_nested_wrappers() {
612    let info = get_info("Option<Vec<Box<i32>>>");
613
614    if let RustType::Option(l1) = &*info.type_
615      && let RustType::Vec(l2) = &*l1.type_
616        && let RustType::Box(l3) = &*l2.type_ {
617          assert_inner_eq(l3, "i32");
618          return;
619        }
620    panic!("Failed to parse deeply nested wrappers");
621  }
622
623  #[test]
624  fn test_references_mut() {
625    let info = get_info("&mut String");
626
627    assert!(info.reference.is_some(), "Should be a reference");
628    let ref_data = info.reference.unwrap();
629
630    match ref_data.kind {
631      RefKind::MutRef => {}
632      _ => panic!("Expected MutRef"),
633    }
634
635    if let RustType::Other(path) = &*info.type_ {
636      let path_str = quote!(#path).to_string();
637      assert_eq!(path_str, "String");
638    } else {
639      panic!("Inner type should be Other(String)");
640    }
641  }
642
643  #[test]
644  fn test_references_const() {
645    let info = get_info("&i32");
646
647    let ref_data = info.reference.expect("Should be a reference");
648    match ref_data.kind {
649      RefKind::Ref => {}
650      _ => panic!("Expected Ref (const)"),
651    }
652  }
653
654  #[test]
655  fn test_other_path() {
656    let info = get_info("my_crate::MyType");
657
658    if let RustType::Other(path) = &*info.type_ {
659      let s = quote::quote!(#path).to_string().replace(" ", "");
660      assert_eq!(s, "my_crate::MyType");
661    } else {
662      panic!("Should have parsed as Other/Path");
663    }
664  }
665}