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}