Răsfoiți Sursa

auto detect optional props

Maccesch 3 ani în urmă
părinte
comite
5818da5f70
1 a modificat fișierele cu 48 adăugiri și 37 ștergeri
  1. 48 37
      packages/core-macro/src/props/mod.rs

+ 48 - 37
packages/core-macro/src/props/mod.rs

@@ -43,27 +43,21 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
             syn::Fields::Unnamed(_) => {
                 return Err(Error::new(
                     ast.span(),
-                    "TypedBuilder is not supported for tuple structs",
+                    "Props is not supported for tuple structs",
                 ))
             }
             syn::Fields::Unit => {
                 return Err(Error::new(
                     ast.span(),
-                    "TypedBuilder is not supported for unit structs",
+                    "Props is not supported for unit structs",
                 ))
             }
         },
         syn::Data::Enum(_) => {
-            return Err(Error::new(
-                ast.span(),
-                "TypedBuilder is not supported for enums",
-            ))
+            return Err(Error::new(ast.span(), "Props is not supported for enums"))
         }
         syn::Data::Union(_) => {
-            return Err(Error::new(
-                ast.span(),
-                "TypedBuilder is not supported for unions",
-            ))
+            return Err(Error::new(ast.span(), "Props is not supported for unions"))
         }
     };
     Ok(data)
@@ -169,6 +163,7 @@ mod util {
 }
 
 mod field_info {
+    use crate::props::type_from_inside_option;
     use proc_macro2::TokenStream;
     use quote::quote;
     use syn::parse::Error;
@@ -202,6 +197,16 @@ mod field_info {
                         Some(syn::parse(quote!(Default::default()).into()).unwrap());
                 }
 
+                // auto detect optional
+                let strip_option_auto = builder_attr.strip_option
+                    || !builder_attr.ignore_option
+                        && type_from_inside_option(&field.ty, true).is_some();
+                if !builder_attr.strip_option && strip_option_auto {
+                    builder_attr.strip_option = true;
+                    builder_attr.default =
+                        Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                }
+
                 Ok(FieldInfo {
                     ordinal,
                     name,
@@ -236,31 +241,8 @@ mod field_info {
             .into()
         }
 
-        pub fn type_from_inside_option(&self) -> Option<&syn::Type> {
-            let path = if let syn::Type::Path(type_path) = self.ty {
-                if type_path.qself.is_some() {
-                    return None;
-                } else {
-                    &type_path.path
-                }
-            } else {
-                return None;
-            };
-            let segment = path.segments.last()?;
-            if segment.ident != "Option" {
-                return None;
-            }
-            let generic_params =
-                if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
-                    generic_params
-                } else {
-                    return None;
-                };
-            if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
-                Some(ty)
-            } else {
-                None
-            }
+        pub fn type_from_inside_option(&self, check_option_name: bool) -> Option<&syn::Type> {
+            type_from_inside_option(self.ty, check_option_name)
         }
     }
 
@@ -271,6 +253,7 @@ mod field_info {
         pub skip: bool,
         pub auto_into: bool,
         pub strip_option: bool,
+        pub ignore_option: bool,
     }
 
     impl FieldBuilderAttr {
@@ -427,8 +410,9 @@ mod field_info {
                                 self.auto_into = false;
                                 Ok(())
                             }
-                            "strip_option" => {
+                            "optional" => {
                                 self.strip_option = false;
+                                self.ignore_option = true;
                                 Ok(())
                             }
                             _ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
@@ -446,6 +430,33 @@ mod field_info {
     }
 }
 
+fn type_from_inside_option(ty: &syn::Type, check_option_name: bool) -> Option<&syn::Type> {
+    let path = if let syn::Type::Path(type_path) = ty {
+        if type_path.qself.is_some() {
+            return None;
+        } else {
+            &type_path.path
+        }
+    } else {
+        return None;
+    };
+    let segment = path.segments.last()?;
+    if check_option_name && segment.ident != "Option" {
+        return None;
+    }
+    let generic_params =
+        if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
+            generic_params
+        } else {
+            return None;
+        };
+    if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
+        Some(ty)
+    } else {
+        None
+    }
+}
+
 mod struct_info {
     use proc_macro2::TokenStream;
     use quote::quote;
@@ -766,7 +777,7 @@ Finally, call `.build()` to create the instance of `{name}`.
             // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
             // nesting is different so we have to do this little dance.
             let arg_type = if field.builder_attr.strip_option {
-                let internal_type = field.type_from_inside_option().ok_or_else(|| {
+                let internal_type = field.type_from_inside_option(false).ok_or_else(|| {
                     Error::new_spanned(
                         &field_type,
                         "can't `strip_option` - field is not `Option<...>`",