1use std::collections::{BTreeMap, BTreeSet};
13use std::iter;
14
15use anyhow::{Result, bail};
16use itertools::Itertools;
17use quote::ToTokens;
18
19pub struct Ir {
21 pub items: BTreeMap<String, Item>,
23 pub generics: BTreeMap<String, BTreeSet<String>>,
29}
30
31#[derive(Debug)]
33pub enum Item {
34 Struct(Struct),
36 Enum(Enum),
38 Abstract,
40}
41
42impl Item {
43 pub fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Field> + 'a> {
44 match self {
45 Item::Struct(s) => Box::new(s.fields.iter()),
46 Item::Enum(e) => Box::new(e.variants.iter().flat_map(|v| &v.fields)),
47 Item::Abstract => Box::new(iter::empty()),
48 }
49 }
50
51 pub fn generics(&self) -> &[ItemGeneric] {
52 match self {
53 Item::Struct(s) => &s.generics,
54 Item::Enum(e) => &e.generics,
55 Item::Abstract => &[],
56 }
57 }
58}
59
60#[derive(Debug)]
62pub struct Struct {
63 pub fields: Vec<Field>,
65 pub generics: Vec<ItemGeneric>,
67}
68
69#[derive(Debug)]
71pub struct Enum {
72 pub variants: Vec<Variant>,
74 pub generics: Vec<ItemGeneric>,
76}
77
78#[derive(Debug)]
80pub struct Variant {
81 pub name: String,
83 pub fields: Vec<Field>,
85}
86
87#[derive(Debug)]
89pub struct Field {
90 pub name: Option<String>,
94 pub ty: Type,
96}
97
98#[derive(Debug)]
100pub struct ItemGeneric {
101 pub name: String,
103 pub bounds: Vec<String>,
105}
106
107#[derive(Debug)]
109pub enum Type {
110 Primitive,
114 Abstract(String),
119 Option(Box<Type>),
124 Vec(Box<Type>),
128 Box(Box<Type>),
132 Local(String),
137 Map { key: Box<Type>, value: Box<Type> },
141}
142
143pub(crate) fn analyze(syn_items: &[syn::DeriveInput]) -> Result<Ir> {
149 let mut items = BTreeMap::new();
150 for syn_item in syn_items {
151 let name = syn_item.ident.to_string();
152 let generics = analyze_generics(&syn_item.generics)?;
153 let item = match &syn_item.data {
154 syn::Data::Struct(s) => Item::Struct(Struct {
155 fields: analyze_fields(&s.fields)?,
156 generics,
157 }),
158 syn::Data::Enum(e) => {
159 let mut variants = vec![];
160 for v in &e.variants {
161 variants.push(Variant {
162 name: v.ident.to_string(),
163 fields: analyze_fields(&v.fields)?,
164 });
165 }
166 Item::Enum(Enum { variants, generics })
167 }
168 syn::Data::Union(_) => bail!("Unable to analyze union: {}", syn_item.ident),
169 };
170 for field in item.fields() {
171 let mut field_ty = &field.ty;
172 while let Type::Box(ty) | Type::Vec(ty) | Type::Option(ty) = field_ty {
173 field_ty = ty;
174 }
175 if let Type::Abstract(name) = field_ty {
176 items.insert(name.clone(), Item::Abstract);
177 }
178 }
179 items.insert(name, item);
180 }
181
182 let mut generics = BTreeMap::<_, BTreeSet<String>>::new();
183 for item in items.values() {
184 for ig in item.generics() {
185 generics
186 .entry(ig.name.clone())
187 .or_default()
188 .extend(ig.bounds.clone());
189 }
190 }
191
192 for item in items.values() {
193 validate_fields(&items, item.fields())?
194 }
195
196 Ok(Ir { items, generics })
197}
198
199fn validate_fields<'a, I>(items: &BTreeMap<String, Item>, fields: I) -> Result<()>
200where
201 I: IntoIterator<Item = &'a Field>,
202{
203 for f in fields {
204 match &f.ty {
205 Type::Local(s) if !items.contains_key(s) => {
206 bail!(
207 "Unable to analyze non built-in type that is not defined in input: {}",
208 s
209 );
210 }
211 _ => (),
212 }
213 }
214 Ok(())
215}
216
217fn analyze_fields(fields: &syn::Fields) -> Result<Vec<Field>> {
218 fields
219 .iter()
220 .map(|f| {
221 Ok(Field {
222 name: f.ident.as_ref().map(|id| id.to_string()),
223 ty: analyze_type(&f.ty)?,
224 })
225 })
226 .collect()
227}
228
229fn analyze_generics(generics: &syn::Generics) -> Result<Vec<ItemGeneric>> {
230 let mut out = vec![];
231 for g in generics.params.iter() {
232 match g {
233 syn::GenericParam::Type(syn::TypeParam { ident, bounds, .. }) => {
234 let name = ident.to_string();
235 let bounds = analyze_generic_bounds(bounds)?;
236 if name.ends_with('2') {
239 bail!(
240 "Generic parameters whose name ends in '2' conflict with folder's naming scheme: {}",
241 name
242 );
243 }
244 out.push(ItemGeneric { name, bounds });
245 }
246 _ => {
247 bail!(
248 "Unable to analyze non-type generic parameter: {}",
249 g.to_token_stream()
250 )
251 }
252 }
253 }
254 Ok(out)
255}
256
257fn analyze_generic_bounds<'a, I>(bounds: I) -> Result<Vec<String>>
258where
259 I: IntoIterator<Item = &'a syn::TypeParamBound>,
260{
261 let mut out = vec![];
262 for b in bounds {
263 match b {
264 syn::TypeParamBound::Trait(t) if t.path.segments.len() != 1 => {
265 bail!(
266 "Unable to analyze trait bound with more than one path segment: {}",
267 b.to_token_stream()
268 )
269 }
270 syn::TypeParamBound::Trait(t) => out.push(t.path.segments[0].ident.to_string()),
271 _ => bail!("Unable to analyze non-trait bound: {}", b.to_token_stream()),
272 }
273 }
274 Ok(out)
275}
276
277fn analyze_type(ty: &syn::Type) -> Result<Type> {
278 match ty {
279 syn::Type::Path(syn::TypePath {
280 qself: None, path, ..
281 }) => match path.segments.len() {
282 2 => {
283 let name = path.segments.iter().map(|s| s.ident.to_string()).join("::");
284 Ok(Type::Abstract(name))
285 }
286 1 => {
287 let segment = path.segments.last().unwrap();
288 let segment_name = segment.ident.to_string();
289
290 let container = |construct_ty: fn(Box<Type>) -> Type| match &segment.arguments {
291 syn::PathArguments::AngleBracketed(args) if args.args.len() == 1 => {
292 match args.args.last().unwrap() {
293 syn::GenericArgument::Type(ty) => {
294 let inner = Box::new(analyze_type(ty)?);
295 Ok(construct_ty(inner))
296 }
297 _ => bail!(
298 "Container type argument is not a basic (i.e., non-lifetime, non-constraint) type argument: {}",
299 ty.into_token_stream()
300 ),
301 }
302 }
303 syn::PathArguments::AngleBracketed(_) => bail!(
304 "Container type does not have exactly one type argument: {}",
305 ty.into_token_stream()
306 ),
307 syn::PathArguments::Parenthesized(_) => bail!(
308 "Container type has unexpected parenthesized type arguments: {}",
309 ty.into_token_stream()
310 ),
311 syn::PathArguments::None => bail!(
312 "Container type is missing type argument: {}",
313 ty.into_token_stream()
314 ),
315 };
316
317 match &*segment_name {
318 "bool" | "usize" | "u8" | "u16" | "u32" | "u64" | "isize" | "i8" | "i16"
319 | "i32" | "i64" | "f32" | "f64" | "char" | "String" | "PathBuf" => {
320 match segment.arguments {
321 syn::PathArguments::None => Ok(Type::Primitive),
322 _ => bail!(
323 "Primitive type had unexpected arguments: {}",
324 ty.into_token_stream()
325 ),
326 }
327 }
328 "Vec" => container(Type::Vec),
329 "Option" => container(Type::Option),
330 "Box" => container(Type::Box),
331 "BTreeMap" => match &segment.arguments {
332 syn::PathArguments::None => bail!("Map type missing arguments"),
333 syn::PathArguments::AngleBracketed(args) if args.args.len() == 2 => {
334 let key = match &args.args[0] {
335 syn::GenericArgument::Type(t) => t,
336 _ => bail!("Invalid argument to map container, should be a Type"),
337 };
338 let value = match &args.args[1] {
339 syn::GenericArgument::Type(t) => t,
340 _ => bail!("Invalid argument to map container, should be a Type"),
341 };
342 Ok(Type::Map {
343 key: Box::new(analyze_type(key)?),
344 value: Box::new(analyze_type(value)?),
345 })
346 }
347 &syn::PathArguments::AngleBracketed(_) => {
348 bail!("wrong type of arguments for map container")
349 }
350 syn::PathArguments::Parenthesized(_) => {
351 bail!("wrong type of arguments for map container")
352 }
353 },
354 _ => Ok(Type::Local(segment_name)),
355 }
356 }
357 _ => {
358 bail!(
359 "Unable to analyze type path with more than two components: '{}'",
360 path.into_token_stream()
361 )
362 }
363 },
364 _ => bail!(
365 "Unable to analyze non-struct, non-enum type: {}",
366 ty.into_token_stream()
367 ),
368 }
369}