summaryrefslogtreecommitdiff
path: root/src/utils.rs
blob: 5bbae6c986a412b053cf9c193c6ecf76bbf740ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use inflector::Inflector;
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::{format_ident, quote};
use syn::{GenericParam, Generics, Visibility};

/// Makes phantom data definitions so that we don't get unused template parameter errors.
pub fn make_generic_consumers(generics: &Generics) -> impl Iterator<Item = (TokenStream, Ident)> {
    generics
        .params
        .clone()
        .into_iter()
        .map(|param| match param {
            GenericParam::Type(ty) => {
                let ident = &ty.ident;
                (
                    quote! { #ident },
                    format_ident!(
                        "_consume_template_type_{}",
                        ident.to_string().to_snake_case()
                    ),
                )
            }
            GenericParam::Lifetime(lt) => {
                let lifetime = &lt.lifetime;
                let ident = &lifetime.ident;
                (
                    quote! { &#lifetime () },
                    format_ident!("_consume_template_lifetime_{}", ident),
                )
            }
            GenericParam::Const(..) => unimplemented!(),
        })
}

// Takes the generics parameters from the original struct and turns them into arguments.
pub fn make_generic_arguments(generics: &Generics) -> Vec<TokenStream> {
    let mut arguments = Vec::new();
    for generic in generics.params.clone() {
        match generic {
            GenericParam::Type(typ) => {
                let ident = &typ.ident;
                arguments.push(quote! { #ident });
            }
            GenericParam::Lifetime(lt) => {
                let lifetime = &lt.lifetime;
                arguments.push(quote! { #lifetime });
            }
            GenericParam::Const(_) => unimplemented!("Const generics are not supported yet."),
        }
    }
    arguments
}

pub fn uses_this_lifetime(input: TokenStream) -> bool {
    for token in input.into_iter() {
        match token {
            TokenTree::Ident(ident) => {
                if ident == "this" {
                    return true;
                }
            }
            TokenTree::Group(group) => {
                if uses_this_lifetime(group.stream()) {
                    return true;
                }
            }
            _ => (),
        }
    }
    false
}

pub fn replace_this_with_lifetime(input: TokenStream, lifetime: Ident) -> TokenStream {
    input
        .into_iter()
        .map(|token| match &token {
            TokenTree::Ident(ident) => {
                if ident == "this" {
                    TokenTree::Ident(lifetime.clone())
                } else {
                    token
                }
            }
            TokenTree::Group(group) => TokenTree::Group(Group::new(
                group.delimiter(),
                replace_this_with_lifetime(group.stream(), lifetime.clone()),
            )),
            _ => token,
        })
        .collect()
}

pub fn submodule_contents_visiblity(original_visibility: &Visibility) -> Visibility {
    match original_visibility {
        // inherited: allow parent of inner submodule to see
        Visibility::Inherited => syn::parse_quote! { pub(super) },
        // restricted: add an extra super if needed
        Visibility::Restricted(ref restricted) => {
            let is_first_component_super = restricted
                .path
                .segments
                .first()
                .map(|segm| segm.ident == "super")
                .unwrap_or(false);
            if restricted.path.leading_colon.is_none() && is_first_component_super {
                let mut new_visibility = restricted.clone();
                new_visibility.in_token = Some(
                    restricted
                        .in_token
                        .clone()
                        .unwrap_or_else(|| syn::parse_quote! { in }),
                );
                new_visibility.path.segments = std::iter::once(syn::parse_quote! { super })
                    .chain(restricted.path.segments.iter().cloned())
                    .collect();
                Visibility::Restricted(new_visibility)
            } else {
                original_visibility.clone()
            }
        }
        // others are absolute, can use them as-is
        _ => original_visibility.clone(),
    }
}

/// Functionality inspired by `Inflector`, reimplemented here to avoid the
/// `regex` dependency.
pub fn to_class_case(s: &str) -> String {
    s.split('_')
        .flat_map(|word| {
            let mut chars = word.chars();
            let first = chars.next();
            // Unicode allows for a single character to become multiple characters when converting between cases.
            first
                .into_iter()
                .flat_map(|c| c.to_uppercase())
                .chain(chars.flat_map(|c| c.to_lowercase()))
        })
        .collect()
}