use std::collections::HashSet;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
Expr, Field, GenericArgument, Ident, ItemStruct, Meta, PathArguments, Token, Type, Visibility,
parse::Parse, parse2, punctuated::Punctuated,
};
use crate::{AttributeParam, CongenItemAttribute};
pub fn struct_configuration(input: ItemStruct) -> TokenStream {
let mut errors = Vec::new();
if matches!(input.fields, syn::Fields::Unnamed(_)) {
return quote! { compile_error!("Configuration does not support Tupple structs") }.into();
}
let congen_attr = CongenItemAttribute::from_attrs(input.attrs.iter(), &mut errors);
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
}
}
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: &CongenItemAttribute,
) -> 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! {}
};
let clone_impl = if opts.clone {
let clone_field = fields.iter().map(|field| {
let ident = &field.field.ident;
quote! {
#ident: self.#ident.clone()
}
});
quote! {
impl core::clone::Clone for #change_type {
fn clone(&self) -> Self {
Self {
#(#clone_field),*
}
}
}
}
} 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),*
}
#clone_impl
}
}
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)]
pub struct CongenFieldAttribute {
pub default: Option<CongenDefault>,
pub inner_default: Option<Expr>,
}
pub enum CongenDefault {
UseDefault,
UseRust,
Expr(Expr),
}
impl Parse for CongenFieldAttribute {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args = Punctuated::<AttributeParam, Token![,]>::parse_terminated(input)?;
let mut default = None;
let mut inner_default = None;
for arg in args {
match arg.ident().to_string().as_str() {
"rust_default" => {
if default.is_some() {
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,
"\"rust_default\" takes no arguments",
));
}
default = Some(CongenDefault::UseRust);
}
"default" => {
if default.is_some() {
return Err(syn::Error::new_spanned(
arg,
"\"default\" should only be specified once in `congen` attribute",
));
}
default = Some(match arg {
AttributeParam::Flag(_ident) => CongenDefault::UseDefault,
AttributeParam::NameValue { value, .. } => CongenDefault::Expr(value),
});
}
"inner_default" => {
if inner_default.is_some() {
return Err(syn::Error::new_spanned(
arg,
"\"inner_default\" should only be specified once in `congen` attribute",
));
}
let AttributeParam::NameValue { value: expr, .. } = arg else {
return Err(syn::Error::new_spanned(
arg,
"\"inner_default\" requires expression as argument",
));
};
inner_default = Some(expr);
}
_ => {
return Err(syn::Error::new_spanned(
arg,
"unknown argument to `congen` attribute",
));
}
}
}
Ok(CongenFieldAttribute {
default,
inner_default,
})
}
}
pub struct CongenField {
pub attr: CongenFieldAttribute,
pub field: Field,
pub inner_type: Option<InnerType>,
pub is_option: bool,
}
#[allow(clippy::large_enum_variant)]
pub enum InnerType {
Single(Type),
#[expect(unused)]
Double(Type, Type),
}
impl InnerType {
pub fn field_type(&self) -> &Type {
match self {
InnerType::Single(typ) => typ,
InnerType::Double(_, typ) => typ,
}
}
}
impl CongenField {
pub fn from_field(errors: &mut Vec<syn::Error>, field: Field) -> CongenField {
let attr = field
.attrs
.iter()
.find_map(|attr| match &attr.meta {
Meta::List(meta_list) if meta_list.path.is_ident(&format_ident!("congen")) => {
Some(parse2(meta_list.tokens.clone()))
}
_ => None,
})
.unwrap_or(Ok(CongenFieldAttribute::default()))
.unwrap_or_else(|err| {
errors.push(err);
CongenFieldAttribute::default()
});
let inner_type = get_inner_type(&field.ty).unwrap_or_else(|err| {
errors.push(err);
None
});
let is_option = is_option(&field.ty).unwrap_or_else(|err| {
errors.push(err);
false
});
CongenField {
attr,
field,
inner_type,
is_option,
}
}
pub fn derive_default_constructor(&self) -> TokenStream {
let Some(inner_type) = self.inner_type.as_ref() else {
return quote! { None };
};
let inner_type = inner_type.field_type();
if let Some(inner_default) = self.attr.inner_default.as_ref() {
quote! {
Some(|| {
let inner_default: #inner_type = #inner_default;
Box::new(inner_default)
})
}
} else if let Some(CongenDefault::Expr(expr)) = self.attr.default.as_ref()
&& self.is_option
{
quote! {
Some(|| {
let outer_default: core::option::Option<#inner_type> = #expr;
let inner_default: #inner_type = core::option::Option::expect(outer_default,
concat!("In order to use default of Option<",
stringify!(#inner_type),
"> for inner type default the default must be Some(_)."));
Box::new(inner_default)
})
}
} else {
quote! {
Some(|| {
let inner_default = <#inner_type as congen::internal::CongenInternal>::default();
let inner_default: #inner_type = core::result::Result::expect(inner_default,
concat!(stringify!(#inner_type), " does not implement Configuration::default")
);
Box::new(inner_default)
})
}
}
}
}
fn get_inner_type(ty: &Type) -> Result<Option<InnerType>, syn::Error> {
let Type::Path(path) = ty else {
return Ok(None);
};
if path.qself.is_some() {
return Err(syn::Error::new_spanned(
ty,
"Qself path type not supported in option for congen",
));
}
let path = &path.path;
let Some(last_segment) = path.segments.iter().last() else {
return Err(syn::Error::new_spanned(
ty,
"Empty path. Is this even possible?",
));
};
let PathArguments::AngleBracketed(opt_inner_type_args) = &last_segment.arguments else {
return Ok(None);
};
let mut args = opt_inner_type_args.args.iter().cloned();
let Some(GenericArgument::Type(first_inner)) = args.next() else {
return Ok(None);
};
let Some(GenericArgument::Type(second_inner)) = args.next() else {
return Ok(Some(InnerType::Single(first_inner)));
};
Ok(Some(InnerType::Double(first_inner, second_inner)))
}
fn is_option(ty: &Type) -> Result<bool, syn::Error> {
let Type::Path(path) = ty else {
return Ok(false);
};
if path.qself.is_some() {
return Err(syn::Error::new_spanned(
ty,
"Qself path type not supported in option for congen",
));
}
let path = &path.path;
if path.leading_colon.is_some() {
return Ok(false);
}
let mut segments = path.segments.iter();
let Some(first) = segments.next() else {
return Err(syn::Error::new_spanned(
ty,
"Empty path. Is this even possible?",
));
};
match first.ident.to_string().as_str() {
"Option" => Ok(true),
"std" | "core" => {
let Some(opt) = segments.next() else {
return Ok(false);
};
if opt.ident != "option" {
return Ok(false);
};
let Some(opt) = segments.next() else {
return Ok(false);
};
if opt.ident != "Option" {
return Ok(false);
};
Ok(true)
}
_ => Ok(false),
}
}