aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/derive
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/derive')
-rw-r--r--mingling_macros/src/derive/enum_tag.rs168
-rw-r--r--mingling_macros/src/derive/grouped.rs87
2 files changed, 255 insertions, 0 deletions
diff --git a/mingling_macros/src/derive/enum_tag.rs b/mingling_macros/src/derive/enum_tag.rs
new file mode 100644
index 0000000..a7f71f0
--- /dev/null
+++ b/mingling_macros/src/derive/enum_tag.rs
@@ -0,0 +1,168 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{
+ Attribute, Data, DeriveInput, Error, Fields, Ident, LitStr, Result, Variant, parse_macro_input,
+};
+
+pub(crate) fn derive_enum_tag(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ match derive_enum_tag_impl(input) {
+ Ok(tokens) => tokens.into(),
+ Err(err) => err.to_compile_error().into(),
+ }
+}
+
+/// Implementation of the `EnumTag` derive macro
+fn derive_enum_tag_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
+ let enum_name = &input.ident;
+ let generics = &input.generics;
+
+ // Extract enum data
+ let data = match input.data {
+ Data::Enum(data_enum) => data_enum,
+ Data::Struct(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not structs",
+ ));
+ }
+ Data::Union(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not unions",
+ ));
+ }
+ };
+
+ // Process each variant
+ let mut variant_info = Vec::new();
+ let mut match_arms = Vec::new();
+ let mut build_match_arms = Vec::new();
+
+ for variant in data.variants {
+ process_variant(
+ &variant,
+ enum_name,
+ &mut variant_info,
+ &mut match_arms,
+ &mut build_match_arms,
+ )?;
+ }
+
+ // Generate the implementation
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let expanded = quote! {
+ impl #impl_generics ::mingling::EnumTag for #enum_name #ty_generics #where_clause {
+ fn enum_info(&self) -> (&'static str, &'static str) {
+ match self {
+ #(#match_arms)*
+ }
+ }
+
+ fn build_enum(name: String) -> Option<Self>
+ where
+ Self: Sized
+ {
+ match name.as_str() {
+ #(#build_match_arms)*
+ _ => None,
+ }
+ }
+
+ fn enums() -> &'static [(&'static str, &'static str)] {
+ &[#(#variant_info),*]
+ }
+ }
+ };
+
+ Ok(expanded)
+}
+
+/// Process a single enum variant
+fn process_variant(
+ variant: &Variant,
+ enum_name: &Ident,
+ variant_info: &mut Vec<proc_macro2::TokenStream>,
+ match_arms: &mut Vec<proc_macro2::TokenStream>,
+ build_match_arms: &mut Vec<proc_macro2::TokenStream>,
+) -> Result<()> {
+ let variant_name = variant.ident.clone();
+
+ // Check if variant has fields
+ match &variant.fields {
+ Fields::Unit => {
+ // Good, unit variant
+ }
+ Fields::Named(_) | Fields::Unnamed(_) => {
+ return Err(Error::new_spanned(
+ variant,
+ format!(
+ "EnumTag cannot be derived for enum variant `{variant_name}` with fields. Only unit variants are supported."
+ ),
+ ));
+ }
+ }
+
+ // Extract description from #[enum_desc] attribute
+ let description = extract_description(&variant.attrs)?;
+
+ // Extract rename from #[enum_rename] attribute
+ let rename = extract_rename(&variant.attrs)?;
+
+ // Generate tokens for this variant
+ let variant_name_str = variant_name.to_string();
+ let display_name = rename.unwrap_or_else(|| variant_name_str.clone());
+ let description_str = description.unwrap_or_default();
+
+ variant_info.push(quote! {
+ (#display_name, #description_str)
+ });
+
+ match_arms.push(quote! {
+ #enum_name::#variant_name => (#display_name, #description_str),
+ });
+
+ build_match_arms.push(quote! {
+ #display_name => Some(#enum_name::#variant_name),
+ });
+
+ Ok(())
+}
+
+/// Extract description from #[`enum_desc`] attribute
+fn extract_description(attrs: &[Attribute]) -> Result<Option<String>> {
+ for attr in attrs {
+ if attr.path().is_ident("enum_desc") {
+ return match attr.parse_args::<LitStr>() {
+ Ok(lit_str) => Ok(Some(lit_str.value())),
+ Err(_) => Err(Error::new_spanned(
+ attr,
+ "#[enum_desc] attribute must be in the form `#[enum_desc(\"description\")]`",
+ )),
+ };
+ }
+ }
+
+ // If no #[enum_desc] attribute, return None
+ Ok(None)
+}
+
+/// Extract rename from #[`enum_rename`] attribute
+fn extract_rename(attrs: &[Attribute]) -> Result<Option<String>> {
+ for attr in attrs {
+ if attr.path().is_ident("enum_rename") {
+ return match attr.parse_args::<LitStr>() {
+ Ok(lit_str) => Ok(Some(lit_str.value())),
+ Err(_) => Err(Error::new_spanned(
+ attr,
+ "#[enum_rename] attribute must be in the form `#[enum_rename(\"new_name\")]`",
+ )),
+ };
+ }
+ }
+
+ // If no #[enum_rename] attribute, return None
+ Ok(None)
+}
diff --git a/mingling_macros/src/derive/grouped.rs b/mingling_macros/src/derive/grouped.rs
new file mode 100644
index 0000000..a00eea1
--- /dev/null
+++ b/mingling_macros/src/derive/grouped.rs
@@ -0,0 +1,87 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{DeriveInput, Ident, parse_macro_input};
+
+pub(crate) fn derive_grouped(input: TokenStream) -> TokenStream {
+ // Parse the input struct/enum
+ let input = parse_macro_input!(input as DeriveInput);
+ let struct_name = input.ident;
+
+ let group_ident: proc_macro2::TokenStream = crate::default_program_path();
+
+ let any_output_convert_impls =
+ proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
+
+ // Generate the Grouped trait implementation
+ let expanded = quote! {
+ ::mingling::macros::register_type!(#struct_name);
+
+ /// SAFETY: This is an internal implementation of the `Grouped` derive macro,
+ /// guaranteeing that the enum value registered by the `register_type!` macro
+ /// is exactly the same as the actual return value,
+ /// which can be confirmed via the `Ident` in the `quote!` block.
+ unsafe impl ::mingling::Grouped<#group_ident> for #struct_name {
+ fn member_id() -> #group_ident {
+ #group_ident::#struct_name
+ }
+ }
+
+ #any_output_convert_impls
+ };
+
+ expanded.into()
+}
+
+#[cfg(feature = "structural_renderer")]
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
+ // Parse the input struct/enum
+ let input_parsed = parse_macro_input!(input as DeriveInput);
+ let struct_name = input_parsed.ident.clone();
+
+ let group_ident: proc_macro2::TokenStream = crate::default_program_path();
+
+ let any_output_convert_impls =
+ proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
+
+ // Generate both Serialize and Grouped implementations
+ let expanded = quote! {
+ #[derive(serde::Serialize)]
+ #input_parsed
+
+ ::mingling::macros::register_type!(#struct_name);
+
+ /// SAFETY: This is an internal implementation of the `Grouped` derive macro,
+ /// guaranteeing that the enum value registered by the `register_type!` macro
+ /// is exactly the same as the actual return value,
+ /// which can be confirmed via the `Ident` in the `quote!` block.
+ unsafe impl ::mingling::Grouped<#group_ident> for #struct_name {
+ fn member_id() -> #group_ident {
+ #group_ident::#struct_name
+ }
+ }
+
+ #any_output_convert_impls
+ };
+
+ expanded.into()
+}
+
+fn build_any_output_convert_impls(
+ struct_name: &Ident,
+ group_ident: &proc_macro2::TokenStream,
+) -> TokenStream {
+ quote! {
+ impl ::std::convert::Into<::mingling::AnyOutput<#group_ident>> for #struct_name {
+ fn into(self) -> ::mingling::AnyOutput<#group_ident> {
+ ::mingling::AnyOutput::new(self)
+ }
+ }
+
+ impl ::std::convert::Into<::mingling::ChainProcess<#group_ident>> for #struct_name {
+ fn into(self) -> ::mingling::ChainProcess<#group_ident> {
+ ::mingling::AnyOutput::new(self).route_chain()
+ }
+ }
+ }
+ .into()
+}