aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md40
-rw-r--r--mingling/src/lib.rs5
-rw-r--r--mingling_macros/src/derive.rs1
-rw-r--r--mingling_macros/src/derive/wrap.rs161
-rw-r--r--mingling_macros/src/lib.rs47
5 files changed, 252 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75f1576..ea71a92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -66,7 +66,45 @@ None
#### Features:
-None
+1. **[`macros:wrap`]** Added the `#[derive(Wrap)]` derive macro that treats a struct as its inner (wrapped) type. The 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();
+ ```
+
+ The macro is re-exported from `mingling::Wrap` and `mingling::prelude::Wrap` (feature-gated behind `macros`).
#### **BREAKING CHANGES** (API CHANGES):
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 2596a9c..6a7074b 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -133,6 +133,9 @@ pub mod macros {
}
#[cfg(feature = "macros")]
+pub use mingling_macros::Wrap;
+
+#[cfg(feature = "macros")]
pub use mingling_macros::EnumTag;
#[cfg(feature = "macros")]
@@ -197,6 +200,8 @@ pub mod prelude {
#[cfg(feature = "core")]
pub use crate::Routable;
#[cfg(feature = "macros")]
+ pub use crate::Wrap;
+ #[cfg(feature = "macros")]
pub use crate::macros::chain;
#[cfg(all(feature = "extras", feature = "macros"))]
pub use crate::macros::command;
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: