auto detect optional props

This commit is contained in:
Maccesch 2022-03-15 16:52:45 +00:00
parent 9a23ee4612
commit 5818da5f70

View file

@ -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<...>`",