Auto-derive Default for KeyGroups

This commit is contained in:
Joscha 2023-04-29 01:50:33 +02:00
parent f3efff68f5
commit 03c7fb567c
4 changed files with 63 additions and 151 deletions

View file

@ -1,14 +1,9 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{Data, DataEnum, DataStruct, DeriveInput, ExprPath, Field, Ident, LitStr, Type};
use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr};
use crate::util;
enum SerdeDefault {
Default(Type),
Path(ExprPath),
}
use crate::util::{self, SerdeDefault};
#[derive(Default)]
struct FieldInfo {
@ -53,17 +48,7 @@ impl FieldInfo {
}
// Find `#[serde(default)]` or `#[serde(default = "bla")]`.
for arg in util::attribute_arguments(field, "serde")? {
if arg.path.is_ident("default") {
if let Some(value) = arg.value {
if let Some(path) = util::into_litstr(value) {
self.serde_default = Some(SerdeDefault::Path(path.parse()?));
}
} else {
self.serde_default = Some(SerdeDefault::Default(field.ty.clone()));
}
}
}
self.serde_default = util::serde_default(field)?;
Ok(())
}
@ -102,13 +87,9 @@ fn from_struct(ident: Ident, data: DataStruct) -> syn::Result<TokenStream> {
doc.value_info.default = Some(#default.to_string());
});
} else if let Some(serde_default) = info.serde_default {
setters.push(match serde_default {
SerdeDefault::Default(ty) => quote! {
doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&<#ty as Default>::default()));
},
SerdeDefault::Path(path) => quote! {
doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&#path()));
},
let value = serde_default.value();
setters.push(quote! {
doc.value_info.default = Some(crate::doc::toml_value_as_markdown(&#value));
});
}

View file

@ -3,7 +3,7 @@ use quote::quote;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput};
use crate::util;
use crate::util::{self, bail};
fn decapitalize(s: &str) -> String {
let mut chars = s.chars();
@ -20,15 +20,26 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
};
let mut bindings = vec![];
let mut defaults = vec![];
for field in &data.fields {
if let Some(field_ident) = &field.ident {
let docstring = util::docstring(field)?;
let description = decapitalize(&docstring);
let description = description.strip_suffix('.').unwrap_or(&description);
let default = util::serde_default(field)?;
let Some(default) = default else {
return bail(field_ident.span(), "must have serde default");
};
let default_value = default.value();
bindings.push(quote! {
(&self.#field_ident, #description)
});
defaults.push(quote! {
#field_ident: #default_value,
});
}
}
@ -41,5 +52,13 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
]
}
}
impl Default for #ident {
fn default() -> Self {
Self {
#( #defaults )*
}
}
}
})
}

View file

@ -1,7 +1,8 @@
use proc_macro2::Span;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::Parse;
use syn::punctuated::Punctuated;
use syn::{Expr, ExprLit, Field, Lit, LitStr, Path, Token};
use syn::{Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type};
pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> {
Err(syn::Error::new(span, message))
@ -80,3 +81,37 @@ pub fn attribute_arguments(field: &Field, path: &str) -> syn::Result<Vec<Attribu
Ok(attrs)
}
pub enum SerdeDefault {
Default(Type),
Path(ExprPath),
}
impl SerdeDefault {
pub fn value(&self) -> TokenStream {
match self {
Self::Default(ty) => quote! {
<#ty as Default>::default()
},
Self::Path(path) => quote! {
#path()
},
}
}
}
/// Find `#[serde(default)]` or `#[serde(default = "bla")]`.
pub fn serde_default(field: &Field) -> syn::Result<Option<SerdeDefault>> {
for arg in attribute_arguments(field, "serde")? {
if arg.path.is_ident("default") {
if let Some(value) = arg.value {
if let Some(path) = into_litstr(value) {
return Ok(Some(SerdeDefault::Path(path.parse()?)));
}
} else {
return Ok(Some(SerdeDefault::Default(field.ty.clone())));
}
}
}
Ok(None)
}