From 3ade25afbc8d2e6918fb7834522ab4a894d1c88d Mon Sep 17 00:00:00 2001 From: Domenic Quirl Date: Tue, 25 Nov 2025 14:09:54 +0100 Subject: [PATCH] feat: allow deriving Deref[Mut] for structs with multiple fields allow selecting the target field for Deref and DerefMut by annotating it with a `#[deref]` helper attribute like is done for enum variants with `#[default]` the implementation checks how many fields are annotated with the attribute so more than one target is disallowed and is backwards-compatible with the current "single-field" behaviour as the attribute is not required for single-field structs --- derive-ex-tests/tests/deref.rs | 35 +++++++++++++ derive-ex/src/item_type.rs | 90 ++++++++++++++++++++++++++++++---- doc/derive_ex.md | 30 ++++++++++++ 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/derive-ex-tests/tests/deref.rs b/derive-ex-tests/tests/deref.rs index 4190afd..d040a64 100644 --- a/derive-ex-tests/tests/deref.rs +++ b/derive-ex-tests/tests/deref.rs @@ -35,6 +35,41 @@ fn single_field() { let _: &u8 = &X { x: 10u8 }; let _: &mut u8 = &mut X { x: 10u8 }; } +#[test] +fn multiple_fields_tuple() { + #[derive_ex(Deref, DerefMut)] + #[expect(unused, reason = "testing with additional fields")] + struct X(#[deref] u8, u32); + + let _: &u8 = &X(10u8, 1u32); + let _: &mut u8 = &mut X(10u8, 1u32); +} +#[test] +fn multiple_fields_named() { + #[derive_ex(Deref, DerefMut)] + #[expect(unused, reason = "testing with additional fields")] + struct X { + #[deref] + x: u8, + y: u32, + } + + let _: &u8 = &X { x: 10u8, y: 1u32 }; + let _: &mut u8 = &mut X { x: 10u8, y: 1u32 }; +} +#[test] +fn not_first_field() { + #[derive_ex(Deref, DerefMut)] + #[expect(unused, reason = "testing with additional fields")] + struct X { + x: u8, + #[deref] + y: u32, + } + + let _: &u32 = &X { x: 10u8, y: 1u32 }; + let _: &mut u32 = &mut X { x: 10u8, y: 1u32 }; +} #[test] fn with_where() { diff --git a/derive-ex/src/item_type.rs b/derive-ex/src/item_type.rs index c1c7958..f8d68c0 100644 --- a/derive-ex/src/item_type.rs +++ b/derive-ex/src/item_type.rs @@ -747,16 +747,49 @@ fn build_deref_for_struct( let mut wcb = WhereClauseBuilder::new(&item.generics); e.push_bounds_to(&mut wcb); - if fields.len() != 1 { - bail!( - Span::call_site(), - "`#[deirve_ex({})]` supports only single field struct.", - kind - ); - } - let target_ty = &fields[0].field.ty; - let member = fields[0].member(); + let targets: Vec<_> = fields + .iter() + .filter(|&field| field.hattrs.deref.deref.value()) + .collect(); + let field_idx = match targets.len() { + 0 => { + if fields.len() == 1 { + 0 + } else { + bail!( + Span::call_site(), + "field with `#[deref]` does not exist but struct has more than one field.", + ); + } + } + 1 => targets[0].index, + _ => { + let names: Vec = targets + .iter() + .map(|&field| { + field + .field + .ident + .as_ref() + .map(Ident::to_string) + .unwrap_or_else(move || field.index.to_string()) + }) + .collect(); + bail!( + targets[0] + .hattrs + .deref + .deref + .span + .unwrap_or(targets[0].field.span()), + "there are multiple variants with `#[deref]` ({})", + names.join(", "), + ) + } + }; + let target_ty = &fields[field_idx].field.ty; + let member = fields[field_idx].member(); let content = match kind { DeriveItemKind::Deref => { quote! { @@ -1193,6 +1226,7 @@ struct HelperAttributeKinds { derive_ex: bool, default: bool, debug: bool, + deref: bool, ord: bool, partial_ord: bool, eq: bool, @@ -1212,6 +1246,7 @@ impl HelperAttributeKinds { match e.kind { DeriveItemKind::Default => self.default = true, DeriveItemKind::Debug => self.debug = true, + DeriveItemKind::Deref => self.deref = true, DeriveItemKind::CompareOp(op) => match op { CompareOp::Ord => self.ord = true, CompareOp::PartialOrd => self.partial_ord = true, @@ -1248,6 +1283,7 @@ impl HelperAttributeKinds { "derive_ex" => self.derive_ex, "default" => self.default, "debug" => self.debug, + "deref" => self.deref, "ord" => self.is_match_cmp_attr(CompareOp::Ord), "partial_ord" => self.is_match_cmp_attr(CompareOp::PartialOrd), "eq" => self.is_match_cmp_attr(CompareOp::Eq), @@ -1269,6 +1305,7 @@ struct HelperAttributes { items: HashMap, default: Option, debug: HelperAttributeForDebug, + deref: HelperAttributeForDeref, cmp: HelperAttributesForCompareOp, } @@ -1296,11 +1333,17 @@ impl HelperAttributes { } else { HelperAttributeForDebug::default() }; + let deref = if kinds.deref { + HelperAttributeForDeref::from_attrs(attrs)? + } else { + HelperAttributeForDeref::default() + }; let cmp = HelperAttributesForCompareOp::from_attrs(attrs, kinds)?; let this = Self { items, default, debug, + deref, cmp, }; this.verify(target)?; @@ -1387,7 +1430,9 @@ impl HelperAttributeForDebug { Ok(Self { transparent: args.transparent, skip: if args.skip.value() || args.ignore.value() { - Flag { span: args.skip.span.or(args.ignore.span) } + Flag { + span: args.skip.span.or(args.ignore.span), + } } else { Flag::NONE }, @@ -1458,6 +1503,31 @@ impl HelperAttributeForDefault { } } +#[derive(StructMeta, Debug, Default)] +struct ArgsForDeref {} + +#[derive(Default)] +struct HelperAttributeForDeref { + deref: Flag, +} + +impl HelperAttributeForDeref { + fn from_attrs(attrs: &[Attribute]) -> Result { + if parse_single::(attrs, "deref")?.is_some() { + let span = attrs + .iter() + .find(|&attr| attr.path().is_ident("deref")) + .expect("parse_single already found a deref attribute") + .span(); + Ok(Self { + deref: Flag { span: Some(span) }, + }) + } else { + Ok(Self { deref: Flag::NONE }) + } + } +} + #[derive(StructMeta, Default, Debug)] struct ArgsForCompareOp { skip: Flag, diff --git a/doc/derive_ex.md b/doc/derive_ex.md index 81f54b1..92c9bd7 100644 --- a/doc/derive_ex.md +++ b/doc/derive_ex.md @@ -546,6 +546,20 @@ struct X(u8); let _: &u8 = &X(10u8); ``` +For structs with multiple fields, you can use `#[deref]` to select the target for the `Deref` implementation. + +```rust +use derive_ex::derive_ex; +#[derive(Debug, Default)] +#[derive_ex(Deref)] +struct X { + a: u8, + #[deref] + b: u32, +} +assert_eq!(X::default().leading_zeros(), 32); +``` + # Derive `DerefMut` You can use `#[derive(DerefMut)]` for struct with a single field to implement `DerefMut`. @@ -559,6 +573,22 @@ struct X(u8); let _: &mut u8 = &mut X(10u8); ``` +For structs with multiple fields, you can use `#[deref]` to select the target for the `Deref` implementation. + +```rust +use derive_ex::derive_ex; + +#[derive(Debug, Default)] +#[derive_ex(Deref, DerefMut)] +struct X { + a: u8, + #[deref] + b: u32, +} + +let _: &mut u32 = &mut X::default(); +``` + # Derive operators ## `Add`-like