use crate::{
ast::{attr::MainField, types},
expand::helpers,
symbol,
};
use quote::quote;
use quote::ToTokens;
pub(super) fn gen_partial_struct(
name: &syn::Ident,
partial_name: &syn::Ident,
vis: &syn::Visibility,
generics: &syn::Generics,
fatts: &[MainField],
) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let field_decls = fatts.iter().filter_map(|field| {
field.attrs.as_ref()?;
let MainField {
name: field_name,
ty,
..
} = field;
let ty = helpers::unwrap_option_type(ty).unwrap_or(ty);
Some(quote! {
pub #field_name: ::core::option::Option<#ty>,
})
});
let type_params: Vec<&syn::Ident> = generics
.params
.iter()
.filter_map(|p| match p {
syn::GenericParam::Type(tp) => Some(&tp.ident),
_ => None,
})
.collect();
let lifetime_params: Vec<&syn::Lifetime> = generics
.params
.iter()
.filter_map(|p| match p {
syn::GenericParam::Lifetime(lp) => Some(&lp.lifetime),
_ => None,
})
.collect();
let (phantom_field, phantom_default) = if type_params.is_empty() && lifetime_params.is_empty() {
(quote! {}, quote! {})
} else {
let lt_refs = lifetime_params.iter().map(|lt| quote! { & #lt () });
(
quote! {
#[doc(hidden)]
pub __tinyklv_phantom: ::core::marker::PhantomData<(
#(#lt_refs,)*
#(#type_params,)*
)>,
},
quote! {
#[doc(hidden)]
__tinyklv_phantom: ::core::marker::PhantomData,
},
)
};
let default_inits = fatts.iter().filter_map(|field| {
let attrs = field.attrs.as_ref()?;
let MainField {
name: field_name,
ty,
..
} = field;
let default = attrs.default.clone();
let ty = helpers::unwrap_option_type(ty).unwrap_or(ty);
let init = match default {
Some(types::DefaultValue::Expr(expr)) => quote! {
::core::option::Option::<#ty>::Some(#expr)
},
Some(types::DefaultValue::Call) => quote! {
::core::option::Option::<#ty>::Some(<#ty as ::core::default::Default>::default())
},
None => quote! {
::core::option::Option::<#ty>::None
},
};
Some(quote! { #field_name: #init, })
});
quote! {
#[doc(hidden)]
#[automatically_derived]
#[doc = concat!(" In-flight partial packet for [`", stringify!(#name), "`]. Mirror of the struct with every klv field as `Option<T>` so the decode loop can fill it incrementally and resume across `Packet::NeedMore` boundaries via [`tinyklv::Decoder`]")]
#[derive(::core::fmt::Debug)]
#vis struct #partial_name #impl_generics #where_clause {
#(#field_decls)*
#phantom_field
}
#[automatically_derived]
impl #impl_generics ::core::default::Default for #partial_name #ty_generics #where_clause {
#[inline(always)]
fn default() -> Self {
Self {
#(#default_inits)*
#phantom_default
}
}
}
}
}
pub(super) fn gen_partial_impl(
struct_name: &syn::Ident,
partial_name: &syn::Ident,
generics: &syn::Generics,
fields: &Vec<MainField>,
) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let default_symbol = symbol::DEFAULT_VALUE.to_token_stream();
let required_guards = fields
.iter()
.filter(|f| f.attrs.is_some())
.filter(|f| !helpers::is_option(f.ty))
.map(|field| {
let MainField { name, .. } = field;
quote! {
let #name = match self.#name {
Some(v) => v,
None => return ::core::result::Result::Err(
concat!(
"`",
stringify!(#struct_name),
"::",
stringify!(#name),
"` is a required value missing from the packet. To prevent this, this field can be set as optional or an `",
stringify!(#default_symbol),
"` See tinyklv docs for more information.",
)
),
};
}
});
let optional_passes = fields
.iter()
.filter(|f| f.attrs.is_some())
.filter(|f| helpers::is_option(f.ty))
.map(|field| {
let MainField { name, .. } = field;
quote! {
let #name = self.#name;
}
});
let klv_field_names = fields
.iter()
.filter(|f| f.attrs.is_some())
.map(|f| f.name.clone());
let elem_name_type_without_klv = fields
.iter()
.filter_map(|f| match &f.attrs {
Some(_) => None,
None => Some((f.name.clone(), f.ty)),
})
.collect::<Vec<_>>();
let default_fields = if elem_name_type_without_klv.is_empty() {
quote! {}
} else {
let names = elem_name_type_without_klv.iter().map(|(n, _)| n.clone());
let types = elem_name_type_without_klv
.iter()
.map(|(_, ty)| helpers::type2fish(ty));
quote! { #(#names: #types::default(),)* }
};
quote! {
#[automatically_derived]
impl #impl_generics ::tinyklv::traits::Partial for #partial_name #ty_generics #where_clause {
type Final = #struct_name #ty_generics;
fn finalize(self) -> ::core::result::Result<
#struct_name #ty_generics,
&'static str,
> {
#(#required_guards)*
#(#optional_passes)*
::core::result::Result::Ok(#struct_name {
#(#klv_field_names,)*
#default_fields
})
}
}
}
}
pub(super) fn gen_try_from_partial_impl(
struct_name: &syn::Ident,
partial_name: &syn::Ident,
generics: &syn::Generics,
) -> proc_macro2::TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_generics ::core::convert::TryFrom<#partial_name #ty_generics> for #struct_name #ty_generics #where_clause {
type Error = &'static str;
#[inline(always)]
fn try_from(p: #partial_name #ty_generics) -> ::core::result::Result<Self, Self::Error> {
<#partial_name #ty_generics as ::tinyklv::traits::Partial>::finalize(p)
}
}
}
}