sylvia_derive/parser/
check_generics.rs

1use proc_macro2::Ident;
2use syn::visit::Visit;
3use syn::{parse_quote, GenericArgument, GenericParam, Path, TraitItemType, Type};
4
5/// Provides method extracting `syn::Path`.
6/// Inteded to be used with `syn::GenericParam` and `syn::GenericArgument`.
7pub trait GetPath {
8    fn get_path(&self) -> Option<Path>;
9}
10
11impl GetPath for GenericParam {
12    fn get_path(&self) -> Option<Path> {
13        match self {
14            GenericParam::Type(ty) => {
15                let ident = &ty.ident;
16                Some(parse_quote! { #ident })
17            }
18            _ => None,
19        }
20    }
21}
22
23impl GetPath for GenericArgument {
24    fn get_path(&self) -> Option<Path> {
25        match self {
26            GenericArgument::Type(Type::Path(path)) => {
27                let path = &path.path;
28                Some(parse_quote! { #path })
29            }
30            _ => None,
31        }
32    }
33}
34
35impl GetPath for TraitItemType {
36    fn get_path(&self) -> Option<Path> {
37        let ident = &self.ident;
38        Some(parse_quote!(#ident))
39    }
40}
41
42impl GetPath for Ident {
43    fn get_path(&self) -> Option<Path> {
44        Some(parse_quote! { #self })
45    }
46}
47
48impl GetPath for Type {
49    fn get_path(&self) -> Option<Path> {
50        match self {
51            Type::Path(path) => Some(path.path.clone()),
52            _ => None,
53        }
54    }
55}
56
57/// Traverses AST tree and checks if generics are used in method signatures.
58#[derive(Debug)]
59pub struct CheckGenerics<'g, Generic> {
60    generics: &'g [&'g Generic],
61    used: Vec<&'g Generic>,
62}
63
64impl<'g, Generic> CheckGenerics<'g, Generic>
65where
66    Generic: GetPath + PartialEq,
67{
68    pub fn new(generics: &'g [&'g Generic]) -> Self {
69        Self {
70            generics,
71            used: vec![],
72        }
73    }
74
75    pub fn used(self) -> Vec<&'g Generic> {
76        self.used
77    }
78
79    /// Returns split between used and unused generics
80    pub fn used_unused(self) -> (Vec<&'g Generic>, Vec<&'g Generic>) {
81        let unused = self
82            .generics
83            .iter()
84            .filter(|gen| !self.used.contains(*gen))
85            .copied()
86            .collect();
87
88        (self.used, unused)
89    }
90}
91
92impl<'ast, Generic> Visit<'ast> for CheckGenerics<'_, Generic>
93where
94    Generic: GetPath + PartialEq,
95{
96    fn visit_path(&mut self, p: &'ast syn::Path) {
97        if let Some(gen) = self
98            .generics
99            .iter()
100            .find(|gen| gen.get_path().as_ref() == Some(p))
101        {
102            if !self.used.contains(gen) {
103                self.used.push(gen);
104            }
105        }
106
107        // Default visit implementation - visiting path deeper
108        for el in &p.segments {
109            self.visit_path_segment(el);
110        }
111    }
112}