ouroboros_macro/
utils.rs

1use heck::ToSnakeCase;
2use proc_macro2::{Group, Ident, TokenStream, TokenTree};
3use quote::{format_ident, quote};
4use syn::{GenericParam, Generics, Visibility};
5
6/// Makes phantom data definitions so that we don't get unused template parameter errors.
7pub fn make_generic_consumers(generics: &Generics) -> impl Iterator<Item = (TokenStream, Ident)> {
8    generics
9        .params
10        .clone()
11        .into_iter()
12        .map(|param| match param {
13            GenericParam::Type(ty) => {
14                let ident = &ty.ident;
15                (
16                    quote! { #ident },
17                    format_ident!(
18                        "_consume_template_type_{}",
19                        ident.to_string().to_snake_case()
20                    ),
21                )
22            }
23            GenericParam::Lifetime(lt) => {
24                let lifetime = &lt.lifetime;
25                let ident = &lifetime.ident;
26                (
27                    quote! { &#lifetime () },
28                    format_ident!("_consume_template_lifetime_{}", ident),
29                )
30            }
31            // rustc don't require constants to consume, so we just skip it. 
32            GenericParam::Const(ct) => {
33                let ident = ct.ident;
34                (
35                    quote! { () },
36                    format_ident!(
37                        "_comsume_template_const_parameter_{}",
38                        ident.to_string().to_snake_case()
39                    ),
40                )
41            },
42        })
43}
44
45// Takes the generics parameters from the original struct and turns them into arguments.
46pub fn make_generic_arguments(generics: Vec<&GenericParam>) -> Vec<TokenStream> {
47    let mut arguments = Vec::new();
48    for generic in generics {
49        match generic {
50            GenericParam::Type(typ) => {
51                let ident = &typ.ident;
52                arguments.push(quote! { #ident });
53            }
54            GenericParam::Lifetime(lt) => {
55                let lifetime = &lt.lifetime;
56                arguments.push(quote! { #lifetime });
57            }
58            GenericParam::Const(ct) => {
59                let ident = &ct.ident;
60                arguments.push(quote! { #ident });
61            },
62        }
63    }
64    arguments
65}
66
67pub fn uses_this_lifetime(input: TokenStream) -> bool {
68    for token in input.into_iter() {
69        match token {
70            TokenTree::Ident(ident) => {
71                if ident == "this" {
72                    return true;
73                }
74            }
75            TokenTree::Group(group) => {
76                if uses_this_lifetime(group.stream()) {
77                    return true;
78                }
79            }
80            _ => (),
81        }
82    }
83    false
84}
85
86pub fn replace_this_with_lifetime(input: TokenStream, lifetime: Ident) -> TokenStream {
87    input
88        .into_iter()
89        .map(|token| match &token {
90            TokenTree::Ident(ident) => {
91                if ident == "this" {
92                    TokenTree::Ident(lifetime.clone())
93                } else {
94                    token
95                }
96            }
97            TokenTree::Group(group) => TokenTree::Group(Group::new(
98                group.delimiter(),
99                replace_this_with_lifetime(group.stream(), lifetime.clone()),
100            )),
101            _ => token,
102        })
103        .collect()
104}
105
106pub fn submodule_contents_visibility(original_visibility: &Visibility) -> Visibility {
107    match original_visibility {
108        // inherited: allow parent of inner submodule to see
109        Visibility::Inherited => syn::parse_quote! { pub(super) },
110        // restricted: add an extra super if needed
111        Visibility::Restricted(ref restricted) => {
112            let is_first_component_super = restricted
113                .path
114                .segments
115                .first()
116                .map(|segm| segm.ident == "super")
117                .unwrap_or(false);
118            if restricted.path.leading_colon.is_none() && is_first_component_super {
119                let mut new_visibility = restricted.clone();
120                new_visibility.in_token = Some(
121                    restricted
122                        .in_token
123                        .unwrap_or_else(|| syn::parse_quote! { in }),
124                );
125                new_visibility.path.segments = std::iter::once(syn::parse_quote! { super })
126                    .chain(restricted.path.segments.iter().cloned())
127                    .collect();
128                Visibility::Restricted(new_visibility)
129            } else {
130                original_visibility.clone()
131            }
132        }
133        // others are absolute, can use them as-is
134        _ => original_visibility.clone(),
135    }
136}
137
138/// Functionality inspired by `Inflector`, reimplemented here to avoid the
139/// `regex` dependency.
140pub fn to_class_case(s: &str) -> String {
141    s.split('_')
142        .flat_map(|word| {
143            let mut chars = word.chars();
144            let first = chars.next();
145            // Unicode allows for a single character to become multiple characters when converting between cases.
146            first
147                .into_iter()
148                .flat_map(|c| c.to_uppercase())
149                .chain(chars.flat_map(|c| c.to_lowercase()))
150        })
151        .collect()
152}