use std::collections::HashSet;
use proc_macro2::{TokenStream, TokenTree};
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{
parse_quote, spanned::Spanned, Attribute, Data, DeriveInput, Field, Fields, Ident, Path, Token,
Type, Visibility,
};
const RENAME_ATTRIBUTE: &str = "optional_rename";
const SKIP_WRAP_ATTRIBUTE: &str = "optional_skip_wrap";
const WRAP_ATTRIBUTE: &str = "optional_wrap";
const SERDE_SKIP_SERIALIZING_NONE: &str = "optional_serde_skip_none";
const CFG_ATTRIBUTE: &str = "cfg";
struct FieldOptions {
wrapping_behavior: bool,
serde_skip: bool,
cfg_attribute: Option<Attribute>,
new_type: Option<TokenTree>,
field_ident: TokenStream,
}
trait OptionalFieldVisitor {
fn visit(
&mut self,
global_options: &GlobalOptions,
old_field: &mut Field,
new_field: &mut Field,
field_options: &FieldOptions,
);
}
struct GenerateTryFromImpl {
field_assign_acc: TokenStream,
field_check_acc: TokenStream,
}
impl GenerateTryFromImpl {
fn new() -> Self {
GenerateTryFromImpl {
field_check_acc: quote! {},
field_assign_acc: quote! {},
}
}
fn get_implementation(self, derive_input: &DeriveInput, new: &DeriveInput) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = derive_input.generics.split_for_impl();
let old_name = &derive_input.ident;
let new_name = &new.ident;
let field_check_acc = self.field_check_acc;
let field_assign_acc = self.field_assign_acc;
quote! {
impl #impl_generics TryFrom<#new_name #ty_generics > #where_clause for #old_name #ty_generics {
type Error = #new_name #ty_generics;
fn try_from(v: Self::Error) -> Result<Self, Self::Error> {
#field_check_acc
Ok(Self {
#field_assign_acc
})
}
}
}
}
}
impl OptionalFieldVisitor for GenerateTryFromImpl {
fn visit(
&mut self,
_global_options: &GlobalOptions,
old_field: &mut Field,
_new_field: &mut Field,
field_options: &FieldOptions,
) {
let ident = &field_options.field_ident;
let cfg_attr = &field_options.cfg_attribute;
let is_wrapped = field_options.wrapping_behavior;
let is_nested = field_options.new_type.is_some();
let is_base_opt = is_type_option(&old_field.ty);
let (unwrap, check) = match (is_base_opt, is_wrapped, is_nested) {
(_, true, false) => (
quote! { .unwrap() },
quote! { #cfg_attr if v.#ident.is_none() { return Err(v); } },
),
(true, true, true) => (
quote! { .unwrap().try_into().ok() },
quote! { #cfg_attr if let Some(i) = &v.#ident { if !i.can_convert() { return Err(v); } } else { return Err(v); } },
),
(false, true, true) => (
quote! { .unwrap().try_into().unwrap() },
quote! { #cfg_attr if let Some(i) = &v.#ident { if !i.can_convert() { return Err(v); } } else { return Err(v); } },
),
(true, false, true) => (
quote! { .try_into().ok() },
quote! { #cfg_attr if !v.#ident.can_convert() { return Err(v); } },
),
(false, false, true) => (
quote! { .try_into().unwrap() },
quote! { #cfg_attr if !v.#ident.can_convert() { return Err(v); } },
),
(_, false, false) => (quote! {}, quote! {}),
};
let field_assign_acc = &self.field_assign_acc;
self.field_assign_acc = quote! {
#field_assign_acc
#cfg_attr
#ident: v.#ident #unwrap,
};
let field_check_acc = &self.field_check_acc;
self.field_check_acc = quote! {
#field_check_acc
#check
};
}
}
struct GenerateApplicableImplVisitor {
acc_concrete: TokenStream,
acc_opt: TokenStream,
acc_can_convert: TokenStream,
}
impl GenerateApplicableImplVisitor {
fn new() -> Self {
GenerateApplicableImplVisitor {
acc_concrete: quote! {},
acc_opt: quote! {},
acc_can_convert: quote! {},
}
}
fn get_implementation(self, orig: &DeriveInput, new: &DeriveInput) -> TokenStream {
let (impl_generics, ty_generics, _) = orig.generics.split_for_impl();
let orig_name = &orig.ident;
let new_name = &new.ident;
let acc_concrete = self.acc_concrete;
let acc_opt = self.acc_opt;
let acc_can_convert = self.acc_can_convert;
quote! {
impl #impl_generics optional_struct::Applicable for #new_name #ty_generics {
type Base = #orig_name #ty_generics;
fn apply_to(self, t: &mut Self::Base) {
#acc_concrete
}
fn apply_to_opt(self, t: &mut Self) {
#acc_opt
}
fn can_convert(&self) -> bool {
#acc_can_convert
true
}
}
}
}
fn get_incremental_setter_concrete(
ident: &TokenStream,
is_wrapped: bool,
is_nested: bool,
is_base_opt: bool,
) -> TokenStream {
match (is_base_opt, is_wrapped, is_nested) {
(true, false, true) => quote! {
if let Some(existing) = &mut t.#ident {
self.#ident.apply_to(existing);
} else {
t.#ident = self.#ident.try_into().ok();
}
},
(true, false, false) => quote! {
if self.#ident.is_some() {
t.#ident = self.#ident;
}
},
(false, false, true) => quote! { self.#ident.apply_to(&mut t.#ident); },
(false, false, false) => quote! { t.#ident = self.#ident; },
(true, true, true) => {
quote! { if let (Some(inner), Some(target)) = (self.#ident, &mut t.#ident) { inner.apply_to(target); } }
}
(false, true, true) => {
quote! { if let Some(inner) = self.#ident { inner.apply_to(&mut t.#ident); } }
}
(_, true, false) => quote! { if let Some(inner) = self.#ident { t.#ident = inner; } },
}
}
fn get_incremental_setter_opt(
ident: &TokenStream,
is_wrapped: bool,
is_nested: bool,
is_base_opt: bool,
) -> TokenStream {
match (is_base_opt, is_wrapped, is_nested) {
(_, false, true) => quote! {
self.#ident.apply_to_opt(&mut t.#ident);
},
(true, false, false) => quote! {
if self.#ident.is_some() {
t.#ident = self.#ident;
}
},
(false, false, false) => quote! { t.#ident = self.#ident; },
(_, true, true) => quote! {
match (&mut t.#ident, self.#ident) {
(None, Some(nested)) => t.#ident = Some(nested),
(Some(existing), Some(nested)) => nested.apply_to_opt(existing),
(_, None) => {},
}
},
(_, true, false) => {
quote! { if let Some(inner) = self.#ident { t.#ident = Some(inner); } }
}
}
}
}
impl OptionalFieldVisitor for GenerateApplicableImplVisitor {
fn visit(
&mut self,
_global_options: &GlobalOptions,
old_field: &mut Field,
_new_field: &mut Field,
field_options: &FieldOptions,
) {
let ident = &field_options.field_ident;
let cfg_attr = &field_options.cfg_attribute;
let is_wrapped = field_options.wrapping_behavior;
let is_nested = field_options.new_type.is_some();
let is_base_opt = is_type_option(&old_field.ty);
let inc_concrete =
Self::get_incremental_setter_concrete(ident, is_wrapped, is_nested, is_base_opt);
let inc_opt = Self::get_incremental_setter_opt(
ident,
is_wrapped,
is_nested,
is_wrapped || is_base_opt,
);
let acc_concrete = &self.acc_concrete;
self.acc_concrete = quote! {
#acc_concrete
#cfg_attr
#inc_concrete
};
let acc_opt = &self.acc_opt;
self.acc_opt = quote! {
#acc_opt
#cfg_attr
#inc_opt
};
let inc_can_convert = match (is_base_opt, is_wrapped, is_nested) {
(_, true, false) => quote! {
if self.#ident.is_none() {
return false;
}
},
(_, true, true) => {
quote! {
if let Some(i) = &self.#ident {
if !i.can_convert() {
return false;
}
} else {
return false;
}
}
}
(_, false, true) => quote! {
if !self.#ident.can_convert() {
return false;
}
},
(_, false, false) => quote! {},
};
let acc_can_convert = &self.acc_can_convert;
self.acc_can_convert = quote! {
#acc_can_convert
#cfg_attr
#inc_can_convert
};
}
}
struct SetNewFieldVisibilityVisitor;
impl OptionalFieldVisitor for SetNewFieldVisibilityVisitor {
fn visit(
&mut self,
global_options: &GlobalOptions,
_old_field: &mut Field,
new_field: &mut Field,
_field_options: &FieldOptions,
) {
if global_options.make_fields_public {
new_field.vis = Visibility::Public(syn::token::Pub(new_field.vis.span()))
}
}
}
struct SetNewFieldTypeVisitor;
impl OptionalFieldVisitor for SetNewFieldTypeVisitor {
fn visit(
&mut self,
_global_options: &GlobalOptions,
old_field: &mut Field,
new_field: &mut Field,
field_options: &FieldOptions,
) {
let mut new_type = if let Some(t) = &field_options.new_type {
quote! {#t}
} else {
let t = &old_field.ty;
quote! {#t}
};
if field_options.wrapping_behavior {
new_type = quote! {Option<#new_type>};
};
new_field.ty = Type::Verbatim(new_type);
}
}
struct AddSerdeSkipAttribute;
impl OptionalFieldVisitor for AddSerdeSkipAttribute {
fn visit(
&mut self,
_global_options: &GlobalOptions,
_old_field: &mut Field,
new_field: &mut Field,
field_options: &FieldOptions,
) {
if !field_options.serde_skip {
return;
}
let attribute: Attribute =
parse_quote! { #[serde(skip_serializing_if = "Option::is_none")] };
new_field.attrs.push(attribute);
}
}
struct RemoveHelperAttributesVisitor;
impl OptionalFieldVisitor for RemoveHelperAttributesVisitor {
fn visit(
&mut self,
_global_options: &GlobalOptions,
old_field: &mut Field,
new_field: &mut Field,
_field_options: &FieldOptions,
) {
let indexes_to_remove = old_field
.attrs
.iter()
.enumerate()
.filter_map(|(i, a)| {
if a.path().is_ident(RENAME_ATTRIBUTE)
|| a.path().is_ident(SKIP_WRAP_ATTRIBUTE)
|| a.path().is_ident(WRAP_ATTRIBUTE)
|| a.path().is_ident(SERDE_SKIP_SERIALIZING_NONE)
{
Some(i)
} else {
None
}
})
.collect::<Vec<_>>();
for i in indexes_to_remove.into_iter().rev() {
old_field.attrs.swap_remove(i);
new_field.attrs.swap_remove(i);
}
}
}
fn borrow_fields(derive_input: &mut DeriveInput) -> &mut Punctuated<Field, Comma> {
let data_struct = match &mut derive_input.data {
Data::Struct(data_struct) => data_struct,
_ => panic!("OptionalStruct only works for structs :)"),
};
match &mut data_struct.fields {
Fields::Unnamed(f) => &mut f.unnamed,
Fields::Named(f) => &mut f.named,
Fields::Unit => unreachable!("A struct cannot have simply a unit field?"),
}
}
fn visit_fields(
visitors: &mut [&mut dyn OptionalFieldVisitor],
global_options: &GlobalOptions,
derive_input: &DeriveInput,
) -> (DeriveInput, DeriveInput) {
let mut new = derive_input.clone();
let mut orig = derive_input.clone();
let old_fields = borrow_fields(&mut orig);
let new_fields = borrow_fields(&mut new);
for (struct_index, (old_field, new_field)) in
old_fields.iter_mut().zip(new_fields.iter_mut()).enumerate()
{
let mut overriden_wrapping = false;
let mut wrapping_behavior =
!is_type_option(&old_field.ty) && global_options.default_wrapping_behavior;
let mut cfg_attribute = None;
let mut new_type = None;
let mut serde_skip = false;
old_field.attrs
.iter()
.for_each(|a| {
if a.path().is_ident(RENAME_ATTRIBUTE) {
let args = a
.parse_args()
.unwrap_or_else(|_| panic!("'{RENAME_ATTRIBUTE}' attribute expects one and only one argument (the new type to use)"));
new_type = Some(args);
if !overriden_wrapping {
wrapping_behavior = false;
}
} else if a.path().is_ident(SKIP_WRAP_ATTRIBUTE) {
wrapping_behavior = false;
overriden_wrapping = true;
} else if a.path().is_ident(WRAP_ATTRIBUTE) {
wrapping_behavior = true;
overriden_wrapping = true;
} else if a.path().is_ident(SERDE_SKIP_SERIALIZING_NONE) {
serde_skip = true;
} else if a.path().is_ident(CFG_ATTRIBUTE) {
cfg_attribute = Some(a.clone());
}
});
let field_ident = if let Some(ident) = &old_field.ident {
quote! {#ident}
} else {
let i = syn::Index::from(struct_index);
quote! {#i}
};
let field_options = FieldOptions {
wrapping_behavior,
cfg_attribute,
new_type,
field_ident,
serde_skip,
};
for v in &mut *visitors {
v.visit(global_options, old_field, new_field, &field_options);
}
}
(orig, new)
}
fn get_derive_macros(new: &DeriveInput, extra_derive: &[String]) -> TokenStream {
let mut extra_derive = extra_derive.iter().collect::<HashSet<_>>();
for attributes in &new.attrs {
let _ = attributes.parse_nested_meta(|derived_trait| {
let derived_trait = derived_trait.path;
let full_path = quote! { #derived_trait };
extra_derive.remove(&full_path.to_string());
Ok(())
});
}
let mut acc = quote! {};
for left_trait_to_derive in extra_derive {
let left_trait_to_derive = format_ident!("{left_trait_to_derive}");
acc = quote! { # left_trait_to_derive, # acc};
}
quote! { #[derive(#acc)] }
}
struct ParsedMacroParameters {
new_struct_name: Option<String>,
default_wrapping: bool,
}
impl Parse for ParsedMacroParameters {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut out = ParsedMacroParameters {
new_struct_name: None,
default_wrapping: true,
};
if let Ok(struct_name) = Ident::parse(input) {
out.new_struct_name = Some(struct_name.to_string());
} else {
return Ok(out);
};
if input.parse::<Token![,]>().is_err() {
return Ok(out);
};
if let Ok(wrapping) = syn::LitBool::parse(input) {
out.default_wrapping = wrapping.value;
} else {
return Ok(out);
};
Ok(out)
}
}
fn is_path_option(p: &Path) -> bool {
p.segments
.last()
.map(|ps| ps.ident == "Option")
.unwrap_or(false)
}
fn is_type_option(t: &Type) -> bool {
macro_rules! wtf {
($reason : tt) => {
panic!(
"Using OptionalStruct for a struct containing a {} is dubious...",
$reason
)
};
}
match &t {
Type::Path(type_path) => is_path_option(&type_path.path),
Type::Array(_) | Type::Tuple(_) => false,
Type::Paren(type_paren) => is_type_option(&type_paren.elem),
Type::ImplTrait(_) | Type::TraitObject(_) => {
panic!("Might already be an option I have no way to tell :/")
}
Type::Infer(_) => panic!("If you cannot tell, neither can I"),
Type::Macro(_) => panic!("Don't think I can handle this easily..."),
Type::Reference(_) => wtf!("reference"),
Type::Never(_) => wtf!("never-type"),
Type::Slice(_) => wtf!("slice"),
Type::Ptr(_) => wtf!("pointer"),
Type::BareFn(_) => wtf!("function pointer"),
Type::Verbatim(_) => todo!("Didn't get what this was supposed to be..."),
Type::Group(_) => todo!("Not sure what to do here"),
_ => panic!("Open an issue please :)"),
}
}
struct GlobalOptions {
new_struct_name: String,
extra_derive: Vec<String>,
default_wrapping_behavior: bool,
make_fields_public: bool,
}
impl GlobalOptions {
fn new(attr: ParsedMacroParameters, struct_definition: &DeriveInput) -> Self {
let new_struct_name = attr
.new_struct_name
.unwrap_or_else(|| "Optional".to_owned() + &struct_definition.ident.to_string());
let default_wrapping_behavior = attr.default_wrapping;
GlobalOptions {
new_struct_name,
extra_derive: vec!["Clone", "PartialEq", "Default", "Debug"]
.into_iter()
.map(|s| s.to_owned())
.collect(),
default_wrapping_behavior,
make_fields_public: true,
}
}
}
pub struct OptionalStructOutput {
pub original: TokenStream,
pub generated: TokenStream,
}
pub fn opt_struct(attr: TokenStream, input: TokenStream) -> OptionalStructOutput {
let derive_input = syn::parse2::<DeriveInput>(input).unwrap();
let macro_params = GlobalOptions::new(syn::parse2::<_>(attr).unwrap(), &derive_input);
let mut applicable_impl_generator = GenerateApplicableImplVisitor::new();
let mut try_from_generator = GenerateTryFromImpl::new();
let mut visitors = [
&mut RemoveHelperAttributesVisitor as &mut dyn OptionalFieldVisitor,
&mut SetNewFieldVisibilityVisitor,
&mut SetNewFieldTypeVisitor,
&mut AddSerdeSkipAttribute,
&mut applicable_impl_generator,
&mut try_from_generator,
];
let (orig, mut new) = visit_fields(&mut visitors, ¯o_params, &derive_input);
new.ident = Ident::new(¯o_params.new_struct_name, new.ident.span());
let try_from_impl = try_from_generator.get_implementation(&derive_input, &new);
let applicable_impl = applicable_impl_generator.get_implementation(&derive_input, &new);
let derives = get_derive_macros(&new, ¯o_params.extra_derive);
let generated = quote! {
#derives
#new
#applicable_impl
#try_from_impl
};
OptionalStructOutput {
original: quote! { #orig },
generated,
}
}