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}