#![allow(dead_code)] use proc_macro::TokenStream; use quote::quote; use syn::{DeriveInput, Ident, TypePath, parse_macro_input}; use crate::get_global_set; /// Derive macro for `StructuralData`. /// /// This marks a type as eligible for structured output (JSON / YAML / TOML / RON). /// The type must also implement `serde::Serialize` — the generated `impl StructuralData` /// will fail to compile if `Serialize` is not in scope or implemented. /// /// Also registers the type name in the global `STRUCTURED_TYPES` registry so that /// the `general_render` match arm is generated by `gen_program!()`. pub(crate) fn derive_structural_data(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let type_name = input.ident; // Register in STRUCTURED_TYPES let type_name_str = type_name.to_string(); get_global_set(&crate::STRUCTURED_TYPES) .lock() .unwrap() .insert(type_name_str); // Generate BOTH the sealed impl AND the StructuralData impl. // Users cannot implement StructuralDataSealed manually (it's #[doc(hidden)]), // so the only way to get StructuralData is through this derive macro. let expanded = quote! { impl ::mingling::__private::StructuralDataSealed for #type_name {} impl ::mingling::__private::StructuralData for #type_name {} }; expanded.into() } /// `pack_structural!` — like `pack!` but also marks the type as supporting /// structured output via `StructuralData`. /// /// # Syntax /// /// ```rust,ignore /// pack_structural!(Info = (String, i32)); /// ``` /// /// This is equivalent to: /// ```rust,ignore /// pack!(Info = (String, i32)); /// impl ::mingling::StructuralData for Info {} /// ``` pub(crate) fn pack_structural(input: TokenStream) -> TokenStream { // Parse same input format as `pack!` let input_parsed = syn::parse_macro_input!(input as PackStructuralInput); let type_name = input_parsed.type_name; let inner_type = input_parsed.inner_type; let attrs = input_parsed.attrs; let program_path = crate::default_program_path(); // Register in STRUCTURED_TYPES let type_name_str = type_name.to_string(); get_global_set(&crate::STRUCTURED_TYPES) .lock() .unwrap() .insert(type_name_str); // Struct definition (with Serialize derive, same as pack! under general_renderer) #[cfg(not(feature = "general_renderer"))] let struct_def = quote! { #(#attrs)* pub struct #type_name { pub inner: #inner_type, } }; #[cfg(feature = "general_renderer")] let struct_def = quote! { #(#attrs)* #[derive(serde::Serialize)] pub struct #type_name { pub inner: #inner_type, } }; // Helper impls (same as pack!) let new_impl = quote! { impl #type_name { pub fn new(inner: #inner_type) -> Self { Self { inner } } } }; let from_into_impl = quote! { impl From<#inner_type> for #type_name { fn from(inner: #inner_type) -> Self { Self::new(inner) } } impl From<#type_name> for #inner_type { fn from(wrapper: #type_name) -> #inner_type { wrapper.inner } } }; let as_ref_impl = quote! { impl ::std::convert::AsRef<#inner_type> for #type_name { fn as_ref(&self) -> &#inner_type { &self.inner } } impl ::std::convert::AsMut<#inner_type> for #type_name { fn as_mut(&mut self) -> &mut #inner_type { &mut self.inner } } }; let deref_impl = quote! { impl ::std::ops::Deref for #type_name { type Target = #inner_type; fn deref(&self) -> &Self::Target { &self.inner } } impl ::std::ops::DerefMut for #type_name { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } }; let default_impl = quote! { impl ::std::default::Default for #type_name where #inner_type: ::std::default::Default, { fn default() -> Self { Self::new(::std::default::Default::default()) } } }; let register_impl = quote! { ::mingling::macros::register_type!(#type_name); }; // StructuralData impl + sealed + registration let structural_impl = quote! { impl ::mingling::__private::StructuralDataSealed for #type_name {} impl ::mingling::__private::StructuralData for #type_name {} }; let expanded = quote! { #struct_def #new_impl #from_into_impl #as_ref_impl #deref_impl #default_impl #register_impl #structural_impl impl Into<::mingling::AnyOutput<#program_path>> for #type_name { fn into(self) -> ::mingling::AnyOutput<#program_path> { ::mingling::AnyOutput::new(self) } } impl Into<::mingling::ChainProcess<#program_path>> for #type_name { fn into(self) -> ::mingling::ChainProcess<#program_path> { ::mingling::AnyOutput::new(self).route_chain() } } impl ::mingling::Groupped<#program_path> for #type_name { fn member_id() -> #program_path { #program_path::#type_name } } }; expanded.into() } /// Input for `pack_structural!` — same format as `pack!`. struct PackStructuralInput { attrs: Vec, type_name: Ident, inner_type: syn::Type, } impl syn::parse::Parse for PackStructuralInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let attrs = input.call(syn::Attribute::parse_outer)?; let type_name: Ident = input.parse()?; input.parse::()?; let inner_type: syn::Type = input.parse()?; Ok(PackStructuralInput { attrs, type_name, inner_type, }) } } /// `group_structural!` — like `group!` but also marks the type as supporting /// structured output via `StructuralData`. /// /// # Syntax /// /// ```rust,ignore /// group_structural!(Info = (String, i32)); /// ``` /// /// This is equivalent to: /// ```rust,ignore /// group!(Info = (String, i32)); /// impl ::mingling::StructuralData for Info {} /// ``` pub(crate) fn group_structural(input: TokenStream) -> TokenStream { // Parse the same input as group! let input_parsed = syn::parse_macro_input!(input as GroupStructuralInput); let is_aliased = matches!(&input_parsed, GroupStructuralInput::Aliased { .. }); let (type_path, type_name, alias_stmt) = match &input_parsed { GroupStructuralInput::Plain(type_path) => { let name = type_path .path .segments .last() .expect("TypePath must have at least one segment") .ident .clone(); (type_path.clone(), name, quote! {}) } GroupStructuralInput::Aliased { alias, type_path } => { let alias_stmt = quote! { pub(crate) type #alias = #type_path; }; (type_path.clone(), alias.clone(), alias_stmt) } }; let type_name_str = type_name.to_string(); // Register in STRUCTURED_TYPES get_global_set(&crate::STRUCTURED_TYPES) .lock() .unwrap() .insert(type_name_str); let program_path = crate::default_program_path(); // Generate unique module name let segments: Vec = type_path .path .segments .iter() .map(|seg| seg.ident.to_string().to_lowercase()) .collect(); let module_name = Ident::new( &format!("internal_group_{}", segments.join("_")), proc_macro2::Span::call_site(), ); // Generate the appropriate `use` statement let type_use = if type_path.path.segments.len() > 1 { quote! { #[allow(unused_imports)] use #type_path; } } else { let ident = &type_name; quote! { #[allow(unused_imports)] use super::#ident; } }; let alias_use = if is_aliased { quote! { use super::#type_name; } } else { quote! {} }; let expanded = quote! { #alias_stmt #[allow(non_camel_case_types)] mod #module_name { use #program_path as __MinglingProgram; #type_use #alias_use impl ::mingling::Groupped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } } impl ::mingling::__private::StructuralDataSealed for #type_name {} impl ::mingling::__private::StructuralData for #type_name {} ::mingling::macros::register_type!(#type_name); } }; expanded.into() } /// Input for `group_structural!` — same format as `group!`. enum GroupStructuralInput { Plain(TypePath), Aliased { alias: Ident, type_path: TypePath }, } impl syn::parse::Parse for GroupStructuralInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let fork = input.fork(); let _first: Ident = fork.parse()?; if fork.peek(syn::Token![=]) { let alias: Ident = input.parse()?; let _eq: syn::Token![=] = input.parse()?; let type_path: TypePath = input.parse()?; Ok(GroupStructuralInput::Aliased { alias, type_path }) } else { let type_path: TypePath = input.parse()?; Ok(GroupStructuralInput::Plain(type_path)) } } }