diff options
Diffstat (limited to 'mingling_macros')
| -rw-r--r-- | mingling_macros/src/derive.rs | 1 | ||||
| -rw-r--r-- | mingling_macros/src/derive/wrap.rs | 161 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 47 |
3 files changed, 208 insertions, 1 deletions
diff --git a/mingling_macros/src/derive.rs b/mingling_macros/src/derive.rs index 58f5526..42df76b 100644 --- a/mingling_macros/src/derive.rs +++ b/mingling_macros/src/derive.rs @@ -3,3 +3,4 @@ pub(crate) mod enum_tag; pub(crate) mod grouped; #[cfg(feature = "structural_renderer")] pub(crate) mod structural_data; +pub(crate) mod wrap; diff --git a/mingling_macros/src/derive/wrap.rs b/mingling_macros/src/derive/wrap.rs new file mode 100644 index 0000000..ad70e28 --- /dev/null +++ b/mingling_macros/src/derive/wrap.rs @@ -0,0 +1,161 @@ +// Doc Not Optimize +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Data, DeriveInput, Field, Fields, Type, parse_macro_input}; + +/// The located inner field of a `Wrap` struct. +struct InnerInfo { + /// Access the inner value from `self`, e.g. `self.name` or `self.0`. + self_access: TokenStream2, + /// Access the inner value mutably from `self`, e.g. `&mut self.name`. + self_access_mut: TokenStream2, + /// Access the inner value from a bound `wrapper` variable. + wrapper_access: TokenStream2, + /// Expression building `Self` from a bound `inner` variable. + construct: TokenStream2, + /// The inner field's type. + inner_ty: Type, +} + +/// Parse the input struct and locate the inner field. +/// +/// Rules: +/// - Named struct with a single field → that field is the inner field. +/// - Named struct with multiple fields → exactly one field must be marked `#[wrap]`. +/// - Tuple struct with a single field → that field is the inner field. +fn locate_inner(input: &DeriveInput) -> Result<InnerInfo, TokenStream2> { + let name = &input.ident; + + match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => { + let all = &fields.named; + let as_fields: Vec<&Field> = all + .iter() + .filter(|f| f.attrs.iter().any(|a| a.path().is_ident("wrap"))) + .collect(); + + if as_fields.len() > 1 { + let span = as_fields[1].ident.as_ref().unwrap_or(name); + return Err(syn::Error::new_spanned( + span, + "only one field may be marked `#[wrap]`", + ) + .to_compile_error()); + } + + let (inner_field, other_fields): (&Field, Vec<&Field>) = if as_fields.len() == 1 { + let inner = as_fields[0]; + let inner_ident = inner.ident.as_ref().unwrap(); + let others = all + .iter() + .filter(|f| f.ident.as_ref() != Some(inner_ident)) + .collect(); + (inner, others) + } else if all.len() == 1 { + (all.first().unwrap(), Vec::new()) + } else { + return Err(syn::Error::new_spanned( + name, + "a struct with multiple fields requires exactly one field marked `#[wrap]`", + ) + .to_compile_error()); + }; + + let field_ident = inner_field.ident.clone().unwrap(); + + let mut named = vec![quote! { #field_ident: inner }]; + for other in other_fields { + let ident = other.ident.as_ref().unwrap(); + named.push(quote! { #ident: ::core::default::Default::default() }); + } + + Ok(InnerInfo { + self_access: quote! { self.#field_ident }, + self_access_mut: quote! { &mut self.#field_ident }, + wrapper_access: quote! { wrapper.#field_ident }, + construct: quote! { Self { #(#named),* } }, + inner_ty: inner_field.ty.clone(), + }) + } + Fields::Unnamed(fields) => { + if fields.unnamed.len() == 1 { + Ok(InnerInfo { + self_access: quote! { self.0 }, + self_access_mut: quote! { &mut self.0 }, + wrapper_access: quote! { wrapper.0 }, + construct: quote! { Self(inner) }, + inner_ty: fields.unnamed.first().unwrap().ty.clone(), + }) + } else { + Err(syn::Error::new_spanned( + name, + "tuple structs with multiple fields are not supported by `Wrap`; \ + use a named struct and mark one field with `#[wrap]`", + ) + .to_compile_error()) + } + } + Fields::Unit => Err(syn::Error::new_spanned( + name, + "unit structs have no inner type; `Wrap` requires a field", + ) + .to_compile_error()), + }, + Data::Enum(_) | Data::Union(_) => Err(syn::Error::new_spanned( + name, + "`Wrap` can only be derived on structs", + ) + .to_compile_error()), + } +} + +pub(crate) fn derive_wrap(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let info = match locate_inner(&input) { + Ok(info) => info, + Err(err) => return err.into(), + }; + + let InnerInfo { + self_access, + self_access_mut, + wrapper_access, + construct, + inner_ty, + } = info; + + let expanded = quote! { + impl #impl_generics ::core::convert::From<#inner_ty> for #name #ty_generics #where_clause { + fn from(inner: #inner_ty) -> Self { + #construct + } + } + + impl #impl_generics ::core::convert::From<#name #ty_generics> for #inner_ty #where_clause { + fn from(wrapper: #name #ty_generics) -> #inner_ty { + #wrapper_access + } + } + + impl #impl_generics ::core::ops::Deref for #name #ty_generics #where_clause { + type Target = #inner_ty; + + fn deref(&self) -> &Self::Target { + &(#self_access) + } + } + + impl #impl_generics ::core::ops::DerefMut for #name #ty_generics #where_clause { + fn deref_mut(&mut self) -> &mut Self::Target { + #self_access_mut + } + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 607ba9f..7da7db3 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -33,7 +33,7 @@ use attr::dispatcher_clap; #[cfg(feature = "extras")] use attr::program_setup; use attr::{chain, help, metadata, renderer}; -use derive::{enum_tag, grouped}; +use derive::{enum_tag, grouped, wrap}; #[cfg(feature = "extras")] use func::entry; #[cfg(feature = "extras")] @@ -1547,6 +1547,51 @@ pub fn r_append(input: TokenStream) -> TokenStream { func::r_append::r_append(input) } +/// Derive macro for treating a struct as its inner (wrapped) type. +/// +/// The `#[derive(Wrap)]` macro generates: +/// - `From<Inner> for Self` — construct the wrapper from the inner value +/// - `From<Self> for Inner` — unwrap back to the inner value (i.e. `Into<Inner>`) +/// - `Deref` / `DerefMut` — delegate all methods to the inner value +/// +/// # Inner field selection +/// +/// - Tuple struct with one field → that field is the inner type +/// - Named struct with one field → that field is the inner type +/// - Named struct with multiple fields → mark exactly one field with `#[wrap]`; +/// the remaining fields are initialized with `Default::default()` when +/// constructing via `From<Inner>` +/// +/// # Example +/// +/// ```rust,ignore +/// use mingling::macros::Wrap; +/// +/// #[derive(Wrap)] +/// struct Name(String); +/// +/// #[derive(Wrap)] +/// struct Greeting { +/// name: String, +/// } +/// +/// #[derive(Wrap)] +/// struct Task { +/// #[wrap] +/// content: String, +/// done: bool, +/// } +/// +/// let name = Name::from("Mingling".to_string()); +/// // `Deref` forwards methods to the inner `String` +/// assert_eq!(name.len(), 8); +/// let inner: String = name.into(); +/// ``` +#[proc_macro_derive(Wrap, attributes(wrap))] +pub fn derive_wrap(input: TokenStream) -> TokenStream { + wrap::derive_wrap(input) +} + /// Derive macro for automatically implementing the `Grouped` trait on a struct. /// /// The `#[derive(Grouped)]` macro: |
