1//! Analyze types
  2use quote::quote;
  3
  4use crate::data_model::TypeSegment;
  5
  6impl From<&str> for TypeSegment {
  7    fn from(s: &str) -> Self {
  8        TypeSegment::String(
  9            s.replace(" :: ", "::")
 10                .replace(" < ", "<")
 11                .replace(" >", ">"),
 12        ) // TODO this is a hack for now
 13    }
 14}
 15
 16impl From<String> for TypeSegment {
 17    fn from(s: String) -> Self {
 18        TypeSegment::String(
 19            s.replace(" :: ", "::")
 20                .replace(" < ", "<")
 21                .replace(" >", ">"),
 22        ) // TODO this is a hack for now
 23    }
 24}
 25
 26/// Converts a syn type to a list of text and Paths
 27pub(super) fn convert_type(ty: &syn::Type) -> Vec<TypeSegment> {
 28    let mut v = convert_type_inner(ty);
 29    // Merge adjacent strings
 30    v = v.iter().fold(Vec::new(), |mut acc, elem| {
 31        if let Some(TypeSegment::String(s)) = acc.last_mut() {
 32            if let TypeSegment::String(next) = elem {
 33                s.push_str(next);
 34                return acc;
 35            }
 36        }
 37        acc.push(elem.clone());
 38        acc
 39    });
 40    v
 41}
 42
 43fn convert_type_inner(ty: &syn::Type) -> Vec<TypeSegment> {
 44    match ty {
 45        syn::Type::Array(array) => {
 46            let mut v = vec!["[".into()];
 47            v.extend(convert_type(&array.elem));
 48            v.push("; ".into());
 49            let len = &array.len;
 50            v.push(quote! { #len }.to_string().into());
 51            v.push("]".into());
 52            v
 53        }
 54        syn::Type::BareFn(func) => vec![quote! { #func }.to_string().into()], // TODO this needs to be expanded
 55        syn::Type::Group(group) => convert_type(&group.elem),
 56        syn::Type::ImplTrait(imp) => {
 57            let mut v = vec!["impl ".into()];
 58            for (i, elem) in imp.bounds.iter().enumerate() {
 59                if i > 0 {
 60                    v.push(" + ".into());
 61                }
 62                v.push(quote! { #elem }.to_string().into()); // TODO this needs to be expanded to capture traits
 63            }
 64            v
 65        }
 66        syn::Type::Infer(_) => vec!["_".into()],
 67        syn::Type::Macro(mac) => vec![quote! { #mac }.to_string().into()],
 68        syn::Type::Never(_) => vec!["!".into()],
 69        syn::Type::Paren(paren) => {
 70            let mut v = vec!["(".into()];
 71            v.extend(convert_type(&paren.elem));
 72            v.push(")".into());
 73            v
 74        }
 75        syn::Type::Path(path) => {
 76            // TODO this is wrong, it puts spaces between the path segments
 77            vec![TypeSegment::Path(
 78                quote! { #path }
 79                    .to_string()
 80                    .replace(" :: ", "::")
 81                    .replace(" < ", "<")
 82                    .replace(" >", ">"),
 83            )]
 84        }
 85        syn::Type::Ptr(ptr) => {
 86            let mut v = vec![];
 87            if ptr.const_token.is_some() {
 88                v.push("*const ".into());
 89            } else if ptr.mutability.is_some() {
 90                v.push("*mut ".into());
 91            } else {
 92                v.push("*".into());
 93            }
 94            v.extend(convert_type(&ptr.elem));
 95            v
 96        }
 97        syn::Type::Reference(ref_) => {
 98            let mut v = vec!["&".into()];
 99            if let Some(lifetime) = &ref_.lifetime {
100                v.push(lifetime.ident.to_string().into());
101            }
102            if ref_.mutability.is_some() {
103                v.push(" mut ".into());
104            } else {
105                v.push(" ".into());
106            }
107            v.extend(convert_type(&ref_.elem));
108            v
109        }
110        syn::Type::Slice(slice) => {
111            let mut v = vec!["[".into()];
112            v.extend(convert_type(&slice.elem));
113            v.push("]".into());
114            v
115        }
116        syn::Type::TraitObject(trait_) => {
117            let mut v = vec!["dyn ".into()];
118            for (i, elem) in trait_.bounds.iter().enumerate() {
119                if i > 0 {
120                    v.push(" + ".into());
121                }
122                v.push(quote! { #elem }.to_string().into()); // TODO this needs to be expanded to capture traits
123            }
124            v
125        }
126        syn::Type::Tuple(tuple) => {
127            let mut v = vec!["(".into()];
128            for (i, elem) in tuple.elems.iter().enumerate() {
129                if i > 0 {
130                    v.push(", ".into());
131                }
132                v.extend(convert_type(elem));
133            }
134            v.push(")".into());
135            v
136        }
137        syn::Type::Verbatim(verb) => vec![quote! { #verb }.to_string().into()],
138        _ => vec![quote! { #ty }.to_string().into()],
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use insta::assert_yaml_snapshot;
146
147    #[test]
148    fn ty_to_type_array() {
149        let ty = syn::parse_quote! { [u8; 10] };
150        let result = convert_type(&ty);
151        assert_yaml_snapshot!(result, @r###"
152        ---
153        - String: "["
154        - Path: u8
155        - String: "; 10]"
156        "###);
157    }
158
159    #[test]
160    fn ty_to_type_infer() {
161        let ty = syn::parse_quote! { _ };
162        let result = convert_type(&ty);
163        assert_yaml_snapshot!(result, @r###"
164        ---
165        - String: _
166        "###);
167    }
168
169    #[test]
170    fn ty_to_type_impl_trait() {
171        let ty = syn::parse_quote! { impl Bound1 + Bound2 + Bound3 };
172        let result = convert_type(&ty);
173        assert_yaml_snapshot!(result, @r###"
174        ---
175        - String: impl Bound1 + Bound2 + Bound3
176        "###);
177    }
178
179    #[test]
180    fn ty_to_type_never() {
181        let ty = syn::parse_quote! { ! };
182        let result = convert_type(&ty);
183        assert_yaml_snapshot!(result, @r###"
184        ---
185        - String: "!"
186        "###);
187    }
188
189    #[test]
190    fn ty_to_type_paren() {
191        let ty = syn::parse_quote! { (u8) };
192        let result = convert_type(&ty);
193        assert_yaml_snapshot!(result, @r###"
194        ---
195        - String: (
196        - Path: u8
197        - String: )
198        "###);
199    }
200
201    #[test]
202    fn ty_to_type_path() {
203        let ty = syn::parse_quote! { std::collections::HashMap<u8, u16> };
204        let result = convert_type(&ty);
205        assert_yaml_snapshot!(result, @r###"
206        ---
207        - Path: "std::collections::HashMap<u8 , u16>"
208        "###);
209    }
210
211    #[test]
212    fn ty_to_type_ptr() {
213        let ty = syn::parse_quote! { *const u8 };
214        let result = convert_type(&ty);
215        assert_yaml_snapshot!(result, @r###"
216        ---
217        - String: "*const "
218        - Path: u8
219        "###);
220    }
221
222    #[test]
223    fn ty_to_type_ref() {
224        let ty = syn::parse_quote! { &'a mut u8 };
225        let result = convert_type(&ty);
226        assert_yaml_snapshot!(result, @r###"
227        ---
228        - String: "&a mut "
229        - Path: u8
230        "###);
231    }
232
233    #[test]
234    fn ty_to_type_slice() {
235        let ty = syn::parse_quote! { [u8] };
236        let result = convert_type(&ty);
237        assert_yaml_snapshot!(result, @r###"
238        ---
239        - String: "["
240        - Path: u8
241        - String: "]"
242        "###);
243    }
244
245    #[test]
246    fn ty_to_type_trait() {
247        let ty = syn::parse_quote! { dyn std::fmt::Debug + 'a };
248        let result = convert_type(&ty);
249        assert_yaml_snapshot!(result, @r###"
250        ---
251        - String: "dyn std::fmt::Debug + 'a"
252        "###);
253    }
254
255    #[test]
256    fn ty_to_type_tuple() {
257        let ty = syn::parse_quote! { (u8, u16) };
258        let result = convert_type(&ty);
259        assert_yaml_snapshot!(result, @r###"
260        ---
261        - String: (
262        - Path: u8
263        - String: ", "
264        - Path: u16
265        - String: )
266        "###);
267    }
268}