use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::parse::Parser;
use syn::parse::{Parse, ParseStream};
use syn::{
Data, DeriveInput, Error, Expr, Fields, Ident, Lit, LitStr, Meta, MetaList, Token, Variant,
parse_macro_input, punctuated::Punctuated,
};
pub fn macro_attribute_serde_literals(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match expand_serde_literals_attribute(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
pub fn macro_derive_literals(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand_literals(&input, LiteralsMode::Both) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
pub fn macro_derive_serialize_literals(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand_literals(&input, LiteralsMode::SerializeOnly) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
pub fn macro_derive_deserialize_literals(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand_literals(&input, LiteralsMode::DeserializeOnly) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
enum LiteralsMode {
Both,
SerializeOnly,
DeserializeOnly,
}
struct LiteralField {
ident: Ident,
rename: Option<LitStr>,
serialize_ty: proc_macro2::TokenStream,
deserialize_ty: proc_macro2::TokenStream,
value: proc_macro2::TokenStream,
expected_display: String,
}
fn expand_serde_literals_attribute(
mut input: DeriveInput,
) -> Result<proc_macro2::TokenStream, Error> {
let mode = consume_serde_derives(&mut input)?;
let impls = expand_literals(&input, mode)?;
strip_consumed_attrs(&mut input);
Ok(quote! {
#input
#impls
})
}
fn consume_serde_derives(input: &mut DeriveInput) -> Result<LiteralsMode, Error> {
let mut serialize = false;
let mut deserialize = false;
let mut attrs = Vec::new();
for attr in input.attrs.drain(..) {
let Meta::List(meta) = &attr.meta else {
attrs.push(attr);
continue;
};
if !meta.path.is_ident("derive") {
attrs.push(attr);
continue;
}
let parser = Punctuated::<syn::Path, Token![,]>::parse_terminated;
let derives = parser.parse2(meta.tokens.clone())?;
let mut kept = Vec::new();
for path in derives {
if path.is_ident("Serialize") {
serialize = true;
} else if path.is_ident("Deserialize") {
deserialize = true;
} else {
kept.push(path);
}
}
if !kept.is_empty() {
attrs.push(syn::parse_quote!(#[derive(#(#kept),*)]));
}
}
input.attrs = attrs;
match (serialize, deserialize) {
(true, true) => Ok(LiteralsMode::Both),
(true, false) => Ok(LiteralsMode::SerializeOnly),
(false, true) => Ok(LiteralsMode::DeserializeOnly),
(false, false) => Err(Error::new_spanned(
&input.ident,
"serde_literals requires deriving Serialize and/or Deserialize",
)),
}
}
fn strip_consumed_attrs(input: &mut DeriveInput) {
input
.attrs
.retain(|attr| !attr.path().is_ident("literals") && !attr.path().is_ident("serde"));
match &mut input.data {
Data::Struct(data) => {
for field in data.fields.iter_mut() {
field.attrs.retain(|attr| !attr.path().is_ident("serde"));
}
}
Data::Enum(data) => {
for variant in data.variants.iter_mut() {
variant.attrs.retain(|attr| {
!attr.path().is_ident("literal") && !attr.path().is_ident("serde")
});
for field in variant.fields.iter_mut() {
field.attrs.retain(|attr| !attr.path().is_ident("serde"));
}
}
}
Data::Union(data) => {
for field in data.fields.named.iter_mut() {
field.attrs.retain(|attr| !attr.path().is_ident("serde"));
}
}
}
}
fn expand_literals(
input: &DeriveInput,
mode: LiteralsMode,
) -> Result<proc_macro2::TokenStream, Error> {
if matches!(input.data, Data::Enum(_)) {
return expand_literal_enum(input, mode);
}
let struct_ident = &input.ident;
if input.generics.lifetimes().next().is_some() {
return Err(Error::new_spanned(
&input.generics,
"Literals does not support lifetime generics",
));
}
let literal_fields = parse_literal_fields(&input.attrs)?;
let fields = match &input.data {
syn::Data::Struct(data) => match &data.fields {
Fields::Named(named) => named.named.iter().cloned().collect::<Vec<_>>(),
_ => {
return Err(Error::new_spanned(
&data.fields,
"Literals only supports structs with named fields",
));
}
},
_ => {
return Err(Error::new_spanned(
&input.ident,
"Literals can only be used with structs",
));
}
};
if literal_fields.is_empty() {
return Err(Error::new_spanned(
&input.ident,
"Literals requires a #[literals(...)] attribute",
));
}
let mut field_names = std::collections::BTreeSet::new();
for field in &fields {
if let Some(ident) = &field.ident {
field_names.insert(ident.to_string());
}
}
for lit_field in &literal_fields {
let name = lit_field.ident.to_string();
if field_names.contains(&name) {
return Err(Error::new_spanned(
&lit_field.ident,
"Literal field conflicts with struct field name",
));
}
}
let serialize_ident = Ident::new(
&format!("__{}LiteralsSerialize", struct_ident),
struct_ident.span(),
);
let deserialize_ident = Ident::new(
&format!("__{}LiteralsDeserialize", struct_ident),
struct_ident.span(),
);
let literal_field_names: Vec<_> = literal_fields.iter().map(|field| &field.ident).collect();
let literal_field_attrs: Vec<_> = literal_fields
.iter()
.map(|field| match &field.rename {
Some(rename) => quote! { #[serde(rename = #rename)] },
None => quote! {},
})
.collect();
let literal_field_serialize_types: Vec<_> = literal_fields
.iter()
.map(|field| &field.serialize_ty)
.collect();
let literal_field_deserialize_types: Vec<_> = literal_fields
.iter()
.map(|field| &field.deserialize_ty)
.collect();
let literal_field_values: Vec<_> = literal_fields.iter().map(|field| &field.value).collect();
let serialize_fields = fields.iter().map(|field| {
let attrs = &field.attrs;
let ident = field.ident.as_ref().expect("Named field");
let ty = &field.ty;
quote! { #(#attrs)* #ident: &'__litty #ty }
});
let deserialize_fields = fields.iter().map(|field| {
let attrs = &field.attrs;
let ident = field.ident.as_ref().expect("Named field");
let ty = &field.ty;
quote! { #(#attrs)* #ident: #ty }
});
let serialize_init_fields = fields.iter().map(|field| {
let ident = field.ident.as_ref().expect("Named field");
quote! { #ident: &self.#ident }
});
let deserialize_init_fields = fields.iter().map(|field| {
let ident = field.ident.as_ref().expect("Named field");
quote! { #ident: helper.#ident }
});
let mut helper_generics = input.generics.clone();
if !fields.is_empty() {
helper_generics
.params
.insert(0, syn::parse_quote!('__litty));
}
let (helper_impl_generics, _helper_ty_generics, helper_where_clause) =
helper_generics.split_for_impl();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let serialize_where_clause = add_serialize_bounds(&input.generics);
let deserialize_generics = add_deserialize_lifetime(&input.generics);
let (deserialize_impl_generics, _deserialize_ty_generics, deserialize_where_clause) =
deserialize_generics.split_for_impl();
let literal_checks = literal_fields.iter().map(|field| {
let ident = &field.ident;
let value = &field.value;
let expected = &field.expected_display;
quote! {
if helper.#ident != #value {
return Err(serde::de::Error::custom(format!(
"expected {} = {}, got {:?}",
stringify!(#ident),
#expected,
helper.#ident
)));
}
}
});
let serialize_tokens = quote! {
#[derive(serde::Serialize)]
struct #serialize_ident #helper_impl_generics #helper_where_clause {
#(#serialize_fields,)*
#(#literal_field_attrs #literal_field_names: #literal_field_serialize_types,)*
}
impl #impl_generics serde::Serialize for #struct_ident #ty_generics #serialize_where_clause {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let helper = #serialize_ident {
#(#serialize_init_fields,)*
#(#literal_field_names: #literal_field_values,)*
};
helper.serialize(serializer)
}
}
};
let deserialize_tokens = quote! {
#[derive(serde::Deserialize)]
struct #deserialize_ident #impl_generics #where_clause {
#(#deserialize_fields,)*
#(#literal_field_attrs #literal_field_names: #literal_field_deserialize_types,)*
}
impl #deserialize_impl_generics serde::Deserialize<'__de> for #struct_ident #ty_generics #deserialize_where_clause {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'__de>,
{
let helper = #deserialize_ident::deserialize(deserializer)?;
#(#literal_checks)*
Ok(Self {
#(#deserialize_init_fields,)*
})
}
}
};
Ok(match mode {
LiteralsMode::Both => quote! {
#serialize_tokens
#deserialize_tokens
},
LiteralsMode::SerializeOnly => serialize_tokens,
LiteralsMode::DeserializeOnly => deserialize_tokens,
})
}
struct EnumLiteralVariant {
ident: Ident,
lit: Lit,
}
struct EnumLiteralVariantHelper {
ident: Ident,
helper_ident: Ident,
trait_ident: proc_macro2::TokenStream,
}
fn expand_literal_enum(
input: &DeriveInput,
mode: LiteralsMode,
) -> Result<proc_macro2::TokenStream, Error> {
let enum_ident = &input.ident;
if input.generics.lifetimes().next().is_some() {
return Err(Error::new_spanned(
&input.generics,
"Literals does not support lifetime generics for enums",
));
}
let Data::Enum(data_enum) = &input.data else {
unreachable!("expand_literal_enum is called only for enums")
};
let mut variants: Vec<EnumLiteralVariant> = vec![];
let mut non_literal_variants: Vec<&Variant> = vec![];
for variant in &data_enum.variants {
let mut literal: Option<Lit> = None;
for attr in &variant.attrs {
if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta {
let is_literal = path
.segments
.iter()
.last()
.is_some_and(|segment| segment.ident == "literal");
if is_literal {
if !matches!(variant.fields, Fields::Unit) {
return Err(Error::new_spanned(
&variant.ident,
"SerializeLiterals/DeserializeLiterals only support unit enum variants with #[literal(...)]",
));
}
literal = Some(syn::parse2::<Lit>(tokens.clone()).map_err(|_| {
Error::new_spanned(
tokens,
"literal enum variant value must be a string, bool, int, or float literal",
)
})?);
}
}
}
if let Some(lit) = literal {
variants.push(EnumLiteralVariant {
ident: variant.ident.clone(),
lit,
});
} else {
non_literal_variants.push(variant);
}
}
if variants.is_empty() {
return Err(Error::new_spanned(
enum_ident,
"SerializeLiterals/DeserializeLiterals enum requires at least one #[literal(...)] unit variant",
));
}
let (impl_generics, ty_generics, _where_clause) = input.generics.split_for_impl();
let serialize_where_clause = add_serialize_bounds(&input.generics);
let deserialize_generics = add_deserialize_lifetime(&input.generics);
let (deserialize_impl_generics, _deserialize_ty_generics, deserialize_where_clause) =
deserialize_generics.split_for_impl();
let mut helper_structs = quote! {};
let mut serialize_arms = quote! {};
let mut deserialize_arms = quote! {};
let mut literal_helpers = Vec::new();
let helper_suffix = match mode {
LiteralsMode::Both => "Serde",
LiteralsMode::SerializeOnly => "Serialize",
LiteralsMode::DeserializeOnly => "Deserialize",
};
let literal_value_ident = Ident::new(
&format!("__{}LittyLiteralValue{}", enum_ident, helper_suffix),
enum_ident.span(),
);
let literal_visitor_ident = Ident::new(
&format!("__{}LittyLiteralVisitor{}", enum_ident, helper_suffix),
enum_ident.span(),
);
for variant in &variants {
let variant_ident = &variant.ident;
let helper_ident = Ident::new(
&format!(
"__{}{}LittyLiteral{}",
enum_ident, variant_ident, helper_suffix
),
variant_ident.span(),
);
let (trait_ident, literal_type, literal_value) = literal_trait_def(&variant.lit)?;
literal_helpers.push(EnumLiteralVariantHelper {
ident: variant_ident.clone(),
helper_ident: helper_ident.clone(),
trait_ident: trait_ident.clone(),
});
helper_structs.extend(quote! {
struct #helper_ident;
impl litty::#trait_ident for #helper_ident {
const LIT: #literal_type = #literal_value;
}
});
serialize_arms.extend(quote! {
#enum_ident::#variant_ident => <#helper_ident as litty::#trait_ident>::lit_serialize(serializer),
});
let deserialize_match = match &variant.lit {
Lit::Str(_) => quote! {
#literal_value_ident::Str(value) if value == <#helper_ident as litty::#trait_ident>::LIT => {
return Ok(#enum_ident::#variant_ident);
}
},
Lit::Bool(_) => quote! {
#literal_value_ident::Bool(value) if value == <#helper_ident as litty::#trait_ident>::LIT => {
return Ok(#enum_ident::#variant_ident);
}
},
Lit::Int(_) => quote! {
#literal_value_ident::Int(value) if value == <#helper_ident as litty::#trait_ident>::LIT => {
return Ok(#enum_ident::#variant_ident);
}
},
Lit::Float(_) => quote! {
#literal_value_ident::Float(value) if value == <#helper_ident as litty::#trait_ident>::LIT => {
return Ok(#enum_ident::#variant_ident);
}
},
_ => unreachable!(),
};
deserialize_arms.extend(deserialize_match);
}
for variant in &non_literal_variants {
serialize_arms.extend(non_literal_variant_serialize_arm(enum_ident, variant)?);
}
let serialize_impl = quote! {
impl #impl_generics serde::Serialize for #enum_ident #ty_generics #serialize_where_clause {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
#serialize_arms
_ => Err(serde::ser::Error::custom(
"SerializeLiterals only supports enum variants annotated with #[literal(...)]",
)),
}
}
}
};
let deserialize_impl = if non_literal_variants.is_empty() {
quote! {
enum #literal_value_ident {
Str(String),
Bool(bool),
Int(i64),
Float(f64),
}
struct #literal_visitor_ident;
impl<'de> serde::de::Visitor<'de> for #literal_visitor_ident {
type Value = #literal_value_ident;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string, bool, integer, or float literal")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(#literal_value_ident::Str(value.to_owned()))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(#literal_value_ident::Str(value))
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(#literal_value_ident::Bool(value))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(#literal_value_ident::Int(value))
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let value = i64::try_from(value)
.map_err(|_| E::custom("integer literal is out of i64 range"))?;
Ok(#literal_value_ident::Int(value))
}
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(#literal_value_ident::Float(value))
}
}
impl #deserialize_impl_generics serde::Deserialize<'__de> for #enum_ident #ty_generics #deserialize_where_clause {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'__de>,
{
let value = deserializer.deserialize_any(#literal_visitor_ident)?;
match value {
#deserialize_arms
_ => Err(serde::de::Error::custom(
"unknown literal enum variant",
)),
}
}
}
}
} else {
let helper_enum_ident = Ident::new(
&format!("__{}LittyUntagged{}", enum_ident, helper_suffix),
enum_ident.span(),
);
let helper_variants = data_enum
.variants
.iter()
.map(|variant| helper_enum_variant(variant, &literal_helpers))
.collect::<Result<Vec<_>, _>>()?;
let helper_match_arms = data_enum
.variants
.iter()
.map(|variant| helper_enum_match_arm(enum_ident, &helper_enum_ident, variant))
.collect::<Result<Vec<_>, _>>()?;
quote! {
impl #deserialize_impl_generics serde::Deserialize<'__de> for #enum_ident #ty_generics #deserialize_where_clause {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'__de>,
{
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum #helper_enum_ident #impl_generics {
#(#helper_variants),*
}
let value = <#helper_enum_ident #ty_generics as serde::Deserialize>::deserialize(deserializer)?;
Ok(match value {
#(#helper_match_arms),*
})
}
}
}
};
Ok(match mode {
LiteralsMode::Both => quote! {
#helper_structs
#serialize_impl
#deserialize_impl
},
LiteralsMode::SerializeOnly => quote! {
#helper_structs
#serialize_impl
},
LiteralsMode::DeserializeOnly => quote! {
#helper_structs
#deserialize_impl
},
})
}
fn helper_enum_variant(
variant: &Variant,
literal_helpers: &[EnumLiteralVariantHelper],
) -> Result<proc_macro2::TokenStream, Error> {
let variant_ident = &variant.ident;
let fields = &variant.fields;
if let Some(helper) = literal_helpers
.iter()
.find(|helper| helper.ident == *variant_ident)
{
let helper_ident = &helper.helper_ident;
let trait_ident = &helper.trait_ident;
let deserialize_with = LitStr::new(
&format!(
"<{helper_ident} as litty::{}>::lit_deserialize",
trait_ident
),
variant_ident.span(),
);
return Ok(quote! {
#[serde(deserialize_with = #deserialize_with)]
#variant_ident
});
}
Ok(quote! { #variant_ident #fields })
}
fn helper_enum_match_arm(
enum_ident: &Ident,
helper_enum_ident: &Ident,
variant: &Variant,
) -> Result<proc_macro2::TokenStream, Error> {
let variant_ident = &variant.ident;
Ok(match &variant.fields {
Fields::Unit => quote! {
#helper_enum_ident::#variant_ident => #enum_ident::#variant_ident
},
Fields::Unnamed(fields) => {
let bindings = (0..fields.unnamed.len())
.map(|index| Ident::new(&format!("field_{index}"), variant_ident.span()))
.collect::<Vec<_>>();
quote! {
#helper_enum_ident::#variant_ident(#(#bindings),*) => {
#enum_ident::#variant_ident(#(#bindings),*)
}
}
}
Fields::Named(fields) => {
let bindings = fields
.named
.iter()
.map(|field| {
field.ident.clone().ok_or_else(|| {
Error::new_spanned(field, "named enum fields must have identifiers")
})
})
.collect::<Result<Vec<_>, _>>()?;
quote! {
#helper_enum_ident::#variant_ident { #(#bindings),* } => {
#enum_ident::#variant_ident { #(#bindings),* }
}
}
}
})
}
fn non_literal_variant_serialize_arm(
enum_ident: &Ident,
variant: &Variant,
) -> Result<proc_macro2::TokenStream, Error> {
let variant_ident = &variant.ident;
Ok(match &variant.fields {
Fields::Unit => quote! {
#enum_ident::#variant_ident => serde::Serialize::serialize(&(), serializer),
},
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => quote! {
#enum_ident::#variant_ident(value) => serde::Serialize::serialize(value, serializer),
},
Fields::Unnamed(fields) => {
let bindings = (0..fields.unnamed.len())
.map(|index| Ident::new(&format!("field_{index}"), variant_ident.span()))
.collect::<Vec<_>>();
quote! {
#enum_ident::#variant_ident(#(#bindings),*) => {
serde::Serialize::serialize(&(#(#bindings),*), serializer)
},
}
}
Fields::Named(fields) => {
let bindings = fields
.named
.iter()
.map(|field| {
field.ident.clone().ok_or_else(|| {
Error::new_spanned(field, "named enum fields must have identifiers")
})
})
.collect::<Result<Vec<_>, _>>()?;
let field_names = bindings
.iter()
.map(|ident| ident.to_string())
.collect::<Vec<_>>();
let fields_len = bindings.len();
quote! {
#enum_ident::#variant_ident { #(#bindings),* } => {
let mut map = serde::Serializer::serialize_map(serializer, Some(#fields_len))?;
#(
serde::ser::SerializeMap::serialize_entry(&mut map, #field_names, #bindings)?;
)*
serde::ser::SerializeMap::end(map)
},
}
}
})
}
fn literal_trait_def(
lit: &Lit,
) -> Result<
(
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
),
Error,
> {
match lit {
Lit::Str(lit_str) => Ok((
quote! { LitStr },
quote! { &'static str },
quote! { #lit_str },
)),
Lit::Bool(lit_bool) => Ok((quote! { LitBool }, quote! { bool }, quote! { #lit_bool })),
Lit::Int(lit_int) => {
let value = lit_int
.base10_digits()
.parse::<i64>()
.map_err(|_| Error::new_spanned(lit_int, "Invalid i64 literal"))?;
Ok((quote! { LitInt }, quote! { i64 }, quote! { #value }))
}
Lit::Float(lit_float) => {
let value = lit_float
.base10_digits()
.parse::<f64>()
.map_err(|_| Error::new_spanned(lit_float, "Invalid f64 literal"))?;
Ok((quote! { LitFloat }, quote! { f64 }, quote! { #value }))
}
_ => Err(Error::new_spanned(
lit,
"Literal values only support string, bool, int, or float literals",
)),
}
}
fn parse_literal_fields(attrs: &[syn::Attribute]) -> Result<Vec<LiteralField>, Error> {
let mut literal_fields = Vec::new();
for attr in attrs {
let Meta::List(MetaList { path, tokens, .. }) = &attr.meta else {
continue;
};
if !path.is_ident("literals") {
continue;
}
let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
let items = parser.parse2(tokens.clone())?;
for item in items {
let (ident, rename, value_expr) = match item {
Meta::NameValue(item) => {
let ident = literal_field_ident(item.path)?;
(ident, None, item.value)
}
Meta::List(item) => {
let ident = literal_field_ident(item.path)?;
let args = syn::parse2::<LiteralFieldArgs>(item.tokens)?;
(ident, args.rename, args.value)
}
Meta::Path(path) => {
return Err(Error::new_spanned(
path,
"Literal field must be `name = value` or `name(value, rename = \"wireName\")`",
));
}
};
let (serialize_ty, deserialize_ty, value, expected_display) =
literal_type_and_value(&value_expr)?;
literal_fields.push(LiteralField {
ident,
rename,
serialize_ty,
deserialize_ty,
value,
expected_display,
});
}
}
Ok(literal_fields)
}
struct LiteralFieldArgs {
value: Expr,
rename: Option<LitStr>,
}
impl Parse for LiteralFieldArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let value = input.parse::<Expr>()?;
let mut rename = None;
while input.peek(Token![,]) {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
let ident = input.parse::<Ident>()?;
input.parse::<Token![=]>()?;
if ident == "rename" {
rename = Some(input.parse::<LitStr>()?);
} else {
return Err(Error::new_spanned(
ident,
"Unsupported literal field option",
));
}
}
Ok(Self { value, rename })
}
}
fn literal_field_ident(path: syn::Path) -> Result<Ident, Error> {
path.get_ident()
.cloned()
.ok_or_else(|| Error::new_spanned(path, "Literal field name must be an identifier"))
}
fn literal_type_and_value(
value: &syn::Expr,
) -> Result<
(
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
String,
),
Error,
> {
match value {
syn::Expr::Path(expr_path)
if expr_path.path.segments.len() == 1
&& expr_path
.path
.segments
.first()
.is_some_and(|segment| segment.ident == "null") =>
{
Ok((quote! { () }, quote! { () }, quote! { () }, "null".into()))
}
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
Lit::Str(lit_str) => Ok((
quote! { &'static str },
quote! { String },
quote! { #lit_str },
lit_str.to_token_stream().to_string(),
)),
Lit::Bool(lit_bool) => Ok((
quote! { bool },
quote! { bool },
quote! { #lit_bool },
lit_bool.to_token_stream().to_string(),
)),
Lit::Int(lit_int) => {
let value = lit_int
.base10_digits()
.parse::<i64>()
.map_err(|_| Error::new_spanned(lit_int, "Invalid i64 literal"))?;
Ok((
quote! { i64 },
quote! { i64 },
quote! { #value },
lit_int.to_token_stream().to_string(),
))
}
Lit::Float(lit_float) => {
let value = lit_float
.base10_digits()
.parse::<f64>()
.map_err(|_| Error::new_spanned(lit_float, "Invalid f64 literal"))?;
Ok((
quote! { f64 },
quote! { f64 },
quote! { #value },
lit_float.to_token_stream().to_string(),
))
}
_ => Err(Error::new_spanned(
&expr_lit.lit,
"Literals only supports null, string, bool, int, or float literals",
)),
},
_ => Err(Error::new_spanned(
value,
"Literal field value must be a literal",
)),
}
}
fn add_serialize_bounds(generics: &syn::Generics) -> Option<syn::WhereClause> {
let mut where_clause = generics
.where_clause
.clone()
.unwrap_or_else(|| syn::WhereClause {
where_token: Default::default(),
predicates: Punctuated::new(),
});
for param in &generics.params {
if let syn::GenericParam::Type(ty) = param {
let ident = &ty.ident;
where_clause
.predicates
.push(syn::parse_quote!(#ident: serde::Serialize));
}
}
if where_clause.predicates.is_empty() {
None
} else {
Some(where_clause)
}
}
fn add_deserialize_lifetime(generics: &syn::Generics) -> syn::Generics {
let mut generics = generics.clone();
generics.params.insert(0, syn::parse_quote!('__de));
let mut where_clause = generics
.where_clause
.take()
.unwrap_or_else(|| syn::WhereClause {
where_token: Default::default(),
predicates: Punctuated::new(),
});
for param in &generics.params {
if let syn::GenericParam::Type(ty) = param {
let ident = &ty.ident;
where_clause
.predicates
.push(syn::parse_quote!(#ident: serde::Deserialize<'__de>));
}
}
if where_clause.predicates.is_empty() {
generics.where_clause = None;
} else {
generics.where_clause = Some(where_clause);
}
generics
}