use std::collections::HashSet;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
DeriveInput, Ident, ItemStruct, Meta, Token, Visibility, parse::Parse, parse_macro_input,
parse2, punctuated::Punctuated,
};
use crate::field::{AttributeParam, CongenDefault, CongenField};
mod field;
#[proc_macro_derive(ValueEnumConfiguration)]
pub fn value_enum_configuration(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ty = &input.ident;
if !matches!(input.data, syn::Data::Enum(_)) {
return quote! { compile_error!("ValueEnumConfiguration can only be derived for enums") }
.into();
}
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
quote! {
impl #impl_generics congen::clap::ValueEnumConfiguration for #ty #ty_generics #where_clause {}
}
.into()
}
#[proc_macro_derive(Configuration, attributes(congen))]
pub fn configuration(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut errors = Vec::new();
let input = parse_macro_input!(input as ItemStruct);
if matches!(input.fields, syn::Fields::Unnamed(_)) {
return quote! { compile_error!("Configuration does not support Tupple structs") }.into();
}
let congen_attr: CongenStructAttribute = input
.attrs
.iter()
.filter_map(|attr| match &attr.meta {
Meta::List(meta_list) if meta_list.path.is_ident(&format_ident!("congen")) => {
match parse2(meta_list.tokens.clone()) {
Ok(attr) => Some(attr),
Err(err) => {
errors.push(err);
None
}
}
}
_ => None,
})
.fold(CongenStructAttribute::default(), |a, b| a.combine(&b));
let fields: Vec<_> = input
.fields
.into_iter()
.map(|field| CongenField::from_field(&mut errors, field))
.map(|mut field| {
if congen_attr.default && field.attr.default.is_none() {
field.attr.default = Some(CongenDefault::UseDefault);
}
field
})
.collect();
let vis = &input.vis;
let change_type = format_ident!("{}Change", input.ident);
let change_type_impl =
derive_change_type(&input.ident, &change_type, vis, &fields, &congen_attr);
let configuration_impl = derive_configuration_impl(&input.ident, &change_type, &fields);
let congen_change_impl = derive_congen_change(&input.ident, &change_type, &fields);
let errors = errors.iter().map(|e| e.to_compile_error());
quote! {
#(#errors)*
#change_type_impl
#configuration_impl
#congen_change_impl
}
.into()
}
fn derive_configuration_impl(
ty: &Ident,
change_type: &Ident,
fields: &[CongenField],
) -> TokenStream {
let type_name = ty.to_string();
let apply_change = fields.iter().map(|field| {
let ident = field.field.ident.as_ref().expect("tuple structs are not supported");
let ty = &field.field.ty;
let default_constructor = field.derive_default_constructor();
quote! {
<#ty as congen::internal::CongenInternal>::apply_change_with_inner_default(&mut self.#ident, change.#ident, #default_constructor);
}
});
let field_desc = fields.iter().map(|field| {
let field_name = if let Some(ident) = &field.field.ident {
let name = ident.to_string();
quote! { #name }
} else {
quote! { compile_error!("Configuration not supported for tupple structs") }
};
let ty = &field.field.ty;
let with_default = if field.attr.default.is_some() {
quote! { .with_default() }
} else {
quote! {}
};
quote! {
children.push(<#ty as congen::internal::CongenInternal>::description(#field_name) #with_default);
}
});
let mut has_default = true;
let field_defaults = fields
.iter()
.map(|field| {
let ident = &field
.field
.ident
.as_ref()
.expect("Tuple structs not supported");
let ty = &field.field.ty;
let Some(default) = field.attr.default.as_ref() else {
has_default = false;
return quote! {
#ident: { return Err(congen::internal::NotSupported) }
};
};
match default {
CongenDefault::UseDefault => quote! {
#ident: <#ty as congen::internal::CongenInternal>::default()?
},
CongenDefault::Expr(expr) => quote! {
#ident: #expr
},
CongenDefault::UseRust => quote! {
#ident: <#ty as std::default::Default>::default()
},
}
})
.collect::<Vec<_>>();
let congen_default_impl = if has_default {
quote! {
impl congen::internal::CongenDefault for #ty {
}
}
} else {
quote! {}
};
let field_types_requiring_default = fields
.iter()
.filter_map(|field| {
let ty = &field.field.ty;
match field.attr.default.as_ref() {
Some(CongenDefault::UseDefault) => Some(ty),
_ => None,
}
.cloned()
})
.collect::<HashSet<_>>();
let field_types_requiring_default = field_types_requiring_default.iter();
quote! {
impl congen::Configuration for #ty {
}
impl congen::internal::CongenInternal for #ty {
type CongenChange = #change_type;
fn apply_change_with_inner_default(
&mut self,
change: #change_type,
default: Option<fn() -> Box<dyn core::any::Any>>
) {
#(#apply_change)*
}
fn description(field_name: &'static str) -> congen::internal::Description {
let mut children = std::vec::Vec::new();
#(#field_desc)*
congen::internal::Description::Composit(
congen::internal::CompositDescription {
field_name,
type_name: Self::type_name(),
fields: children,
has_default: #has_default,
allow_unset: false,
}
)
}
fn default() -> Result<Self, congen::internal::NotSupported> {
#[allow(unreachable_code)]
Ok(Self {
#(#field_defaults),*
})
}
fn type_name() -> std::borrow::Cow<'static, str> {
#type_name.into()
}
}
#congen_default_impl
#(const _: bool = congen::internal::assert_impl_default::<#field_types_requiring_default>();)*
}
}
fn derive_change_type(
config_type: &Ident,
change_type: &Ident,
vis: &Visibility,
fields: &[CongenField],
opts: &CongenStructAttribute,
) -> TokenStream {
let change_fields_decls = fields.iter().map(|field| {
let ident = &field.field.ident;
let ty = &field.field.ty;
quote! { #ident: <#ty as congen::internal::CongenInternal>::CongenChange }
});
let derive_debug = if opts.debug {
quote! { #[derive(Debug)] }
} else {
quote! {}
};
quote! {
#[doc = concat!("Change type for [`", stringify!(#config_type), "`] in use with [`congen::Configuration`]")]
#[derive(Default)]
#derive_debug
#vis struct #change_type {
#(#change_fields_decls),*
}
}
}
fn derive_congen_change(
config_type: &Ident,
change_type: &Ident,
fields: &[CongenField],
) -> TokenStream {
let field_idents: Vec<_> = fields.iter().map(|field| &field.field.ident).collect();
let fields_from_path = fields.iter().map(|field| {
let ident = &field.field.ident.clone().expect("tuples are not supported");
let ident_str = ident.to_string();
let field_ty = field.field.ty.clone();
let special_case_default_verb = match &field.attr.default {
Some(CongenDefault::Expr(default_expr)) => {
quote! {
let mut path = path.peekable();
let verb = if path.peek().is_none() && matches!(verb, congen::internal::ChangeVerb::UseDefault) {
let value: #field_ty = #default_expr;
congen::internal::ChangeVerb::SetAny(Box::new(value))
} else {
verb
};
}
},
_ => quote! {}
};
quote! {
Some(#ident_str) => {
#special_case_default_verb
congen::internal::CongenChange::apply_change(
&mut change.#ident,
<#field_ty as congen::internal::CongenInternal>::CongenChange::from_path_and_verb(path, verb)?,
);
}
}
});
let field_path_names = fields.iter().map(|field| {
field
.field
.ident
.as_ref()
.expect("Tuple structs not supported")
.to_string()
});
quote! {
impl congen::internal::CongenChange for #change_type {
type Configuration = #config_type;
fn empty() -> Self {
#change_type {
#(#field_idents: congen::internal::CongenChange::empty()),*
}
}
fn default() -> Result<Self, congen::internal::NotSupported> {
fn map_err(err: congen::internal::VerbError) -> congen::internal::NotSupported {
match err {
congen::internal::VerbError::InvalidPath | congen::internal::VerbError::ParseError(_) | congen::internal::VerbError::DowncastFailed | congen::internal::VerbError::InvalidDescription =>
panic!("unexpected error while creating default CongenChange: {err}"),
congen::internal::VerbError::NotSupported(_) | congen::internal::VerbError::UnsupportedVerb(_) => congen::internal::NotSupported,
err => panic!("unknown error while creating default CongenChange: {err}"),
}
}
let mut default = Self::empty();
#(
eprintln!("create default for: {}", #field_path_names);
default.apply_change(
#change_type::from_path_and_verb([#field_path_names].into_iter(), congen::internal::ChangeVerb::UseDefault)
.map_err(map_err)?
);
)*
eprintln!("done!");
Ok(default)
}
fn apply_change(&mut self, change: Self) {
#(congen::internal::CongenChange::apply_change(&mut self.#field_idents, change.#field_idents));*
}
fn from_path_and_verb<'a, P>(
mut path: P,
verb: congen::internal::ChangeVerb)
-> Result<Self, congen::internal::VerbError>
where P: Iterator<Item = &'a str> {
let field_name = path.next();
let mut change = Self::empty();
match field_name {
#(#fields_from_path,)*
Some(_) => return Err(congen::internal::VerbError::InvalidPath),
None => {
return match verb {
congen::internal::ChangeVerb::Set(_) | congen::internal::ChangeVerb::SetAny(_) | congen::internal::ChangeVerb::List(_)
=> Err(congen::internal::VerbError::UnsupportedVerb(verb)),
congen::internal::ChangeVerb::SetFlag | congen::internal::ChangeVerb::Unset => {
eprintln!("set-flag and unset are not supported by derived congens");
Err(congen::internal::VerbError::UnsupportedVerb(verb))
},
congen::internal::ChangeVerb::UseDefault => Ok(<Self as congen::internal::CongenChange>::default()?),
unknown => {
eprintln!("unkown ChangeVerb {unknown:?}");
Err(congen::internal::VerbError::UnsupportedVerb(unknown))
}
}
},
};
Ok(change)
}
}
}
}
#[derive(Default)]
struct CongenStructAttribute {
debug: bool,
default: bool,
}
impl CongenStructAttribute {
fn combine(&self, other: &Self) -> Self {
Self {
debug: self.debug || other.debug,
default: self.default || other.default,
}
}
}
impl Parse for CongenStructAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args = Punctuated::<AttributeParam, Token![,]>::parse_terminated(input)?;
let mut impl_debug = false;
let mut gen_default = false;
for arg in args {
match arg.ident().to_string().as_str() {
"default" => {
if gen_default {
return Err(syn::Error::new_spanned(
arg,
"\"default\" should only be specified once in `congen` attribute",
));
}
if !matches!(arg, AttributeParam::Flag(_)) {
return Err(syn::Error::new_spanned(
arg,
"\"default\" is a flag and takes no arguments",
));
}
gen_default = true;
}
"debug" => {
if impl_debug {
return Err(syn::Error::new_spanned(
arg,
"\"debug\" should only be specified once in `congen` attribute",
));
}
if !matches!(arg, AttributeParam::Flag(_)) {
return Err(syn::Error::new_spanned(
arg,
"\"debug\" is a flag and takes no arguments",
));
}
impl_debug = true;
}
_ => {
return Err(syn::Error::new_spanned(
arg,
"unknown argument to `congen` attribute",
));
}
}
}
Ok(CongenStructAttribute {
debug: impl_debug,
default: gen_default,
})
}
}