use std::collections::HashSet;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::ext::IdentExt as _;
use syn::{Data, DeriveInput, Error, Fields, Path, Type};
use crate::attrs::{ContainerAttrs, DefaultValue, FieldAttrs};
#[derive(Clone, Copy)]
pub(crate) struct Ctx<'a> {
pub(crate) root: &'a Path,
pub(crate) container: &'a ContainerAttrs,
pub(crate) ident: &'a syn::Ident,
pub(crate) interner: &'a syn::Ident,
pub(crate) hygiene: &'a HashSet<String>,
}
#[derive(Clone, Copy)]
pub(crate) struct SerializeCtx<'a> {
pub(crate) root: &'a Path,
pub(crate) container: &'a ContainerAttrs,
pub(crate) ident: &'a syn::Ident,
pub(crate) hygiene: &'a HashSet<String>,
}
pub(crate) fn field_seed(
root: &Path,
interner: &syn::Ident,
ty: &Type,
attrs: &FieldAttrs,
with_seed: Option<&syn::Ident>,
interner_access: &TokenStream2,
) -> TokenStream2 {
if attrs.via_serde {
quote!(#root::DeserializeSeed::<#ty>::new())
} else if let Some(with_seed) = with_seed {
quote!(#with_seed)
} else {
quote!(#root::DeserializeInSeed::<#ty, #interner>::new(&mut *#interner_access))
}
}
pub(crate) fn with_seed_def(root: &Path, name: &syn::Ident, ty: &Type, attrs: &FieldAttrs) -> Option<TokenStream2> {
let path = attrs.with.as_ref()?;
let serde = quote!(#root::__private::serde);
Some(quote! {
struct #name;
impl<'de> #serde::de::DeserializeSeed<'de> for #name {
type Value = #ty;
fn deserialize<__D>(self, __d: __D) -> ::core::result::Result<Self::Value, __D::Error>
where
__D: #serde::Deserializer<'de>,
{
#path(__d)
}
}
})
}
pub(crate) fn missing_value_expr(
ident: &syn::Ident,
attrs: &FieldAttrs,
container: &ContainerAttrs,
container_default_binding: &TokenStream2,
missing: &TokenStream2,
) -> TokenStream2 {
match &attrs.default {
Some(DefaultValue::Trait) => quote!(::core::default::Default::default()),
Some(DefaultValue::Path(path)) => quote!(#path()),
None if container.default.is_some() => quote!(#container_default_binding.#ident),
None => missing.clone(),
}
}
pub(crate) fn tuple_missing_value_expr(
index: &syn::Index,
attrs: &FieldAttrs,
container: &ContainerAttrs,
container_default_binding: &TokenStream2,
missing: &TokenStream2,
) -> TokenStream2 {
match &attrs.default {
Some(default) => default_value_expr(default),
None if container.default.is_some() => quote!(#container_default_binding.#index),
None => missing.clone(),
}
}
pub(crate) fn skip_default_expr(attrs: &FieldAttrs) -> TokenStream2 {
if let Some(DefaultValue::Path(path)) = &attrs.default {
quote!(#path())
} else {
quote!(::core::default::Default::default())
}
}
pub(crate) fn default_value_expr(default: &DefaultValue) -> TokenStream2 {
match default {
DefaultValue::Trait => quote!(::core::default::Default::default()),
DefaultValue::Path(path) => quote!(#path()),
}
}
pub(crate) fn container_name(ident: &syn::Ident, rename: Option<&str>) -> String {
rename.map_or_else(|| ident.unraw().to_string(), str::to_owned)
}
pub(crate) fn is_phantom_data(ty: &Type) -> bool {
let Type::Path(path) = ty else {
return false;
};
path.qself.is_none() && path.path.segments.last().is_some_and(|segment| segment.ident == "PhantomData")
}
pub(crate) fn transparent_deserialize_target(plan: &NamedPlan) -> bool {
!plan.attrs.skip && plan.attrs.default.is_none() && !is_phantom_data(&plan.ty)
}
pub(crate) fn transparent_serialize_target(plan: &NamedPlan) -> bool {
!plan.attrs.skip_serializing && !is_phantom_data(&plan.ty)
}
pub(crate) fn transparent_other_expr(plan: &NamedPlan) -> TokenStream2 {
if plan.attrs.skip {
skip_default_expr(&plan.attrs)
} else if let Some(default) = &plan.attrs.default {
default_value_expr(default)
} else {
quote!(::core::default::Default::default())
}
}
pub(crate) fn validate_transparent_container(input: &DeriveInput, container: &ContainerAttrs) -> syn::Result<()> {
if !container.transparent {
return Ok(());
}
let Data::Struct(data) = &input.data else {
return Err(Error::new_spanned(input, "serde `transparent` is supported only for structs"));
};
match &data.fields {
Fields::Named(_) => Ok(()),
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(()),
Fields::Unnamed(_) | Fields::Unit => Err(Error::new_spanned(
&input.ident,
"serde `transparent` requires a single-field tuple struct or a struct with exactly one transparent field",
)),
}
}
pub(crate) struct NamedPlan {
pub(crate) ident: syn::Ident,
pub(crate) ty: Type,
pub(crate) attrs: FieldAttrs,
pub(crate) wire_name: String,
pub(crate) serialize_wire_name: String,
pub(crate) with_seed: Option<syn::Ident>,
pub(crate) serialize_with_adapter: Option<syn::Ident>,
pub(crate) binding: syn::Ident,
pub(crate) slot: syn::Ident,
}
pub(crate) struct TuplePlan {
pub(crate) index: syn::Index,
pub(crate) ty: Type,
pub(crate) attrs: FieldAttrs,
pub(crate) with_seed: Option<syn::Ident>,
pub(crate) serialize_with_adapter: Option<syn::Ident>,
pub(crate) binding: syn::Ident,
}
pub(crate) fn serialize_with_adapter_def(root: &Path, name: &syn::Ident, ty: &Type, path: &Path) -> TokenStream2 {
let serde = quote!(#root::__private::serde);
quote! {
struct #name<'a>(&'a #ty);
impl<'a> #serde::Serialize for #name<'a> {
fn serialize<__S>(&self, __serializer: __S) -> ::core::result::Result<__S::Ok, __S::Error>
where
__S: #serde::Serializer,
{
#path(self.0, __serializer)
}
}
}
}
pub(crate) fn reject_conflicting_deserialize_modes<T: quote::ToTokens>(spanned: T, attrs: &FieldAttrs) -> syn::Result<()> {
if attrs.via_serde && attrs.with.is_some() {
return Err(Error::new_spanned(
&spanned,
"`#[internity(via_serde)]` and serde `deserialize_with`/`with` are mutually exclusive for `DeserializeIn`",
));
}
if attrs.skip && (attrs.via_serde || attrs.with.is_some()) {
return Err(Error::new_spanned(
&spanned,
"serde `skip`/`skip_deserializing` cannot be combined with a custom deserializer mode \
(`via_serde`/`deserialize_with`/`with`)",
));
}
Ok(())
}
pub(crate) fn reject_conflicting_serialize_modes<T: quote::ToTokens>(spanned: T, attrs: &FieldAttrs) -> syn::Result<()> {
if attrs.via_serde && attrs.serialize_with.is_some() {
return Err(Error::new_spanned(
spanned,
"`#[internity(via_serde)]` and serde `serialize_with`/`with` are mutually exclusive for `SerializeIn`",
));
}
Ok(())
}
pub(crate) fn reject_skip_serializing_if<T: quote::ToTokens>(spanned: T, attrs: &FieldAttrs) -> syn::Result<()> {
if attrs.skip_serializing_if && !attrs.skip_serializing {
return Err(Error::new_spanned(
spanned,
"internity::SerializeIn does not support serde `skip_serializing_if`: a runtime skip \
predicate would diverge from the type's ordinary Serde wire schema. Remove the \
attribute, or use `#[serde(skip_serializing)]` to always omit the field.",
));
}
Ok(())
}
pub(crate) fn serialize_field_expr(root: &Path, access: &TokenStream2, attrs: &FieldAttrs, adapter: Option<&syn::Ident>) -> TokenStream2 {
if let Some(adapter) = adapter {
quote!(#adapter(&self.#access))
} else if attrs.via_serde {
quote!(&self.#access)
} else {
quote!(#root::__private::SerializeInWith::new(&self.#access, __reader))
}
}
pub(crate) fn serialize_direct_call(root: &Path, serde: &TokenStream2, access: &TokenStream2, attrs: &FieldAttrs) -> TokenStream2 {
if let Some(path) = &attrs.serialize_with {
quote!(#path(&self.#access, __serializer))
} else if attrs.via_serde {
quote!(#serde::Serialize::serialize(&self.#access, __serializer))
} else {
quote!(#root::__private::SerializeIn::serialize_in(&self.#access, __reader, __serializer))
}
}