inter_struct_codegen/generate/
field.rs

1use proc_macro2::TokenStream;
2use syn::{GenericArgument, PathArguments, Type};
3
4use crate::error::*;
5
6/// Internal representation of parsed types
7///
8/// We either expect fields to have a generic type `T` or `Option<T>`.
9/// Allow dead code, since this is what we're going to use as soon as proc macro hygiene has
10/// improved.
11#[allow(clippy::large_enum_variant)]
12#[allow(dead_code)]
13pub enum FieldType {
14    Normal(Type),
15    Optional { inner: Type, outer: Type },
16    Invalid,
17}
18
19/// This function takes any [Type] and determines, whether it's an `Option<T>` or just a `T`.
20///
21/// This detected variant is represented via the [FieldType] enum.
22/// Invalid or unsupported types return the `FieldType::Invalid` variant.
23///
24/// Known limitations:
25///
26/// This doesn't work with type aliases. We literally check the tokens for `Option<...>`.
27/// If there's an optional type that doesn't look like this, we won't detect it.
28pub fn determine_field_type(ty: Type) -> Result<FieldType, TokenStream> {
29    match ty.clone() {
30        Type::Path(type_path) => {
31            // The path is relative to `Self` and thereby non-optional
32            if type_path.qself.is_some() {
33                return Ok(FieldType::Normal(ty));
34            }
35
36            let path = type_path.path;
37
38            // `Option<T>` shouldn't have a leading colon or multiple segments.
39            if path.leading_colon.is_some() || path.segments.len() > 1 {
40                return Ok(FieldType::Normal(ty));
41            }
42
43            // The path should have at least one segment.
44            let segment = if let Some(segment) = path.segments.iter().next() {
45                segment
46            } else {
47                return Ok(FieldType::Normal(ty));
48            };
49
50            // The segment isn't an option.
51            if segment.ident != "Option" {
52                return Ok(FieldType::Normal(ty));
53            }
54
55            // Get the angle brackets
56            let generic_arg = match &segment.arguments {
57                PathArguments::AngleBracketed(params) => {
58                    if let Some(arg) = params.args.iter().next() {
59                        arg
60                    } else {
61                        return Err(err!(ty, "Option doesn't have a type parameter.."));
62                    }
63                }
64                _ => {
65                    return Err(err!(
66                        ty,
67                        "Unknown path arguments behind Option. Please report this."
68                    ))
69                }
70            };
71
72            // This argument must be a type:
73            match generic_arg {
74                GenericArgument::Type(inner_type) => Ok(FieldType::Optional {
75                    inner: inner_type.clone(),
76                    outer: ty,
77                }),
78                _ => Err(err!(ty, "Option path argument isn't a type.")),
79            }
80        }
81        _ => Err(err!(
82            ty,
83            "Found a non-path type. This isn't supported in inter-struct yet."
84        )),
85    }
86}